bokeh, dynamically change plot width - python

I'm trying to change plot width when taptool selects something.
I invoke this using bokeh serve, and navigating to the localhost website
when I click on one of the rectangles, my console prints out "callback" and "callback2" but my plot doesn't change width.
What am I doing wrong?
counts = [1*10**7,2*10**7,3*10**7] #dummy data
l_edge = [x for x in range(len(counts))]
r_edge = [x + .85 for x in range(len(counts))]
data = {
'height': counts,
'leftEdges': l_edge,
'rightEdges': r_edge,
}
p = figure()
s = ColumnDataSource(data)
p.add_tools(TapTool())
def callbackfcn(attr,old,new):
global p
print('callback')
p.width = np.random.choice([100,200,300,1000,10000])
p.height = np.random.choice([100,200,300,1000,10000])
print('callback2')
r = p.quad(top='height',bottom=0,
left = 'leftEdges',right = 'rightEdges',
source = s)
r.data_source.on_change('selected',callbackfcn)
layout = column(p)
curdoc().add_root(layout)

Have you tried instead including sizing_mode in your layout? Take a look at the Bokeh docs for an example.

Related

Custom JS Callback for allowing only one circle to be shown on hovering in Circle plot in Bokeh

I am new to Bokeh I am trying to replicate the line plots shown in https://www.worldometers.info/coronavirus/ using Bokeh.Here is my full code
from datetime import datetime, timedelta, date
import requests
import json
from bokeh.plotting import output_notebook, figure, show
from bokeh.models import ColumnDataSource, HoverTool, Title
def DateFormatter(x):
s = '01222020'
global given_date
given_date = datetime(month=int(s[:2]), day=int(s[2:4]), year=int(s[4:]))
given_date += timedelta(days=x)
final = str(given_date.strftime('%m{}%d{}%y').format('/','/'))
if int(final[0:2])<10 and int(final[3:5])<10:
final = final[1:]
final = final[0 : 2 : ] + final[3 : :]
elif int(final[0:2])<10:
final = final[1:]
elif int(final[3:5])<10:
final = final[0 : 2 : ] + "/" + final[4 : :]
return final
def DateFormatterForPlot(x):
s_plot = '01222020'
global given_date_plot
given_date_plot = datetime(month=int(s_plot[:2]), day=int(s_plot[2:4]), year=int(s_plot[4:]))
given_date_plot += timedelta(days=x-1)
final_plot1 = str(given_date_plot.strftime('%b %d'))
if int(final_plot1[4:6])<10:
final_plot1 = final_plot1[0 : 4 : ] + final_plot1[5 : :]
return final_plot1
finallist=[]
l2 = []
plot_list = []
country = "India"
chart_type = "cases"
base_site = f'https://disease.sh/v3/covid-19/historical/{country}?lastdays=all'
r = requests.get(base_site)
if r.status_code == 200:
packages_json = r.json()
today_date = str(date.today().strftime('%m{}%d{}%y').format('/','/'))
if int(today_date[0:2])<10 and int(today_date[3:5])<10:
today_date = today_date[1:]
today_date = today_date[0 : 2 : ] + today_date[3 : :]
elif int(today_date[0:2])<10:
today_date = today_date[1:]
elif int(today_date[3:5])<10:
today_date = today_date[0 : 2 : ] + "/" + today_date[4 : :]
for i in range(1,1000):
current_date = DateFormatter(i)
if current_date==today_date:
f_date = datetime(2020, 1, 22)
delta = given_date - f_date
break;
for i in range(delta.days):#Day 157 is 26th June
try:
a = DateFormatter(i)
packages_str = json.dumps(packages_json['timeline'][chart_type][a], indent=2)
finallist.append(int(packages_str))
except KeyError:
pass
count = 0
for i in finallist:
count = count + 1
for i in range(1,count+1):
l2.append(i)
for i in l2:
plot_list.append(DateFormatterForPlot(i))
else:
print("Not a valid country")
source = ColumnDataSource(data=dict(y=finallist, x=l2, desc=plot_list))
TOOLTIPS = """
<style>
.bk-tooltip>div:not(:first-child) {display:none;}
</style>
<b>X: </b> #desc <br>
<b>Y: </b> #y{0,0}
"""
plot = figure(background_fill_color='#fafafa', x_axis_label='Days', y_axis_label='Coronavirus {}'.format(chart_type.capitalize()), plot_width=1200, plot_height=400, toolbar_location=None)
plot.line('x', 'y', source=source, legend = 'Number of {}'.format(chart_type.capitalize()), line_width=2, color='gray')
cr = plot.circle('x', 'y', size=10, source=source, fill_color="grey", hover_fill_color="gainsboro", fill_alpha=0.1, line_color=None, hover_line_color="white", hover_fill_alpha=1)
plot.add_tools(HoverTool(tooltips=TOOLTIPS, renderers=[cr]))
plot.add_layout(Title(text="Coronavirus {} Example Graph".format(chart_type.capitalize()), align="center"), "above")
plot.legend.location = 'top_left'
plot.left[0].formatter.use_scientific = False #Used to disable scientific notation on y-axis
The problem that is arising here is that more than one circle glyphs are being shown when cursor is hovered on the area of the plot where the density of circle glyphs are high.
I think there is a CustomJS callback for Hovertool to allow only one circle glyph to be shown on hovering mouse over it but I am not able to implement it.
Output of the snippet can be seen here http://geetanshjindal.pythonanywhere.com/charts/
I am using the latest bokeh version 2.1.1 and python version 3.6
I think you have three options and all have to do with de-densifying your plot:
Reduce the size of the circle glyphs so that they don't overlap. size=5 seems to work OK for your plot width.
Increase plot width so that the circle glyphs are more spaced out. Of course, this will add a scroll bar so might not be the best user experience.
Don't remove the Bokeh toolbar and encourage the user to interact with the plot - zooming in to see all circle glyphs.
The approach of selectively showing tooltips using CSS is interesting, but it hides the decision-making ("show the tooltip only for the first circle of the overlapping two") from the user, potentially causing confusion.
One alternative approach would be to remove the tooltips altogether and add annotations in key places instead (passing a million of cases?), as well as putting a table with all dates and values as a side panel for reference.
For annotations, you can use a combination of a Label and Bezier curve models:
from bokeh.models import Label, Bezier
test_label = Label(x=50, y=550000, x_units='data', y_units='data',
text='Custom description of the datapoint', render_mode='css',
border_line_color='white', border_line_alpha=1.0,
border_line_width=3, background_fill_color='white',
background_fill_alpha=1.0)
# play around with control point values for best effect
curve_source = ColumnDataSource(dict(
x0=[150],
y0=[395048],
x1=[130],
y1=[600000],
cx0=[150],
cy0=[500000],
cx1=[150],
cy1=[600000]
)
)
test_curve = Bezier(
x0="x0", y0="y0", x1="x1", y1="y1",
cx0="cx0", cy0="cy0", cx1="cx1", cy1="cy1",
line_color="green", line_width=1)
plot.add_glyph(curve_source, test_curve)
plot.add_layout(test_label)

