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.
Related
I am working on a plotter for Finite Element Method solutions. I decided to use the Plotly library because of the carpet plots. I have my data to plot and this is my result:
Flow over NACA0012
Each element is represented as a Carpet, and for each carpet the solution is shown as a Countourcarpet. Everything is in place, but the rendering is too slow and the interactive interface is therefore nearly useless. Is there a way to enhance the performance of the rendering? I have read about different renderers in Plotly, but the plot just does not open. Is there a a way to speed up the rendering? Surely I will have to manage larger dataset. In this example I am using 740 carpets.
These are the Contourcarpet settings:
fig.add_trace(go.Contourcarpet(
a = a,
b = b,
z = u, # Sution correspondent at (a,b) parametric location
showlegend = showLegendFlag,
name = "Density",
legendgroup = "Density",
autocolorscale = False,
colorscale = "Inferno",
autocontour = False,
carpet = str(e), # The carpet on which to plot the solution is
# referenced as a string number
contours = dict(
start = start1, # Min value
end = end1, # Max value
size = abs(end1-start1) / countour_number, # Plot colour discretization
showlines = False
),
line = dict(
smoothing = 0
),
colorbar = dict(
len = 0.4,
y = 0.25
)
))
And these are the layout settings:
fig.update_layout(
plot_bgcolor="#FFF",
yaxis = dict(
zeroline = False,
range = [-1.800,1.800],
showgrid = False
),
dragmode = "pan",
height = 700,
xaxis = dict(
zeroline = False,
scaleratio = 1,
scaleanchor = 'y',
range = [-3.800,3.800],
showgrid = False
),
title = "Flow over NACA 0012",
hovermode = "closest",
margin = dict(
r = 80,
b = 40,
l = 40,
t = 80
),
width = 900
)
fig.show()
I new in using bokeh.
This is what I am doing. From osmnx I get data of schools and hospitals in Haiti.
Without writing all the code I arrive to get the following
data1=dict(
x=list(schools['x'].values),
y=list(schools['y'].values)
)
data2=dict(
x=list(hospitals['x'].values),
y=list(hospitals['y'].values)
)
building = 'Schools'
buildings = {
'Schools': {
'title': 'Schools',
'data': data1,
'color': 'black'
},
'Hospitals': {
'title': 'Hospitals',
'data': data2,
'color': 'red'
}
}
building_select = Select(value=building, title='Building', options=sorted(buildings.keys()))
I would like to change the visualisation between schools and hospitals by selecting it. I define the function that change the data to take and the color.
def returnInfo(building):
dataPoints = buildings[building]['data']
color = buildings[building]['color']
return dataPoints, color
dataPoints, color = returnInfo(building)
I define the function make_plot
def make_plot(dataPoints, title, color):
TOOLS = "pan, wheel_zoom, reset,save"
p = figure(plot_width=800,
tools=TOOLS,
x_axis_location=None,
y_axis_location=None)
# Add points on top (as black points)
buildings = p.circle('x', 'y', size=4, source=data1, color=color)
hover_buildings = HoverTool(renderers = [buildings], point_policy="follow_mouse", tooltips = [("(Long, Lat)", "($x, $y)")])
p.add_tools(hover_buildings)
return p
plot = make_plot(dataPoints, "Data for " + buildings[building]['title'], color)
then I update
def update_plot(attrname, old, new):
building = building_select.value
p.title.text = "Data for " + buildings[building]['title']
src = buildings[building]['data']
dataPoints, color = returnInfo(building)
dataPoints.update
building_select.on_change('value', update_plot)
controls = column(building_select)
curdoc().add_root(row(plot, controls))
but it does not work: i.e. I am not able to change the points from schools to hospitals even if I have the cursor. Where is the error in the update section?
As first solution I suggest to use legend.click_plolicy = 'hide' to toggle visibility of your buildings on the map (Bokeh v1.1.0)
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.tile_providers import CARTODBPOSITRON_RETINA
import osmnx as ox
amenities = ['hospital', 'school']
for i, amenity in enumerate(amenities):
buildings = ox.pois_from_address("Port-au-Prince, Haiti", amenities = [amenity], distance = 3500)[['geometry', 'name', 'element_type']]
for item in ['way', 'relation']:
buildings.loc[buildings.element_type == item, 'geometry'] = buildings[buildings.element_type == item]['geometry'].map(lambda x: x.centroid)
buildings.name.fillna('Hospital' if i == 0 else 'School', inplace = True)
amenities[i] = buildings.to_crs(epsg = 3857)
p = figure(title = "Port-au-Prince, Haiti", tools = "pan,wheel_zoom,hover,reset", x_range = (-8057000, -8048500), y_range = (2098000, 2106000),
tooltips = [('Name', '#name'), ("(Long, Lat)", "($x, $y)")], x_axis_location = None, y_axis_location = None, active_scroll = 'wheel_zoom')
p.add_tile(CARTODBPOSITRON_RETINA)
p.grid.grid_line_color = None
for i, b in enumerate(amenities):
source = ColumnDataSource(data = dict(x = b.geometry.x, y = b.geometry.y, name = b.name.values))
p.circle('x', 'y', color = 'red' if i == 0 else 'blue', source = source, legend = 'Hospital' if i == 0 else 'School')
p.legend.click_policy = 'hide'
show(p)
And if you want the Select widget then here is another alternative (Bokeh v1.1.0):
from bokeh.models import ColumnDataSource, Column, Select, CustomJS
from bokeh.plotting import figure, show
from bokeh.tile_providers import CARTODBPOSITRON_RETINA
import osmnx as ox
amenities = ['hospital', 'school']
for i, amenity in enumerate(amenities):
buildings = ox.pois_from_address("Port-au-Prince, Haiti", amenities = [amenity], distance = 3500)[['geometry', 'name', 'element_type']]
for item in ['way', 'relation']:
buildings.loc[buildings.element_type == item, 'geometry'] = buildings[buildings.element_type == item]['geometry'].map(lambda x: x.centroid)
buildings.name.fillna('Hospital' if i == 0 else 'School', inplace = True)
buildings = buildings.to_crs(epsg = 3857)
amenities[i] = dict(x = list(buildings.geometry.x), y = list(buildings.geometry.y), name = list(buildings.name.values), color = (['red'] if i == 0 else ['blue']) * len(buildings.name.values))
source = ColumnDataSource(amenities[0])
p = figure(title = "Hospitals", tools = "pan,wheel_zoom,hover,reset", x_range = (-8057000, -8048500), y_range = (2098000, 2106000),
tooltips = [('Name', '#name'), ("(Long, Lat)", "($x, $y)")], x_axis_location = None, y_axis_location = None, active_scroll = 'wheel_zoom')
p.add_tile(CARTODBPOSITRON_RETINA)
p.circle(x = 'x', y = 'y', color = 'color', source = source)
p.grid.grid_line_color = None
code = ''' source.data = (cb_obj.value == 'Hospitals' ? data[0] : data[1]); p.title.text = cb_obj.value; '''
select = Select(options = ['Hospitals', 'Schools'], callback = CustomJS(args=dict(p = p, source = source, data = amenities), code = code))
show(Column(p, select))
Let me know if you need any explanation on this code.
Below are the changes required to make your code work:
In your make_plot method, since you want to update the title of the plot on selection change, replace
p = figure(plot_width=800,
tools=TOOLS,
x_axis_location=None,
y_axis_location=None)
with
p = figure(plot_width=800,
tools=TOOLS,
title=title,
x_axis_location=None,
y_axis_location=None)
Also, since you want to update the data and color of the buildings, return the buildings too in the method, so that the complete method now looks like:
def make_plot(dataPoints, title, color):
TOOLS = "pan, wheel_zoom, reset,save"
p = figure(plot_width=800,
tools=TOOLS,
title=title,
x_axis_location=None,
y_axis_location=None)
# Add points on top (as black points)
buildings = p.circle('x', 'y', size=4, source=data1, color=color)
hover_buildings = HoverTool(renderers = [buildings], point_policy="follow_mouse", tooltips = [("(Long, Lat)", "($x, $y)")])
p.add_tools(hover_buildings)
return p, buildings
Next, instead of the call to
plot = make_plot(dataPoints, "Data for " + buildings[building]['title'], color)
you need to get the returned buildings also in a variable so that it can be directly updated. So now your call will look like
plot, b = make_plot(dataPoints, "Data for " + buildings[building]['title'], color)
Finally, change your update_plot method, so that it looks like this:
def update_plot(attrname, old, new):
building = building_select.value
plot.title.text = "Data for " + buildings[building]['title']
src = buildings[building]['data']
dataPoints, color = returnInfo(building)
b.data_source.data = dataPoints
b.glyph.fill_color = color
With these changes, it would work as expected. See the results attached.
Sample data used is:
data1=dict(
x=[1,2,3],
y=[2,1,3]
)
data2=dict(
x=[1,2,3],
y=[1,3,2]
)
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
from bokeh.layouts import column, row, WidgetBox
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
output_notebook()
def histogram_tab(webs):
def make_dataset(params_list, range_start = 0.0, range_end = 1, bin_width = 0.005):
#check to make sure the start is less than the end
assert range_start < range_end, "Start must be less than end!"
#by_params = pd.DataFrame(columns=[ ,'Max', 'Avarage', 'Min','color'])
by_params = pd.DataFrame(columns=[ 'left','right', 'proportion', 'p_proportion','p_interval', 'name', 'w_name','color'])
#
range_extent = range_end - range_start
values = ['Min', "Avarage", 'Max']
# Iterate through all the parameters
for i, para_name in enumerate(params_list):
#print para_name
# Subset to the parameter
subset = webs[para_name]
# note: subset have to be a list of values
# [webs.columns[i%6]]
# Create a histogram with specified bins and range
arr_hist, edges = np.histogram(subset,
bins = int(range_extent / bin_width),
range = [range_start, range_end])
# Divide the counts by the total to get a proportion and create df
arr_df= pd.DataFrame({'proportion': arr_hist ,
'left': edges[:-1], 'right': edges[1:]}) #/ np.sum(arr_hist)
# Format the proportion
arr_df['p_proportion'] = ['%0.00005f' % proportion for proportion in arr_df['proportion']]
# Format the interval
arr_df['p_interval'] = ['%d to %d scale' % (left, right) for left,
right in zip(arr_df['left'], arr_df['right'])]
# Assign the parameter for labels
arr_df['name'] = para_name
arr_df['w_name'] = webs['Site name']
# Color each parametr differently
arr_df['color'] = Category20_16[i]
# Add to the overall dataframe
by_params = by_params.append(arr_df)
# Overall dataframe
by_params = by_params.sort_values(['name','left'])
return ColumnDataSource(by_params)
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size ='20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
# Blank plot with correct labels
p = figure(plot_width = 700, plot_height = 700,
title = "Histogram of Parametes for the websites",
x_axis_label = 'parameters', y_axis_label = "values")
# Quad glyphs to create a histogram
p.quad(source=src, bottom =0,left = 'left', right = 'right', color ='color', top= 'proportion',fill_alpha = 0.7, hover_fill_color = 'color', legend = 'name',
hover_fill_alpha = 1.0, line_color = 'white') #top='proportion',
# Hover tool with vline mode
hover = HoverTool(tooltips=[('Parameter','#name'),
('Website','#w_name'),
('Proportion','p_proportion')
],
mode='vline')
p.add_tools(hover)
# Stypling
p = style(p)
return p
# Update function takes three default parameters
def update(attr, old, new):
# Get the list of parameter for the graph
parameter_to_plot = [para_selection.labels[i] for i in para_selection.active]
# Make a new dataset based on the selected parameter and the
# make_dataset function defined earlier
new_src = make_dataset(parameter_to_plot, range_start = 0, range_end = 1, bin_width = 0.005) # note range are not specified
# Convert dataframe to column data source
new_src = ColumnDataSource(new_src)
# Update the source used the quad glpyhs
src.data.update(new_src.data)
list_of_params = list(webs.columns[1:].unique())
list_of_params.sort()
para_selection = CheckboxGroup(labels=list_of_params, active = [0,1])
para_selection.on_change('active',update)
binwidth_select = Slider(start =0, end = 1,
step = 0.00025, value = 0.0005,
title = 'Change in parameter')
binwidth_select.on_change('value', update)
range_select = RangeSlider(start=0, end=1, value =(0,1),
step=0.00025, title = 'Change in range')
range_select.on_change('value', update)
initial_params = [para_selection.labels[i] for i in para_selection.active]
src = make_dataset(initial_params,
range_start = range_select.value[0],
range_end = range_select.value[1],
bin_width = binwidth_select.value)
p = make_plot(src)
#show(p)
# Put controls in a single element
controls = WidgetBox(para_selection, binwidth_select, range_select)
# Create a row layout
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child = layout, title = 'Histogram')
#return tab
tabs = Tabs(tabs=[tab])
webs.add_root(tabs)
# Set up an application
handler = FunctionHandler(histogram_tab(webs))
app = Application(handler)
add_root is a method on Document, you are trying to call it on a DataFrame called webs, apparently, which is why you get that message. The structure of a Bokeh app in a notebook should look like this:
# create a function to define the app, must accept "doc" as the parameter
def myfunc(doc):
# make Bokeh objects
# add stuff to doc
doc.add_root(stuff)
# pass the function, but *don't* execute it
handler = FunctionHandler(myfunc)
app = Application(handler)
Note that the last two lines are not necessary in recent version of Bokeh, you can just call:
show(myfunc)
directly. There is a full example in the repo:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
Your code should be structured very similarly to that.
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.
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.