Multiple stacked chart - python-3.x

I'm trying to reproduce this kind of chart:
So a bar chart with some stacked bar and some non stacked.
The closest I come by is this code:
import matplotlib.pyplot as plt
fooMeans = (20, 35, 30, 35, 27)
barMeans = (25, 32, 34, 20, 25)
ind = list(range(len(fooMeans)))
p1 = plt.bar(ind, barMeans, align='edge', width= 0.4)
p2 = plt.bar(ind, fooMeans, align='edge', width= 0.2)
p3 = plt.bar(ind, barMeans, bottom=fooMeans, align='edge', width= 0.2)
p4 = plt.bar(ind, fooMeans, align='edge', width= -0.2)
plt.xticks(ind, ('G1', 'G2', 'G3', 'G4', 'G5'))
plt.yticks(range(0, 81, 10))
plt.legend((p1[0], p2[0], p3[0], p4[0]), ('Foo', 'Bar','Fii', 'Fuu'))
plt.savefig('foo.png', bbox_inches='tight')
Which draws this:
That's not so bad but the x ticks are not aligned (probably because of the align='edge' of the bar) and the width trick that I use to show the bar next to each other (and not draw all the bar one on top of another) kind of look like an hack, is there any cleaner, by the books way to do this?

Okay, so I find a cleaner way.
The best seems to play with the x position list (ind in my previous code sample).
So I've come up with a little function that can handle it for me:
def make_indice_list(indices, bar_number, bar_width, spacing_ratio=0):
# "Center" the bar number around 0, not clear but if you have 3 bar,
# bar_number_indices = [-1, 0, 1]
bar_number_indices = [i - int(bar_number/2) for i in range(bar_number)]
indices_list = []
for number in bar_number_indices:
indices_list.append([ ( ((number* bar_width) + (spacing_ratio*bar_width) * number) + ind) for ind in indices])
return indices_list
And I do this to use it:
indice_list = make_indice_list(ind, 3, 0.2, 0.1)
p1 = plt.bar(indice_list[0], barMeans, width= 0.2)
p2 = plt.bar(indice_list[1], fooMeans, width= 0.2)
p3 = plt.bar(indice_list[1], barMeans, bottom=fooMeans, width= 0.2)
p4 = plt.bar(indice_list[2], fooMeans, width= -0.2)
No more center, nor weird width, you just have to use the same indices for the stacked bar (and consider them as one when calling the function).
Wich, at the end, draw this:
You could probably make some improvement for the make_indice_list function, mostly removing the comprehension list and using numpy list but I think that's a sweet solution to me.

Related

Is there a way to prevent a rectangular pyqtgraph ROI to not move left-right?