Update python bokeh layout using bokeh server?

What I want to do:
I made a dynamic map using bokeh, pandas and geopandas. The data to be displayed is loaded from a table, then mapped to a country per year. The year to be displayed is determined by a bokeh slider. You can also hover over a country and get its value. I now want to be able to change the data source by selecting a radio button. To display the data correctly, I want to change the color palette, rescale it (a range from e.g. 50 to 100 instead of 0 to 4.5), update the scaling on the slider to the new lowest year to highest year, and then display the world-map with the new data. I also want to update the title of the map from e.g. "Fertility" to "Life expectancy".
What I already have:
I have a working dynamic map with Slider and Hover tool. I also have a list from which the data to be used is taken (datapath, title to be used, color palette to be used, highest and lowest year, highest and lowest value). I have a radio button group, with three different data sources to choose from. All paths are relative, the data is provided with a consistent structure. I had the map changing the data below and displaying the new stuff, but than I did something and it stopped working. I also had the Hover tool display the right values, but with a wrong (old) description.
What I need help with:
Updating the color bar to accomodate the new palette and the new range
Updating the slider to accomodate a changed range
Updating the title displayed to show what's actually displayed
What I already tried:
I've put the whole loading and displaying in the function executed when the radio button group is changed. The first thing this function does is clearing the layout and then rebuilding it. Unfortunately, this is neither efficient, nor working, since I only get the radio button Group and an empty space below, no matter what I do. I've searched for a solution, but all I found (and tried) didn't do what I needed.
I can provide the actual code, if needed (though some of the variables and documentations are in German), but since I'm pretty new to the whole python thing, I don't now, what exactly of that about 300 lines of code you need. Just let me now, and I'll try and provide.
Hope you can help me with that.
Thanks in advance,
Asd789
EDIT: As correctly pointed out in the comments, some code to help understand what I did.
I'll cut all the imports for the sake of brevity, as a mistake there would have shown up as error in my terminal. Also leaving out comments.
geoFrame #dataframe for geopanda shapefile
configList = [0, "Fertility", 'YlGnBu', 'Year' ]
df #dataframe for the .csv file
higStep = df['step'].max()
lowStep = df['step'].min()
configList.append(higStep)
higVal = df['valueInterest'].max()
merged = geoFrame.merge(df, left_on = 'country_code', right_on = 'code')
merged_json = json.loads(merged.to_json())
json_data = json.dumps(merged_json)
geosource = GeoJSONDataSource(geojson = json_data)
palette = brewer[configList[2]][8]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = 4)
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal')
p = figure(title = configList[1], plot_height = 600 , plot_width = 950, toolbar_location = None)
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches('xs','ys', source = geosource,fill_color = {'field' :'valueInterest', 'transform' : color_mapper},
line_color = 'black', line_width = 0.25, fill_alpha = 1)
p.add_layout(color_bar, 'below')
df_curr = df[df['step'] == higStep]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = 40, nan_color = '#d9d9d9')
def json_data(selectedStep):
st = selectedStep
df_st = df[df['step'] == st]
merged = geoFrame.merge(df_st, left_on = 'country_code', right_on = 'code', how = 'left')
merged_json = json.loads(merged.to_json())
json_data = json.dumps(merged_json)
return json_data
geosource = GeoJSONDataSource(geojson = json_data(higStep))
palette = brewer[configList[2]][8]
palette = palette[::-1]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = higVal/2, nan_color = '#d9d9d9')
hover = HoverTool(tooltips = [ ('Country/region','#country'),(configList[1], '#valueInterest')])
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal')
p = figure(title = configList[1], plot_height = 600 , plot_width = 950, toolbar_location = None, tools = [hover])
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches('xs','ys', source = geosource,fill_color = {'field' :'valueInterest', 'transform' : color_mapper},
line_color = 'black', line_width = 0.25, fill_alpha = 1)
p.add_layout(color_bar, 'below')
def update_plot(attr, old, new):
st = slider.value
new_data = json_data(st)
geosource.geojson = new_data
p.title.text = configList[1] %st
slider = Slider(title = configList[3],start = 1950, end = 2015, step = 1, value = 2015)
slider.on_change('value', update_plot)
def radHandler(attr, new, old):
if radio.active == 0:
datapath = os.path.join(dataloc, 'children-per-woman-UN.csv')
configList = [0, "Fertility", 'YlGnBu', 'Year']
elif radio.active == 1:
#see above with diferent data
elif radio.active == 2:
#see above with different data
curdoc().clear()
higStep = df['step'].max()
lowStep = df['step'].min()
configList.append(higStep)
update_plot(attr, new, old)
hover = HoverTool(tooltips = [ ('Country/region','#country'),(configList[1], '#valueInterest')])
palette = brewer[configList[2]][8]
palette = palette[::-1]
olor_mapper = LinearColorMapper(palette = palette, low = 0, high = higVal/2, nan_color = '#d9d9d9')
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal'
p = figure(title = configList[1]+' '+str(configList[4]), plot_height = 600 , plot_width = 950, toolbar_location = None, tools = [hover])
layout = column(widgetbox(radio),p,widgetbox(slider))
curdoc().add_root(layout)
radio = RadioButtonGroup(labels=['Fertility', 'Life expectancy', 'Covid-19 total cases'], active=0)
radio.on_change('active',radHandler)
layout = column(p, widgetbox(radio),widgetbox(slider))
curdoc().title = configList[1]
curdoc().add_root(layout)
Sorry I could'nt cook it down further, but I don't know what's essential and what's just fancy stuff around.
This code works until I touch the radio button group. After that, the plot itself is just blank, without any prompts anywhere. The code itself is not entirely my fault, I got it to fix and maybe expand on it, the expansion being the ability to switch between data sources as decribed above.

