Related
I have spent this week trying to figure out how to design an editable datatable via Flask without much success. I found the plugin bootstable, and I had my table ready to go, with the data populating the cells perfectly. But I could not pip install jquery (required) and its dependencies. This appears to be a common problem, evidenced by the posts I saw online.
So I circled back to the editable Dash/plotly datatables.
My problem here is that I cannot figure out how to populate the cells with the data I need to use. I pasted the code from one the Dash editable templates below as an example, as well as my best attempt to make it work. Changing the columns is easy. And the code works fine with the dummy data, but I cannot figure out how to make my data (currently in JSON form ) compatible with this template. I have poured over the Dash/Plotly website and I don't see much direction on this topic.
How would arrange my data so that it can be used by data=[ ] in the code below?
My data is a list of Python dictionaries currently in JSON form, and sent as a simple list via Flask to index.html. I know it transfers because it works in other unsuccessful attempts. I just can't install the dependencies, as noted above. I could easily transform it into a CSV file if necessary. Does anyone know how to resolve this issue?
Any help at this point would be greatly appreciated.
Here is the template online, found at this address.
import dash
from dash.dependencies import Input, Output
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
app = dash.Dash(__name__)
params = [
'Weight', 'Torque', 'Width', 'Height',
'Efficiency', 'Power', 'Displacement'
]
app.layout = html.Div([
dash_table.DataTable(
id='table-editing-simple',
columns=(
[{'id': 'Model', 'name': 'Model'}] +
[{'id': p, 'name': p} for p in params]
),
data=[
dict(Model=i, **{param: 0 for param in params})
for i in range(1, 5)
],
editable=True
),
dcc.Graph(id='table-editing-simple-output')
])
#app.callback(
Output('table-editing-simple-output', 'figure'),
Input('table-editing-simple', 'data'),
Input('table-editing-simple', 'columns'))
def display_output(rows, columns):
df = pd.DataFrame(rows, columns=[c['name'] for c in columns])
return {
'data': [{
'type': 'parcoords',
'dimensions': [{
'label': col['name'],
'values': df[col['id']]
} for col in columns]
}]
}
if __name__ == '__main__':
app.run_server(debug=True)
Here is my attempt to make it work, though I receive an "Error loading layout" message, where user_edits is the list of dictionaries and each_dict is each dictionary in the list.
Thank you for any sincere feedback.
app = dash.Dash(__name__)
with open("./json/flask_dict.json", "r") as flask_dicts:
user_edits = json.load(flask_dicts)
app.layout = html.Div([
dash_table.DataTable(
id='table-editing-simple',
columns=(
[{'id': 'Index', 'name': 'Index'}] +
[{'id': 'Amounts', 'name': 'Amounts'}] +
[{'id': 'Modifiers', 'name': 'Modifiers'}] +
[{'id': 'Units', 'name': 'Units'}] +
[{'id': 'Ings', 'name': 'Ings'}]
),
data=[{v for k,v in each_dict.items()} for each_dict in user_edits],
editable=True
),
dcc.Graph(id='table-editing-simple-output')
])
In case it helps, here is the list of dictionaries:
[
{'index': 0, 'amount': '.5', 'mod': 'None', 'units': 'teaspoon', 'ing': 'dried oregano'},
{'index': 1, 'amount': '0.25', 'mod': 'None', 'units': 'tsp', 'ing': 'red chilli flakes'},
{'index': 2, 'amount': '0.25', 'mod': 'None', 'units': 'tsp', 'ing': 'ground cloves'},
{'index': 3, 'amount': '1', 'mod': 'None', 'units': 'tbsp', 'ing': 'sunflower oil'},
{'index': 4, 'amount': '1', 'mod': 'None', 'units': 'tsp', 'ing': 'mustard seeds'},
{'index': 5, 'amount': '1', 'mod': 'divided', 'units': 'tsp', 'ing': 'salt'},
{'index': 6, 'amount': '1.33', 'mod': 'None', 'units': 'tsp', 'ing': 'cumin'},
{'index': 7, 'amount': '1.5', 'mod': 'None', 'units': 'teaspoon', 'ing': 'dried thyme'},
{'index': 8, 'amount': '10', 'mod': 'None', 'units': 'teaspoon', 'ing': 'cardamom pods'},
{'index': 9, 'amount': '3', 'mod': 'None', 'units': 'cm', 'ing': 'ginger'},
{'index': 10, 'amount': '3', 'mod': 'medium', 'units': 'cm', 'ing': 'shallots'},
{'index': 11, 'amount': '300', 'mod': 'None', 'units': 'grams', 'ing': 'red lentils'},
{'index': 12, 'amount': '4', 'mod': 'minced', 'units': 'grams', 'ing': 'cloves of garlic'},
{'index': 13, 'amount': '400', 'mod': 'None', 'units': 'grams', 'ing': 'diced tomatoes'},
{'index': 14, 'amount': '80', 'mod': 'None', 'units': 'grams', 'ing': 'baby spinach'},
{'index': 15, 'amount': '1', 'mod': 'None', 'units': 'handful', 'ing': 'cilantro'},
{'index': 16, 'amount': '1', 'mod': 'Half', 'units': 'handful', 'ing': 'lemon'}
]
With some assistance from the Dash/Plotly community, I was finally able to figure this one out.
My data below was already structured in the right way, so I did that much right. I only needed to set the data equal to my list of dictionaries.
My mistake was thinking I needed to iterate through it.
Here is the input and code if you wish to try it:
1) List of dictionaries that needed to be placed inside an editable table:
recipe_ents_list = [{'Index': 0, 'Amounts': '.5', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'dried oregano'},
{'Index': 1, 'Amounts': '0.25','Modifiers': 'None', 'Units': 'tsp', 'Ings': 'red chilli flakes'},
{'Index': 2, 'Amounts': '0.25', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'ground cloves'},
{'Index': 3, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'tbsp', 'Ings': 'sunflower oil'},
{'Index': 4, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'mustard seeds'},
{'Index': 5, 'Amounts': '1', 'Modifiers': 'divided', 'Units': 'tsp', 'Ings': 'salt'},
{'Index': 6, 'Amounts': '1.33', 'Modifiers': 'None', 'Units': 'tsp', 'Ings': 'cumin'},
{'Index': 7, 'Amounts': '1.5', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'dried thyme'},
{'Index': 8, 'Amounts': '10', 'Modifiers': 'None', 'Units': 'teaspoon', 'Ings': 'cardamom pods'},
{'Index': 9, 'Amounts': '3', 'Modifiers': 'None', 'Units': 'cm', 'Ings': 'ginger'},
{'Index': 10, 'Amounts': '3', 'Modifiers': 'medium', 'Units': 'cm', 'Ings': 'shallots'},
{'Index': 11, 'Amounts': '300', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'red lentils'},
{'Index': 12, 'Amounts': '4', 'Modifiers': 'minced', 'Units': 'grams', 'Ings': 'cloves of garlic'},
{'Index': 13, 'Amounts':'400', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'diced tomatoes'},
{'Index': 14, 'Amounts': '80', 'Modifiers': 'None', 'Units': 'grams', 'Ings': 'baby spinach'},
{'Index': 15, 'Amounts': '1', 'Modifiers': 'None', 'Units': 'handful','Ings': 'cilantro'},
{'Index': 16, 'Amounts': '1', 'Modifiers': 'Half', 'Units': 'handful', 'Ings': 'lemon'}]
2) Here is the final, working code. You can replicate the plot with the dictionaries above and the code below. It desperately need formatting, but it is accurate and editable.
from dash import Dash, dcc, html, dash_table
app = Dash(__name__)
app.layout = html.Div([
dash_table.DataTable(
id='table-editing-simple',
columns=[{'id': i, 'name':i} for i in ['Index', 'Amounts', 'Modifiers', 'Units', 'Ings']],
data=recipe_ents_list,
editable=True
),
dcc.Graph(id='table-editing-simple-output')
])
if __name__ == "__main__":
app.run_server(debug=True)
The data parameter of DataTable is expecting the format to be a list of dictionaries, that's true. But the dictionary format is expected to be like: {'columnName':'value'}. Your list comprehension is removing the key (column name) and only including the value, and is returned as a set. What you've made is a list of sets.
Try setting data = user_edits. It appears the format is already correct and no comprehensions are required.
Edit:
On second glance, you will need to update your columns object to match the same spelling and case as the keys in your data object. E.g "Amounts" vs "amount" -- these need to match.
I want to change the bar color of the state: AZ, CA, FL, NY, OH, and OK. I did it by counting the index; however, I am wondering if I can change the color according to the names of the x ticks.
import matplotlib.pylab as plt
fig=plt.figure(figsize=(10,8), dpi= 90)
lists = sorted(frequency_state.items())
x, y = zip(*lists)
bars = plt.bar(x, y, color = 'grey')
plt.grid()
plt.xticks(rotation = 90)
for i in [2,3,5,23,24,25,31]:
bars[i].set_color('r')
plt.show()
{'FL': 45,
'OK': 37,
'OH': 33,
'NY': 28,
'TX': 27,
'CA': 25,
'AZ': 17,
'GA': 10,
'KY': 9,
'MN': 8,
'MA': 8,
'LA': 8,
'PA': 7,
'ID': 7,
'NJ': 6,
'VA': 6,
'IN': 6,
'MT': 6,
'TN': 5,
'CT': 5,
'NC': 5,
'WI': 5,
'MD': 4,
'IL': 4,
'UT': 3,
'IA': 3,
'MI': 3,
'AR': 2,
'MO': 2,
'SC': 2,
'AL': 2,
'NV': 2,
'OR': 1,
'SD': 1,
'ND': 1}
Here is the graph:
Normalize the value in the colormap you want to display and set it to the desired color of the bar chart.
import matplotlib.pylab as plt
import matplotlib.colors as mcolors
frequency_state = {'FL': 45, 'OK': 37, 'OH': 33, 'NY': 28, 'TX': 27, 'CA': 25, 'AZ': 17, 'GA': 10, 'KY': 9, 'MN': 8,
'MA': 8, 'LA': 8, 'PA': 7, 'ID': 7, 'NJ': 6, 'VA': 6, 'IN': 6, 'MT': 6, 'TN': 5, 'CT': 5, 'NC': 5, 'WI': 5,
'MD': 4, 'IL': 4, 'UT': 3, 'IA': 3, 'MI': 3, 'AR': 2, 'MO': 2, 'SC': 2, 'AL': 2, 'NV': 2, 'OR': 1, 'SD': 1, 'ND': 1}
fig=plt.figure(figsize=(10,8), dpi= 90)
ax = plt.subplot()
colormap = plt.cm.Blues
normalize = mcolors.Normalize(vmin=min(frequency_state.values()), vmax=max(frequency_state.values()))
lists = sorted(frequency_state.items())
x, y = zip(*lists)
bars = plt.bar(x, y, color='grey')
plt.grid()
plt.xticks(rotation = 90)
for i in [2,3,5,23,24,25,31]:
bars[i].set_color(colormap(normalize(lists[i][1])))
plt.show()
In the docs for plotly.py tick formatting here, it states that you can set the tickmode to array and just specify the tickvals and ticktext e.g.
import plotly.graph_objects as go
go.Figure(go.Scatter(
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
y = [28.8, 28.5, 37, 56.8, 69.7, 79.7, 78.5, 77.8, 74.1, 62.6, 45.3, 39.9]
))
fig.update_layout(
xaxis = dict(
tickmode = 'array',
tickvals = [1, 3, 5, 7, 9, 11],
ticktext = ['One', 'Three', 'Five', 'Seven', 'Nine', 'Eleven']
)
)
fig.show()
But this does not seem to work when tickvals is a list of datetime objects.
What I want to do is show an x-axis tick for each point in my scatter plot where the x values are all datetime objects but this does not seem to work. No error is thrown and the graph is rendered as if I did not try update the x ticks. My code for this is below:
# lambda expression to convert datetime object to string of desired format
date_to_string_lambda = lambda x: x.strftime("%e %b")
fig.update_layout(
xaxis = dict(
tickmode = 'array',
# all points should have a corresponding tick
tickvals = list(fig.data[0].x),
# datetime value represented as %e %b string i.e. space padded day and abreviated month.
ticktext = list(map(date_to_string_lambda, list(fig.data[0].x))),
)
)
Instead of showing a tick for each value it goes to the default tick mode and shows ticks at intervals i.e.
Image of graph produced
The values for layout when print(fig) is run after the above code are below, where the xaxis dict is important. Note that the tickvals are no longer of type datetime.
'layout': {'hovermode': 'x',
'legend': {'title': {'text': ''}, 'tracegroupgap': 0, 'x': 0.01, 'y': 0.98},
'margin': {'b': 0, 'l': 0, 'r': 0, 't': 0},
'template': '...',
'title': {'text': ''},
'xaxis': {'anchor': 'y',
'domain': [0.0, 1.0],
'fixedrange': True,
'tickmode': 'array',
'ticktext': [27 Apr, 3 May, 9 May, 13 May, 20 May],
'tickvals': [2020-04-27 00:00:00, 2020-05-03 00:00:00,
2020-05-09 00:00:00, 2020-05-13 00:00:00,
2020-05-20 00:00:00],
'title': {'text': 'Date'}},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'fixedrange': True, 'title': {'text': 'Total Tests'}}}
This seems to be a bug with plotly.py, so is there a workaround for this?
The data is a time series, with many member ids associated with many categories:
data_df = pd.DataFrame({'Date': ['2018-09-14 00:00:22',
'2018-09-14 00:01:46',
'2018-09-14 00:01:56',
'2018-09-14 00:01:57',
'2018-09-14 00:01:58',
'2018-09-14 00:02:05'],
'category': [1, 1, 1, 2, 2, 2],
'member': ['bob', 'joe', 'jim', 'sally', 'jane', 'doe'],
'data': ['23', '20', '20', '11', '16', '62']})
There are about 50 categories with 30 members, each with around 1000 datapoints.
I am trying to make one plot per category.
By subsetting each category then plotting via:
fig, ax = plt.subplots(figsize=(8,6))
for i, g in category.groupby(['memeber']):
g.plot(y='data', ax=ax, label=str(i))
plt.show()
This works fine for a single category, however, when i try to use a for loop to repeat this for each category, it does not work
tests = pd.DataFrame()
for category in categories:
tests = df.loc[df['category'] == category]
for test in tests:
fig, ax = plt.subplots(figsize=(8,6))
for i, g in category.groupby(['member']):
g.plot(y='data', ax=ax, label=str(i))
plt.show()
yields an "AttributeError: 'str' object has no attribute 'groupby'" error.
What i would like is a loop that spits out one graph per category, with all the members' data plotted on each graph
Creating your dataframe
import pandas as pd
data_df = pd.DataFrame({'Date': ['2018-09-14 00:00:22',
'2018-09-14 00:01:46',
'2018-09-14 00:01:56',
'2018-09-14 00:01:57',
'2018-09-14 00:01:58',
'2018-09-14 00:02:05'],
'category': [1, 1, 1, 2, 2, 2],
'member': ['bob', 'joe', 'jim', 'sally', 'jane', 'doe'],
'data': ['23', '20', '20', '11', '16', '62']})
then [EDIT after comments]
import matplotlib.pyplot as plt
import numpy as np
subplots_n = np.unique(data_df['category']).size
subplots_x = np.round(np.sqrt(subplots_n)).astype(int)
subplots_y = np.ceil(np.sqrt(subplots_n)).astype(int)
for i, category in enumerate(data_df.groupby('category')):
category_df = pd.DataFrame(category[1])
x = [str(x) for x in category_df['member']]
y = [float(x) for x in category_df['data']]
plt.subplot(subplots_x, subplots_y, i+1)
plt.plot(x, y)
plt.title("Category {}".format(category_df['category'].values[0]))
plt.tight_layout()
plt.show()
yields to
Please note that this nicely takes care also of bigger groups like
data_df2 = pd.DataFrame({'category': [1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 5],
'member': ['bob', 'joe', 'jim', 'sally', 'jane', 'doe', 'ric', 'mat', 'pip', 'zoe', 'qui', 'quo', 'qua'],
'data': ['23', '20', '20', '11', '16', '62', '34', '27', '12', '7', '9', '13', '7']})
Far from an expert with pandas, but if you execute the following simple enough snippet
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({'Date': ['2018-09-14 00:00:22',
'2018-09-14 00:01:46',
'2018-09-14 00:01:56',
'2018-09-14 00:01:57',
'2018-09-14 00:01:58',
'2018-09-14 00:02:05'],
'category': [1, 1, 1, 2, 2, 2],
'Id': ['bob', 'joe', 'jim', 'sally', 'jane', 'doe'],
'data': ['23', '20', '20', '11', '16', '62']})
fig, ax = plt.subplots()
for item in df.groupby('category'):
ax.plot([float(x) for x in item[1]['category']],
[float(x) for x in item[1]['data'].values],
linestyle='none', marker='D')
plt.show()
you produce this figure
But there is probably a better way.
EDIT: Based on the changes made to your question, I changed my snippet to
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
df = pd.DataFrame({'Date': ['2018-09-14 00:00:22',
'2018-09-14 00:01:46',
'2018-09-14 00:01:56',
'2018-09-14 00:01:57',
'2018-09-14 00:01:58',
'2018-09-14 00:02:05'],
'category': [1, 1, 1, 2, 2, 2],
'Id': ['bob', 'joe', 'jim', 'sally', 'jane', 'doe'],
'data': ['23', '20', '20', '11', '16', '62']})
fig, ax = plt.subplots(nrows=np.unique(df['category']).size)
for i, item in enumerate(df.groupby('category')):
ax[i].plot([str(x) for x in item[1]['Id']],
[float(x) for x in item[1]['data'].values],
linestyle='none', marker='D')
ax[i].set_title('Category {}'.format(item[1]['category'].values[0]))
fig.tight_layout()
plt.show()
which now displays
I am using plotly and python. The goal is to have a chart like this, where each bar represents the distribution of a particular series using different percentiles.
This is sample data ( this data has nothing to do with the above graph. )
Percentile 0.05 0.25 0.50 0.75 0.95
Timestep
2017M06 0.009531 0.009531 0.009531 0.009531 0.009531
2017M07 -0.098059 -0.096431 0.007171 0.076327 0.143442
2017M08 -0.040074 -0.035968 0.007923 0.043307 0.088125
2017M09 -0.042111 -0.025118 0.043147 0.056743 0.073169
2017M10 -0.044251 -0.014754 0.003749 0.060594 0.136222
2017M11 -0.097137 -0.068795 -0.068711 0.008935 0.048629
2017M12 -0.050702 -0.029305 -0.016308 0.002923 0.111673
2018M01 -0.013028 0.004550 0.010547 0.025975 0.045995
2018M02 -0.057814 -0.044448 -0.044320 -0.041016 0.051825
2018M03 0.009961 0.023378 0.058440 0.124610 0.152549
2018M04 -0.034927 0.025411 0.075795 0.095152 0.117307
2018M05 -0.023744 -0.005568 0.077677 0.137097 0.144172
2018M06 -0.048046 -0.007990 -0.006329 0.015632 0.019013
Here is the attempt to create something similar in Plotly, using "overlay" barchart mode.
which creates a graph likes this.
As you can see, the colors are all messed up. Some numbers don't even show up since the smaller number is placed under the larger one.
How can I fix this ? What I need is at each bar, for positive values, the bars are plotted in descending order, from largest to smallest. For negative values, the bars need to be plotted in ascending order, from smallest to largest.
As a side note, I am aware that there are four kinds of plotly bar charts: stack, overlay, group and relative. I tested all those and overlay is the one that I want since I need stacked bar, where underlying data are both positive and negative.
Here is the code that produces the above graph.
iplot(
{'data': [{
'name': '0.05',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['2017M06', '2017M07', '2017M08', '2017M09', '2017M10', '2017M11',
'2017M12', '2018M01', '2018M02', '2018M03', '2018M04', '2018M05',
'2018M06'], dtype=object),
'y': array([ 0.009531 , -0.098059 , -0.0400744, -0.0421108, -0.0442508,
-0.0971366, -0.0507018, -0.0130276, -0.0578136, 0.0099612,
-0.0349274, -0.023744 , -0.048046 ])},
{
'name': '0.25',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['2017M06', '2017M07', '2017M08', '2017M09', '2017M10', '2017M11',
'2017M12', '2018M01', '2018M02', '2018M03', '2018M04', '2018M05',
'2018M06'], dtype=object),
'y': array([ 0.009531, -0.096431, -0.035968, -0.025118, -0.014754, -0.068795,
-0.029305, 0.00455 , -0.044448, 0.023378, 0.025411, -0.005568,
-0.00799 ])},
{
'name': '0.5',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['2017M06', '2017M07', '2017M08', '2017M09', '2017M10', '2017M11',
'2017M12', '2018M01', '2018M02', '2018M03', '2018M04', '2018M05',
'2018M06'], dtype=object),
'y': array([ 0.009531, 0.007171, 0.007923, 0.043147, 0.003749, -0.068711,
-0.016308, 0.010547, -0.04432 , 0.05844 , 0.075795, 0.077677,
-0.006329])},
{
'name': '0.75',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['2017M06', '2017M07', '2017M08', '2017M09', '2017M10', '2017M11',
'2017M12', '2018M01', '2018M02', '2018M03', '2018M04', '2018M05',
'2018M06'], dtype=object),
'y': array([ 0.009531, 0.076327, 0.043307, 0.056743, 0.060594, 0.008935,
0.002923, 0.025975, -0.041016, 0.12461 , 0.095152, 0.137097,
0.015632])},
{
'name': '0.95',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['2017M06', '2017M07', '2017M08', '2017M09', '2017M10', '2017M11',
'2017M12', '2018M01', '2018M02', '2018M03', '2018M04', '2018M05',
'2018M06'], dtype=object),
'y': array([ 0.009531 , 0.1434422, 0.0881254, 0.0731686, 0.136222 ,
0.0486294, 0.1116726, 0.045995 , 0.0518248, 0.1525492,
0.1173072, 0.1441722, 0.0190128])}],
'layout': {'barmode': 'overlay'}}
)
Let's say the data we are interested in is
1 2 3
4 5 6
7 8 9
I want to plot from first row to second row and second row to third row. The following Python code would achieve that.
iplot(
{'data': [{'base': array([ 1,2,3]),
'marker': {'color': 'rgba(0, 97, 37, 1.0)',
'line': {'color': 'rgba(0, 97, 37, 1.0)', 'width': 1}},
'name': 'first',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['dog', 'cat', 'monkey'], dtype=object),
'y': array([ 3,3,3,])},
{'base': array([4,5,6]),
'marker': {'color': 'rgba(138, 33, 14, 1.0)',
'line': {'color': 'rgba(138, 33, 14, 1.0)', 'width': 1}},
'name': 'second',
'orientation': 'v',
'text': '',
'type': 'bar',
'x': array(['dog', 'cat', 'monkey'], dtype=object),
'y': array([ 3,3,3])}],
'layout': {'barmode': 'overlay',
'legend': {'bgcolor': '#F5F6F9', 'font': {'color': '#4D5663'}},
'paper_bgcolor': '#F5F6F9',
'plot_bgcolor': '#F5F6F9',
'title': 'main title',
'titlefont': {'color': '#4D5663'},
'xaxis1': {'gridcolor': '#E1E5ED',
'showgrid': True,
'tickfont': {'color': '#4D5663'},
'title': '',
'titlefont': {'color': '#4D5663'},
'zerolinecolor': '#E1E5ED'},
'yaxis1': {'gridcolor': '#E1E5ED',
'showgrid': True,
'tickfont': {'color': '#4D5663'},
'title': '',
'titlefont': {'color': '#4D5663'},
'zerolinecolor': '#E1E5ED'}}}
)
This is the result.
The trick is to write the lower bound as base and the difference as y values. This also takes care of the fact that having both positive and negative values screw things up, since the user has complete control over where each color starts and ends.