I have this snippet from part of a code I'm working on with pyqtgraph ROI. I have also set boundaries in which the ROI cannot cross on all sides. When I move either of the vertical scale handles, it gives the ROI an opportunity to move left-right if click inside of it. Is there a way to prevent this from happening? I only want it to move up-down. This is kind of like setting another bound within the original bound to the ROI depending on the current X start position and X stop position.
I tried modifying the self.maxBound variable in the ROI class using signals but it became messy, and the ROI kept ignoring the right bound.
Any help will be greatly appriciated
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QGridLayout,
QWidget)
import pyqtgraph as pg
import sys
import numpy as np
from PyQt5.QtCore import QRectF, QRect
class TestROI(QMainWindow):
imageWidth = 1000
imageHardLimitTop = 1048
imageHardLimitBottom = 2047
shallowestPixel = 1400
deepestPixel = 1699
ROITopBottomMargin = 20
shaderDarkness = 185
shaderColor = (0, 0, 50, shaderDarkness)
imageHardLimitDepth = imageHardLimitBottom - imageHardLimitTop + 1
def __init__(self):
super(TestROI, self).__init__()
self.mainWidget = QWidget()
self.imagePlotCanvas = pg.GraphicsLayoutWidget()
self.graphGridLayout = QGridLayout(self.mainWidget)
self.graphGridLayout.addWidget(self.imagePlotCanvas)
self.setCentralWidget(self.mainWidget)
self.showMaximized()
# Place for 2D images:
self.plotArea = self.imagePlotCanvas.addPlot()
self.plotArea.setRange(
QRect(0, self.imageHardLimitTop, self.imageWidth, self.imageHardLimitDepth))
self.plotArea.setLimits(xMin=-1000, xMax=2000)
self.plotArea.setAspectLocked(True)
self.plotArea.invertY(b=True)
self.plotArea.showGrid(x=True, y=True, alpha=1.0)
self.plotArea.showButtons()
self.twoDImageROI = pg.ROI(pos=[0, self.shallowestPixel],
size=[
self.imageWidth, self.deepestPixel - self.shallowestPixel + 1],
removable=False,
maxBounds=QRectF(-1, self.imageHardLimitTop-1,
self.imageWidth + 2, self.imageHardLimitDepth+1),
scaleSnap=False, translateSnap=False)
self.twoDImageROI.setZValue(20)
self.shadedTopRegion = pg.LinearRegionItem(
[self.imageHardLimitTop, self.shallowestPixel], orientation=pg.LinearRegionItem.Horizontal, movable=False)
self.shadedTopRegion.setBrush(color=self.shaderColor)
self.shadedTopRegion.setZValue(10)
self.plotArea.addItem(self.shadedTopRegion)
self.shadedBottomRegion = pg.LinearRegionItem(
[self.deepestPixel + 1, self.imageHardLimitBottom+1], orientation=pg.LinearRegionItem.Horizontal, movable=False)
self.shadedBottomRegion.setBrush(color=self.shaderColor)
self.shadedBottomRegion.setZValue(10)
self.plotArea.addItem(self.shadedBottomRegion)
# self.twoDImageROI.setAcceptedMouseButtons(Qt.LeftButton)
self.twoDImageROI.sigRegionChanged.connect(self.imageROIChanged)
# Shaded Region on the left and right to cover up the above two vertical lines when out of image bound.
self.shadedLeftRegion = pg.LinearRegionItem(
[-1000, 0], orientation=pg.LinearRegionItem.Vertical, movable=False)
self.shadedLeftRegion.setBrush(color=(0, 0, 0))
self.shadedLeftRegion.setZValue(20)
self.plotArea.addItem(self.shadedLeftRegion)
self.shadedRightRegion = pg.LinearRegionItem(
[1000, 2000], orientation=pg.LinearRegionItem.Vertical, movable=False)
self.shadedRightRegion.setBrush(color=(0, 0, 0))
self.shadedRightRegion.setZValue(20)
self.plotArea.addItem(self.shadedRightRegion)
self.twoDImageROI.addScaleHandle([0.5, 0.0], [0.5, 1.0], index=0)
self.twoDImageROI.addScaleHandle([0.5, 1.0], [0.5, 0.0], index=1)
self.twoDImageROI.addScaleHandle([1.0, 0.5], [0.5, 0.5])
self.twoDImageROI.addScaleHandle([0.0, 0.5], [0.5, 0.5])
self.twoDOCMImage = pg.ImageItem(border='c')
self.twoDOCMImage.setParentItem(self.plotArea)
self.plotArea.addItem(self.twoDOCMImage)
self.plotArea.addItem(self.twoDImageROI)
zeroImage = np.zeros(self.imageWidth * self.imageHardLimitDepth)\
.reshape((self.imageWidth, self.imageHardLimitDepth))
self.twoDOCMImage.setImage(zeroImage)
startingRect = QRect(0, self.imageHardLimitTop,
self.imageWidth, self.imageHardLimitDepth)
self.twoDOCMImage.setRect(startingRect)
def imageROIChanged(self):
x, top = self.twoDImageROI.pos()
top = int(round(top, 0))
w, h = self.twoDImageROI.size()
h = int(round(h, 0))
bot = top + h - 1
self.shallowestPixel = top
self.deepestPixel = bot
self.updateLinearRegion()
def updateLinearRegion(self):
x, top = self.twoDImageROI.pos()
top = int(round(top, 0))
w, h = self.twoDImageROI.size()
h = int(round(h, 0))
bot = top + h - 1
self.shadedTopRegion.setRegion([self.imageHardLimitTop, top])
self.shadedBottomRegion.setRegion([bot, self.imageHardLimitBottom+1])
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = TestROI()
main_window.show()
sys.exit(app.exec_())
I believe that issue You are talking about is this 1px wiggle from left to right when moving ROI up and down. This effect is due to the translation of real ROI position to plot X, Y values. There will always be rounding and thus ROI will always wiggle a bit left and right.
To prevent this, You can manually set constant x position of Your ROI. For that, You have to override setPos of ROI class.
Here is example ROI class, that You can use:
class ConstantXROI(ROI):
constant_x = 0
def setPos(self, pos, y=None, update=True, finish=True):
pos.setX(self.constant_x)
super().setPos(pos, y=y, update=update, finish=finish)
Then in Your code, just use:
self.twoDImageROI = ConstantXROI(...)

