I would like to create two charts that are superimposed, but with two legends. One chart uses rects with one color palette, and the second chart displays circles with a second color palette. This should be very straightforward, but something is wrong. I only get a single legend. I also want the legends to be selectable. Here is a self-contained MWE, representative of a more complex use case. Below the code, I show an image of what the code produces: single legend, single color palette. Is this expected behavior or some kind of bug? Any insight is appreciated. Thanks!
streamimport pandas as pd
import altair as alt
import streamlit as st
# Demonstrate two categorical legends with selection_multi.
# There appears to be a bug when using shift-click on one menu, then the other.
def drawPlot():
x1 = [1, 2, 3]
y1 = [1, 2, 3]
x2 = [4, 5, 6]
y2 = [4, 5, 6]
df = pd.DataFrame({'x1':x1, 'y1':y1, 'x2':x2, 'y2':y2})
palette1 = alt.Color('x1:N',
scale=alt.Scale(
domain=[1, 2, 3],
range=['lightgreen', 'darkgreen', 'yellow'],
)
)
palette2 = alt.Color('x2:N',
scale=alt.Scale(
domain=[4, 5, 6],
range=['lightblue', 'darkblue', 'purple'],
)
)
select1 = alt.selection_multi(fields=['x1'], bind='legend')
select2 = alt.selection_multi(fields=['x2'], bind='legend')
nodes1 = alt.Chart(df).mark_rect(
width=20, height=20,
).encode(
x = 'x1:N',
y = 'y1:N',
color = palette1,
).add_selection(
select1
)
nodes2 = alt.Chart(df).mark_circle(
width=20, height=20, size=1200,
).encode(
x = 'x2:N',
y = 'y2:N',
color = palette2,
).add_selection(
select2
)
full_chart = (nodes1 + nodes2).properties(
height=500,
width=1000,
)
return full_chart
#----------------------------------------------------------------
if __name__ == "__main__":
chart = drawPlot()
st.altair_chart(chart, use_container_width=True)
Altair/Vega-Lite combine existing scales among charts into a single legend by default when possible for a more compact layout. When scales are independent of each other and should be represented in separate legends, you would need to resolve them manually, in your case it would look like this
chart.resolve_scale(color='independent')
You can read more on this page in the docs.
Related
I'm generating different Pie charts that have legends of different lengths. The problem is that when the legend is long, the Pie chart is smaller, I'd like to make the Pie chart always the same size.
This is my code:
pie_chart = go.Pie(labels=labels, values=y)
fig = go.Figure(data=[pie_chart])
fig.update_layout(legend={'font': {'size': 17}})
io_bytes = fig.to_image(format="png", scale=2.5, width=900, height=500)
These are the results:
Big pie chart, short legend:
Small pie chart, long legend:
Given that you've forced your image to a particular size, a long label is going to force the graph to get smaller and smaller. You might be expecting the labels to word-wrap, but they don't. You could attempt to implement some sort of word-wrapping capability to your labels, which might suit your needs.
import plotly.graph_objects as go
labels = ['This is a super long label name that would shrink your chart because there is not enough room','Hydrogen','Carbon_Dioxide','Nitrogen']
values = [4500, 2500, 1053, 500]
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.update_layout(legend={'font': {'size': 17}, })
fig.update_layout(
width=900,
height=500
)
fig.show()
Adding the html break tag every n words to turn label into a multi-line label.
import numpy as np
new_labels = []
max_label_length = 6
for label in labels:
l = label.split()
if len(l)>5:
l = np.array_split(l, int(len(l)/max_label_length))
l = ['<br>'.join(' '.join(x for x in w) for w in l)]
new_labels.append(l)
fig = go.Figure(data=[go.Pie(labels=new_labels, values=values)])
fig.update_layout(legend={'font': {'size': 17}, })
fig.update_layout(
width=900,
height=500
)
fig.show()
As per the documentation, the plot will normally resize to accommodate the legend. But you can use specific anchor points to help adjust where the legend sits. And thus restrict how it impacts the chart.
Example code:
import plotly.graph_objects as go
from IPython.display import Image
labels = ['This is a very very very long label to illustrate the point, that you can have very long labels','This is just another label']
y = [62, 38]
pie_chart = go.Pie(labels=labels, values=y)
fig = go.Figure(data=[pie_chart])
fig.update_layout(legend=dict(
font = dict(size=17),
orientation="v",
yanchor="bottom",
y=1.1,
xanchor="right",
x=1
))
io_bytes = fig.to_image(format="png", scale=2.5, width=900, height=500)
Image(io_bytes)
Output:
And one with short labels:
Is there a possibility to to change the frame color of link in a Sankey diagram?
So far I have the following code:
from plotly import graph_objects as go
label = ['Start', 'Intermediate', 'End']
source = [0, 0, 1]
target = [1, 2, 2]
value = [50, 10, 5]
link_color = ["#6E0CED", "#6E0CED","#D4000D"]
node_color = ["#6E0CED", "#D4000D","#ED682C"]
link = dict(
source = source,
target = target,
value = value,
color = link_color
)
node = dict(
label=label,
pad=35,
thickness=5,
color = node_color,
line = dict(color = "black", width = 0.5),
)
data = go.Sankey(link = link, node = node)
fig = go.Figure(data)
fig.update_layout(
title_text = "Title",
font =dict(size = 12, color = 'white'),
paper_bgcolor = "#000000"
)
fig.show()
resulting in the following plot:
The two purple flows are quite hard to distinguish, so I'd like to add a thin white frame on the top and bottom of each link to make them better distinguishable. So far I could not find any code so change only the frame color. I know that I could change one of the link's colors but I'd like to keep all links from one node the same color to reduce complexity when the diagram becomes bigger.
I have my plot clipped so it only shows certain ranges on the y axis. I added text to it using this code:
text2 = plot2.mark_text(align='left', dx=5, dy= -8, size = 15).encode(text = alt.Text('Accuracy', format = ',.2f'))
But this added annotation appears outside of the plot. So I need to get rid of it.
In the plot, I'm using sth like this:clip = True in mark_line().
You need to set clip=True for the text mark explicitly:
df = pd.DataFrame({'x': [1, 3], 'y': [1, 4], 'text': ['a', 'b']})
chart = alt.Chart(df).mark_line(clip=True).encode(
x=alt.X('x', scale=alt.Scale(domain=[0, 2])),
y='y'
)
chart + chart.mark_text().encode(text='text')
chart + chart.mark_text(clip=True).encode(text='text')
I want to create a lollipop plot with several horizontal line segments like this - https://python-graph-gallery.com/184-lollipop-plot-with-2-group. I'd like to use plotly since I prefer the graphics (and easy interactivity) but can't find a succint way.
There's both line graphs (https://plot.ly/python/line-charts/) and you can add lines in the layout (https://plot.ly/python/shapes/#vertical-and-horizontal-lines-positioned-relative-to-the-axes), but both of these solutions require each line segment to be added separately, with about 4-8 lines of code each. While I could just for-loop this, would appreciate if anyone can point me to anything with inbuilt vectorization, like the matplotlib solution (first link)!
Edit: Also tried the following code, to first make the plot ala matplotlib, then convert to plotly. The line segments disappear in the process. Starting to think it's just impossible.
mpl_fig = plt.figure()
# make matplotlib plot - WITH HLINES
plt.rcParams['figure.figsize'] = [5,5]
ax = mpl_fig.add_subplot(111)
ax.hlines(y=my_range, xmin=ordered_df['value1'], xmax=ordered_df['value2'],
color='grey', alpha=0.4)
ax.scatter(ordered_df['value1'], my_range, color='skyblue', alpha=1,
label='value1')
ax.scatter(ordered_df['value2'], my_range, color='green', alpha=0.4 ,
label='value2')
ax.legend()
# convert to plotly
plotly_fig = tls.mpl_to_plotly(mpl_fig)
plotly_fig['layout']['xaxis1']['showgrid'] = True
plotly_fig['layout']['xaxis1']['autorange'] = True
plotly_fig['layout']['yaxis1']['showgrid'] = True
plotly_fig['layout']['yaxis1']['autorange'] = True
# plot: hlines disappear :/
iplot(plotly_fig)
You can use None in the data like this:
import plotly.offline as pyo
import plotly.graph_objs as go
fig = go.Figure()
x = [1, 4, None, 2, 3, None, 3, 4]
y = [0, 0, None, 1, 1, None, 2, 2]
fig.add_trace(
go.Scatter(x=x, y=y))
pyo.plot(fig)
Plotly doesn't provide a built in vectorization for such chart, because it can be done easily by yourself, see my example based on your provided links:
import pandas as pd
import numpy as np
import plotly.offline as pyo
import plotly.graph_objs as go
# Create a dataframe
value1 = np.random.uniform(size = 20)
value2 = value1 + np.random.uniform(size = 20) / 4
df = pd.DataFrame({'group':list(map(chr, range(65, 85))), 'value1':value1 , 'value2':value2 })
my_range=range(1,len(df.index)+1)
# Add title and axis names
data1 = go.Scatter(
x=df['value1'],
y=np.array(my_range),
mode='markers',
marker=dict(color='blue')
)
data2 = go.Scatter(
x=df['value2'],
y=np.array(my_range),
mode='markers',
marker=dict(color='green')
)
# Horizontal line shape
shapes=[dict(
type='line',
x0 = df['value1'].loc[i],
y0 = i + 1,
x1 = df['value2'].loc[i],
y1 = i + 1,
line = dict(
color = 'grey',
width = 2
)
) for i in range(len(df['value1']))]
layout = go.Layout(
shapes = shapes,
title='Lollipop Chart'
)
# Plot the chart
fig = go.Figure([data1, data2], layout)
pyo.plot(fig)
With the result I got:
I am trying to make two sets of box plots using Matplotlib. I want each set of box plot filled (and points and whiskers) in a different color. So basically there will be two colors on the plot
My code is below, would be great if you can help make these plots in color. d0 and d1 are each list of lists of data. I want the set of box plots made with data in d0 in one color, and the set of box plots with data in d1 in another color.
plt.boxplot(d0, widths = 0.1)
plt.boxplot(d1, widths = 0.1)
To colorize the boxplot, you need to first use the patch_artist=True keyword to tell it that the boxes are patches and not just paths. Then you have two main options here:
set the color via ...props keyword argument, e.g.
boxprops=dict(facecolor="red"). For all keyword arguments, refer to the documentation
Use the plt.setp(item, properties) functionality to set the properties of the boxes, whiskers, fliers, medians, caps.
obtain the individual items of the boxes from the returned dictionary and use item.set_<property>(...) on them individually. This option is detailed in an answer to the following question: python matplotlib filled boxplots, where it allows to change the color of the individual boxes separately.
The complete example, showing options 1 and 2:
import matplotlib.pyplot as plt
import numpy as np
data = np.random.normal(0.1, size=(100,6))
data[76:79,:] = np.ones((3,6))+0.2
plt.figure(figsize=(4,3))
# option 1, specify props dictionaries
c = "red"
plt.boxplot(data[:,:3], positions=[1,2,3], notch=True, patch_artist=True,
boxprops=dict(facecolor=c, color=c),
capprops=dict(color=c),
whiskerprops=dict(color=c),
flierprops=dict(color=c, markeredgecolor=c),
medianprops=dict(color=c),
)
# option 2, set all colors individually
c2 = "purple"
box1 = plt.boxplot(data[:,::-2]+1, positions=[1.5,2.5,3.5], notch=True, patch_artist=True)
for item in ['boxes', 'whiskers', 'fliers', 'medians', 'caps']:
plt.setp(box1[item], color=c2)
plt.setp(box1["boxes"], facecolor=c2)
plt.setp(box1["fliers"], markeredgecolor=c2)
plt.xlim(0.5,4)
plt.xticks([1,2,3], [1,2,3])
plt.show()
You can change the color of a box plot using setp on the returned value from boxplot(). This example defines a box_plot() function that allows the edge and fill colors to be specified:
import matplotlib.pyplot as plt
def box_plot(data, edge_color, fill_color):
bp = ax.boxplot(data, patch_artist=True)
for element in ['boxes', 'whiskers', 'fliers', 'means', 'medians', 'caps']:
plt.setp(bp[element], color=edge_color)
for patch in bp['boxes']:
patch.set(facecolor=fill_color)
return bp
example_data1 = [[1,2,0.8], [0.5,2,2], [3,2,1]]
example_data2 = [[5,3, 4], [6,4,3,8], [6,4,9]]
fig, ax = plt.subplots()
bp1 = box_plot(example_data1, 'red', 'tan')
bp2 = box_plot(example_data2, 'blue', 'cyan')
ax.legend([bp1["boxes"][0], bp2["boxes"][0]], ['Data 1', 'Data 2'])
ax.set_ylim(0, 10)
plt.show()
This would display as follows:
This question seems to be similar to that one (Face pattern for boxes in boxplots)
I hope this code solves your problem
import matplotlib.pyplot as plt
# fake data
d0 = [[4.5, 5, 6, 4],[4.5, 5, 6, 4]]
d1 = [[1, 2, 3, 3.3],[1, 2, 3, 3.3]]
# basic plot
bp0 = plt.boxplot(d0, patch_artist=True)
bp1 = plt.boxplot(d1, patch_artist=True)
for box in bp0['boxes']:
# change outline color
box.set(color='red', linewidth=2)
# change fill color
box.set(facecolor = 'green' )
# change hatch
box.set(hatch = '/')
for box in bp1['boxes']:
box.set(color='blue', linewidth=5)
box.set(facecolor = 'red' )
plt.show()
Change the color of a boxplot
import numpy as np
import matplotlib.pyplot as plt
#generate some random data
data = np.random.randn(200)
d= [data, data]
#plot
box = plt.boxplot(d, showfliers=False)
# change the color of its elements
for _, line_list in box.items():
for line in line_list:
line.set_color('r')
plt.show()