How do I access and change a column in ColumnDataSource in bokeh?

I wanted to create a plot with bokeh in python which runs quite well so far. But now I wanted to add a Slider and tell him to hide all bars in my vbar plot which are lower than the value of the slider.
current = df[(df['ID'] > num_tokens.value[0])].dropna()
source.data = {
'ID': current.ID
}
I tried to create a variable 'current' and assign it to the 'ID' column so that the plot can update the plot. But I always get a TypeError: Int is not subscriptable. How can I make my slider widget make work?
Thank you in advance
Don't know if we must close this issue or not but I would sugget using a customJS callback:
Create initially a source and a render_soruce from df
source = ColumnDataSource(df)
renderer_source = ColumnDataSource(df)
Then define your callback and your slider
code = """
var slider_value= cb_obj.value; //cb_obj is your slider widget
var data=source.data;
var data_id = data['ID'];
var data_x=data['x'];
var data_y=data['y'];
var render_x=render['x'];
var render_y=render['y'];
var x = [];
var y = [];
render_x=[];
render_y=[];
for (var i=0;i<data_id.length; i++){
if (data_id[i]== slider_valuer) {
x.push(data_x[i]);
y.push(data_y[i]);
}
renderer_source.data['x']=x;
renderer_source.data['y']=y;
renderer_source.change.emit();
"""
callback = CustomJS(args=dict(source=source, renderer_source=renderer_source), code=code)
slider = Slider(start=0, end=(max_value_o_slider), value=1, step=1, title="Frame")
slider.js_on_change('value', callback)
And identify source=renderer_source in your plot
You can achieve this with almost no JavaScript using a view:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CDSView, CustomJSFilter, Slider, CustomJS
from bokeh.plotting import figure
N = 5
x = list(range(1, N + 1))
top = list(range(1, N + 1))
# Specifying manual ranges to prevent range changes when some bars are hidden.
p = figure(x_range=(0, N + 1), y_range=(0, N + 1))
ds = ColumnDataSource(data=dict(x=x, top=top))
s = Slider(start=0, end=N, value=0)
# Making sure that everything that depends on the `ds`
# is notified when the slider value is changed.
s.js_on_change('value', CustomJS(args=dict(ds=ds),
code="ds.change.emit();"))
view = CDSView(source=ds,
filters=[CustomJSFilter(args=dict(ds=ds, slider=s),
code="return ds.data['top'].map((top) => top > slider.value);")])
p.vbar(x='x', width=0.7, top='top', bottom=0, source=ds, view=view)
curdoc().add_root(column(p, s))