Annotate Percentage of Group within a Seaborn CountPlot

The below code gets the percentage of all collisions. However, I want to get the percentage within a group. E.G. Mid-Block (not related to intersection) has 2 labels, a 1(red) or a 2(green/blue). Currently, the percentages next to those bars are percentages of the whole (bar count / all collisions), but I need to display the percentage within just one y-axis label. E.G. for Mid-block (not related to intersection), bar count / all collisions within mid-block (not related to intersection). I do not know how to do this, so if someone could point me in the right direction or give me some code that I could study to understand, I'd be very grateful.
Thank you so much for your time.
plt.style.use('ggplot')
plt.figure(figsize = (20, 15))
ax = sb.countplot(y = "JUNCTIONTYPE", hue = "SEVERITYCODE", data = dfm)
plt.title('Number of Persons vs. Number of Collisions by Severity', fontsize = 30)
plt.xlabel('Number of Collisions', fontsize = 24)
plt.ylabel('Number of Persons', fontsize = 24)
plt.tick_params(labelsize=18);
plt.legend(fontsize = 18, title = "Severity", loc = 'lower right')
plt.text(5, 6, "Figure 8: Number of persons plotted against the number of collisions grouped by severity", fontsize = 16)
# labels = [item.get_text() for item in ax.get_yticklabels()]
# labels[0] = 'No'
# labels[1] = 'Yes'
# ax.set_yticklabels(labels)
for p in ax.patches:
width = p.get_width()
height = p.get_height()
x, y = p.get_xy()
ax.annotate(int(width),
((x + width), y),
xytext = (30, -25),
fontsize = 18,
color = '#000000',
textcoords = 'offset points',
ha = 'right',
va = 'center')
for p in ax.patches:
width = p.get_width()
height = p.get_height()
x, y = p.get_xy()
totals = []
for i in ax.patches:
totals.append(i.get_width())
total = sum(totals)
ax.text(width + 0.3, y + 0.38,
str(
round((width/total) * 100, 2))
+ '%',
fontsize=18)
You could pre-calculate the per-group percentage points and use the order in which seaborn / matplotlib draws the bars to reference them.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
titanic = sns.load_dataset('titanic')
# prepare the dataset
df = (titanic
.groupby(["embark_town", "survived"])
.size()
.reset_index()
.replace({"survived": {0:"no", 1:"yes"}})
.rename(columns={0:"count"}))
# calculate survival % per town of embarkation
df["percent"] = (df
.groupby("embark_town")
.apply(lambda x: x["count"] / x["count"].sum()).values)
# sort the dataframe to match the drawing order
df.sort_values(by=["survived", "embark_town"], inplace=True)
# visualisation
plt.style.use('ggplot')
fig = sns.catplot(
x="count", y="embark_town", hue="survived",
kind="bar", data=df, height=4, aspect=2)
for i, bar in enumerate(fig.ax.patches):
height = bar.get_height()
fig.ax.annotate(
# reference the pre-calculated row in the dataframe
f"{df.iloc[i, 3] :.0%}",
xycoords="data",
xytext=(20, -15),
textcoords="offset points",
xy=(bar.get_width(), bar.get_y()),
ha='center', va='center')
# make space for annonations
plt.margins(x=0.2)
plt.show()