QWebEngineView: "cannot read property 'pageX' of undefined" when muting bokeh legend

I'm using PyQt5 to create a GUI and in this GUI I visualize Bokeh graphs using QWebEngineView.
It works fine but when I tried to implement the "muting" legend like this I get an error:
js: Uncaught TypeError: Cannot read property 'pageX' of undefined
If I use the show method, I get the expecting result in my browser. However if I use save and display it to the QWebEngineView I get the mentioned error.
Any ideas?
The slot in my Gui Class to plot and show in the QWebEngineView:
Notes: Ignore the Bar and Pizza plots, it is the scatter and line that is relevant to this matter
def plotGraph(self, df=None):
# Get parameters to plot
x = str(self.ui.comboBox_x_axis.currentText())
y = str(self.ui.comboBox_y_axis.currentText())
# Define axis types
try:
x_axis_type = str(
self.ui.comboBox_plot_scale.currentText()).split('-')[0]
y_axis_type = str(
self.ui.comboBox_plot_scale.currentText()).split('-')[1]
except:
x_axis_type = 'auto'
y_axis_type = 'auto'
# Define kind of graph
kind = str(self.ui.comboBox_plot_style.currentText())
# For bar chart define groups
group = str(self.ui.comboBox_group.currentText())
# Prepare data for plot
if (kind == 'bar' and group != "Don't group"):
data = df[[x, y, group]]
else:
data = df[[x, y]]
data = data.sort_values(x, axis=0)
# Dynamically define plot size
width = round(self.ui.webViewer.frameGeometry().width())
height = round(self.ui.webViewer.frameGeometry().height())
# Plot and save html
self.plot = self.graph.plot(
data, kind, x_axis_type, y_axis_type, width, height)
self.plot_num = 1
# Display it at QWebEngineView
self.ui.webViewer.setUrl(QtCore.QUrl(
"file:///C:/Users/eandrade_brp/Documents/git/tl-data-viewer/plot.html"))
Here is the Graph class that handles all the bokeh plots (I omitted some non necessary code)
class Graph(object):
"""docstring for ClassName"""
def __init__(self, file_name="plot.html"):
super(Graph, self).__init__()
output_file(file_name)
def plot(self, data, kind, x_axis_type, y_axis_type, width, height):
p = None
if kind == 'scatter' or kind == 'line':
layout, p = self.createFigure(
data, kind, x_axis_type, y_axis_type, width, height)
elif kind == 'bar':
layout = self.plot_Bar(data, width, height)
elif kind == 'pizza':
layout = self.plot_Pizza(
data, width, height)
# Show/save
save(layout)
return p
def createFigure(self, data, kind, x_axis_type, y_axis_type, width, height):
source, xdata, ydata, xvalues, yvalues = self.prepare_data(data)
# Define tool
tools = "pan, box_zoom, lasso_select, undo, redo"
wheel_zoom = WheelZoomTool()
hover = HoverTool(
tooltips=[
(data.columns[0], '$x'),
(data.columns[1], '$y')],
mode='mouse')
# Create first figure and customize
fig1 = figure(title="{} vs {}" .format(ydata, xdata), tools=tools,
x_axis_type=x_axis_type, y_axis_type=y_axis_type,
toolbar_location="right", plot_width=round(0.9 * width),
plot_height=round(0.75 * height))
fig1.add_tools(wheel_zoom)
fig1.add_tools(hover)
fig1.toolbar.active_scroll = wheel_zoom
fig1.background_fill_color = "beige"
fig1.background_fill_alpha = 0.4
# Create second figure and customize
fig2 = figure(title='Overview', title_location="left",
x_axis_type=x_axis_type, y_axis_type=y_axis_type,
tools='', plot_width=round(0.9 * width), plot_height=round(0.25 * height))
fig2.xaxis.major_tick_line_color = None
fig2.xaxis.minor_tick_line_color = None
fig2.yaxis.major_tick_line_color = None
fig2.yaxis.minor_tick_line_color = None
fig2.xaxis.major_label_text_color = None
fig2.yaxis.major_label_text_color = None
# Add View box to second figure
rect = Rect(x='x', y='y', width='width', height='height', fill_alpha=0.1,
line_color='black', fill_color='black')
fig2.add_glyph(source, rect)
# Add JS callBacks
self.JS_linkPlots(fig1, source)
# Plots
plots = self.plot_continuous(source, xvalues, yvalues, fig1, kind)
self.plot_continuous(source, xvalues, yvalues, fig2, kind)
s2 = ColumnDataSource(data=dict(ym=[0.5, 0.5]))
fig1.line(x=[0, 1], y='ym', color="orange",
line_width=5, alpha=0.6, source=s2)
# Add legends
legend = Legend(items=[
(ydata, plots)],
location=(0, 0),
click_policy="mute")
# Add legend to fig layout
fig1.add_layout(legend, 'below')
# Layout
layout = col(fig1, fig2)
return layout, fig1
def plot_continuous(self, source, xvalues, yvalues, fig, kind, color=0):
if kind == 'scatter':
s = fig.scatter(
xvalues, yvalues,
fill_color='white', fill_alpha=0.6,
line_color=Spectral10[color], size=8,
selection_color="firebrick",
nonselection_fill_alpha=0.2,
nonselection_fill_color="blue",
nonselection_line_color="firebrick",
nonselection_line_alpha=1.0)
return [s]
elif kind == 'line':
l = fig.line(
xvalues, yvalues, line_width=2, color=Spectral10[color], alpha=0.8,
muted_color=Spectral10[color], muted_alpha=0.2)
s = fig.scatter(
xvalues, yvalues,
fill_color="white", fill_alpha=0.6,
line_color=Spectral10[color], size=8,
selection_color="firebrick",
nonselection_fill_alpha=0.2,
nonselection_fill_color="blue",
nonselection_line_color="firebrick",
nonselection_line_alpha=1.0)
return [s, l]
else:
raise 'Wrong type of plot'
def prepare_data(self, data):
xdata = data.columns[0]
xvalues = data[xdata]
ydata = data.columns[1]
yvalues = data[ydata]
source = ColumnDataSource(data)
return source, xdata, ydata, xvalues, yvalues
First, a disclaimer: Bokeh makes no claim to function, either fully or partially, with Qt browser widgets. We are simply not equipped to be able to maintain that claim rigorously under continuous testing, therefore we cannot make it. If anyone would ever like to step in as a maintainer of that functionality, it's possible in the future that we can make stronger support claims.
Bokeh uses a third party library Hammer.js to provide uniform low-level event handling across different platforms. Bokeh expects that the events that are generated have a pageX and pageY attributes. It appears that Qt's browser widget does not satisfy this expectation, leading to the error you are seeing. It's possible that updating the version of Hammer used by Bokeh might fix the problem. It's possible that a workaround could be introduced. In any case, it would require new work on BokehJS itself.
The short answer is: this interacive legend probably just is not going to work on Qt. As a workaround, use Bokeh widgets or Qt Widgets to high and show glyphs, and do not rely on the interactive legend capability.
Longer term: Wo could look into some of the ideas suggested above. But we would need assistance to do do. We do not have the bandwidth, ability, or experience to build Qt apps ourselves to test potential fixes. If you have the ability to work together with a core dev on finding a solution, please feel free to make an issue on the issue tracker.