How do I put a simple caption below my x axis in Matplotib?

I am trying to put a simple description of my plot right below the x axis with plt.text. Either there is not enough room or it's in my plot. Can someone help. Here is my code and what it looks like.
def econPlot1(plot1_data):
x = list(range(plot1_data.shape[0]))
y1 = plot1_data[:, 1]
# plotting the line 1 points
plt.plot(x, y1, label = "FFR")
# line 2 points
y2 = plot1_data[:, 2]
#fig = plt.figure()
plt.axis([0, 10, 0, 10])
t = ("This is a really long string that I'd rather have wrapped so that it "
"doesn't go outside of the figure, but if it's long enough it will go "
"off the top or bottom!")
plt.text(-1, 0, t, ha='center', rotation=0, wrap=True)
# plotting the line 2 points
plt.plot(x, y2, label = "Inflation")
plt.xlabel('time')
x_tick_indices = list(range(0, plot1_data.shape[0], 12))
x_tick_values = x_tick_indices
x_tick_labels = [plot1_data[i, 0] for i in x_tick_indices]
plt.xticks(x_tick_values, x_tick_labels, rotation ='vertical')
# Set a title of the current axes.
plt.title('FFR vs Inflation over time')
# show a legend on the plot
#plt.legend()
# Display a figure.
plt.show()
logging.debug('plot1 is created')
I managed to put your text at the bottom of the figure the following way:
import textwrap
# Operations on the source data
x = list(range(plot1_data.shape[0]))
y1 = plot1_data[:, 1]
y2 = plot1_data[:, 2]
x_tick_indices = list(range(0, plot1_data.shape[0], 12))
x_tick_values = x_tick_indices
x_tick_labels = [plot1_data[i, 0] for i in x_tick_indices]
t = "This is a really long string that I'd rather have wrapped so that it doesn't go "\
"outside of the figure, but if it's long enough it will go off the top or bottom!"
tt = textwrap.fill(t, width=70)
# Plotting
plt.plot(x, y1, label='FFR')
plt.plot(x, y2, label='Inflation')
plt.xlabel('Time')
plt.xticks(x_tick_values, x_tick_labels, rotation ='vertical')
plt.title('FFR vs Inflation over time')
plt.text(len(x) / 2, 0, tt, ha='center', va='top');
My experience indicates that plt.text does not support wrap parameter,
so I wrapped it using textwrap.fill.
I also didn't call plt.axis, relying on default limits for both x and y. If you need to set limits, do it rather only for y axis,
e.g. plt.ylim((0, 8)), but then you will have to adjust also y parameter
in plt.text.
For source data limited to 3 years (for each month in these 3 years and
Jan 1 the next year) I got the following result:

tkinter histogram prints downwards

I am trying to draw two histograms alongside one another using tkinter canvas. Everything sort of works ( looks extremely scruffy at the moment) but the histograms are drawn downwards. I have tried making the y0 value negative, but then nothing at all is drawn.
I am using two lists of numerical data, the first with 50 observations and the other with eleven observations, the scales are not the same, but it is the qualitative effect I want at the moment.
The offending code is as follows:
root = Tk()
canvas = Canvas(root, width=620, height=400, background = "salmon")
canvas.grid()
# draw x-axis lines
canvas.create_line(0,2, 500, 0, width = 2, fill = "firebrick")
canvas.create_line(505,2, 610, 0, width = 2, fill = "dark slate blue")
# draw histograms
for idx in range(len(main_counts[0])):
canvas.create_rectangle(idx*10, main_counts[0][idx], 10 +(idx*10), 0, fill = "medium sea green", outline = "firebrick")
canvas.create_text(idx*10 + 8, 40, text = idx + 1, font = ("Comic sans MS",8), fill = "firebrick")
for idx in range(len(star_counts[2])):
canvas.create_rectangle((505 + idx*10), star_counts[2][idx], (515 + (idx*10)), 0, fill = "gold", outline = "dark slate blue")
canvas.create_text(505 + idx*10 + 8, 120, text = idx + 1, font = ("Comic sans MS", 8) , fill = "dark slate blue")
root.mainloop()
I know that I am missing something quite simple and obvious to all of you, but I just can't see it or the way to make my y0 negative which will presumably solve the problem. I can also not see my x-axes, but that may be because they are occluded by the histogram bars.
Many thanks for your patience and help! Any other suggestions about formatting the graphs will be welcomed including suggestions of best font to use for small digit screen display
The system coordinates start in the upper-left corner so you should write something like:
main_counts =[[10, 20, 30]]
for idx in range(len(main_counts[0])):
canvas.create_rectangle(idx*10, 200 -main_counts[0][idx], 10 +(idx*10), 200, fill = "medium sea green", outline = "firebrick")
canvas.create_text(idx*10 + 8, 210, text = idx + 1, font = ("Comic sans MS",8), fill = "firebrick")
You can use enumerate for more readable code:
for idx, val in enumerate(main_counts[0]):
canvas.create_rectangle(idx*10, 200 -val, 10 +(idx*10), 200, fill = "medium sea green", outline = "firebrick")
canvas.create_text(idx*10 + 8, 210, text = idx + 1, font = ("Comic sans MS",8), fill = "firebrick")
In simple terms - Try to create the histogram from a point on the coordinate according to your value as your second argument in rectangle_crete() function, and then go upto the point where you want your base of the histogram should,which will me constant for all your histograms. Because Tkinter coordinates starts from (0,0) and goes from up to down.
An example code is here -
from Tkinter import *
from random import randint # for testing histogram
master = Tk()
w = Canvas(master, width=1000, height=500)
w.pack()
start_point = 70
padding_y = 450
width = 50
height = 450
list = []
for i in range(1,10):
list.append(randint(1,4))
for i in range(1,10):
end_point = start_point+width
w.create_rectangle(start_point, list[i-1]*100, end_point, height, fill="blue")
start_point += width+20
mainloop()

Can i make the variable used in commands lock when using buttons and changing variables

I am making a Rubik's cube solver using Python and Tkinter and I have encountered a problem when trying to reduce the size of my code.
The peice of code I am showing is for getting to know the situation of the Cube. It draws a net of a cube using buttons with images, but they don't have fixed variables and so when i change a the variable the are defined with, the command variable also changes. is there a way to get round it simply
#Defines Images
White = PhotoImage(file="White.gif")
Yellow = PhotoImage(file="Yellow.gif")
Blue = PhotoImage(file="Blue.gif")
Green = PhotoImage(file="Green.gif")
Red = PhotoImage(file="Red.gif")
Orange = PhotoImage(file="Orange.gif")
#List with Images, positions and values
Colours = [[White, 550, 40, 0, "White"],
[Yellow, 550, 520, 1, "Yellow"],
[Blue, 790, 280, 2, "Blue"],
[Green, 310, 280, 3, "Green"],
[Red, 550, 280, 4, "Red"],
[Orange, 70, 280, 5, "Orange"]]
#Testing Function
def swicth(a):
global Colours
print(Colours[a][4])
#Creates the Buttons in specific locations
for i in Colours:
for j in range(3):
yOffset = (j * 80) + i[2]
for k in range(3):
xOffset = (k * 80) + i[1]
Button(Solver, image = i[0], command=lambda:swicth(i[3])).place(x=xOffset, y=yOffset)
This code works for the most part, but all the buttons have the same outcome of 5. I have got ways of getting around by having 6 sets of for loops for the six colours, but it would be great if anyone could help
change your lambda to this:
..., command=lambda arg=i[3]:swicth(arg)
This will cause i[3] to be bound to the lambda at the time the lambda is created.

Resources