How do you turn off (or hide) the seconds y axis scale on a combination chart in openpyxl?

How do you turn off (or hide) the seconds y axis scale on a combination chart in openpyxl?
I can find the xml difference by comparing the before and after changes to hide the scale (I just change the excel file extension to '.zip' to access the xml):
-<c:valAx>
<c:axId val="156672520"/>
-<c:scaling>
<c:orientation val="minMax"/>
</c:scaling>
<c:delete val="0"/>
<c:axPos val="r"/>
<c:majorGridlines/>
<c:numFmt sourceLinked="1" formatCode="0.0"/>
<c:majorTickMark val="out"/>
<c:minorTickMark val="none"/>
<c:tickLblPos val="none"/> # this changes from 'nextTo'
<c:crossAx val="207247000"/>
<c:crosses val="max"/>
<c:crossBetween val="between"/>
</c:valAx>
I've tried this (last few lines are the 'tickLblPos' ):
mainchart = LineChart()
mainchart.style = 12
v2 = Reference(WorkSheetOne, min_col=1, min_row=2+CombBarLineDataOffsetFromTop, max_row=3+CombBarLineDataOffsetFromTop, max_col=13)
mainchart.add_data(v2, titles_from_data=True, from_rows=True)
mainchart.layout = Layout(
ManualLayout(
x=0.12, y=0.25, # position from the top
h=0.9, w=0.75, # this is scaling the chart into the container
xMode="edge",
yMode="edge",
)
)
mainchart.title = "Chart Title"
# Style the lines
s1 = mainchart.series[0]
#Marker type
s1.marker.symbol = "diamond" # triangle
s1.marker.size = 9
s1.marker.graphicalProperties.solidFill = "C00000" # Marker filling
s1.marker.graphicalProperties.line.solidFill = "000000" # Marker outline
s1.graphicalProperties.line.noFill = False
# Line color
s1.graphicalProperties.line.solidFill = "000000" # line color
s2 = mainchart.series[1]
s2.graphicalProperties.line.solidFill = "000000"
s2.graphicalProperties.line.dashStyle = "dash"
mainchart.dataLabels = DataLabelList()
mainchart.dataLabels.showVal = False
mainchart.dataLabels.dLblPos = 't'
mainchart.height = 15
mainchart.width = 39
#Create the Chart
chart2 = BarChart()
chart2.type = "col"
chart2.style = 10 # simple bar
chart2.y_axis.axId = 0
dataone = Reference(WorkSheetOne, min_col=2, min_row=CombBarLineDataOffsetFromTop+1, max_row=CombBarLineDataOffsetFromTop+1, max_col=13 )
doneseries = Series(dataone, title="Series Title")
chart2.append(doneseries)
cats = Reference(WorkSheetOne, min_col=2, min_row=CombBarLineDataOffsetFromTop, max_row=CombBarLineDataOffsetFromTop, max_col=13)
chart2.set_categories(cats)
# Set the series for the chart data
series3Total = chart2.series[0]
fill3Total = PatternFillProperties(prst="pct5")
fill3Total.foreground = ColorChoice(srgbClr='996633') # brown
fill3Total.background = ColorChoice(srgbClr='996633')
series3Total.graphicalProperties.pattFill = fill3Total
chart2.dataLabels = DataLabelList()
chart2.dataLabels.showVal = False
chart2.shape = 2
mainchart.y_axis.crosses = "max"
mainchart.y_axis.tickLblPos = "none" # nextTo -- this doesn't work
mainchart += chart2
WorkSheetOne.add_chart(mainchart, 'A1')
How can I translate the difference in the XML to an attribute with openpyxl?
The problem here is with some of default values for some attributes which use 3-valued logic at times so that None != "none", ie. <c:tickLblPos /> != <c:tickLblPos val="none"/> because the default is "nextTo". This plays havoc with the Python semantics (3-valued logic is always wrong) where the default is not to set an attribute if the value is None in Python. This really only affects ChartML and I've added some logic to the descriptors for the relevant objects so that "none" will be written where required.
But this code isn't publicly available yet. Get in touch with my by e-mail if you'd like a preview.

Categories

Resources