-
-
Notifications
You must be signed in to change notification settings - Fork 328
-
Hi,
I have created a plotly chart on my web page with some filters that filter the data depending on what a user selects.
Basic calculations are filtering successfully however the plotly graph remains static. Below is a snippet of my code:
def create_fig(data): fig = px.pie( chart_data, values="Count", names="Status", hole=0.8, color="Status", color_discrete_map={"Success": "#D7BB0B", "Failed": "#F2F2F2"}, height=70, ) fig.update_layout(plot_bgcolor="#10253F", paper_bgcolor="#10253F", showlegend=False) fig.update_traces(textinfo="none", hoverinfo="none", hovertemplate=None) fig.update_layout(margin_l=0) fig.update_layout(margin_r=0) fig.update_layout(margin_b=0) fig.update_layout(margin_t=0) fig.write_html("fig.html", include_plotlyjs="cdn") fig_html = open("fig.html", "r", encoding="utf-8").read() return html.div(utils.html_to_vdom(fig_html)) @component def dashboard(): return interactions_layout() @component def interactions_layout(): data_filter, set_data_filter = hooks.use_state("A") plotly_data, set_plotly_data = hooks.use_state(read_data(data_filter)) def handle_event(event): set_data_filter(event["target"]["value"]) set_plotly_data(read_data(data_filter)) layout = html.div( html.select( {"value": data_filter, "on_change": handle_event}, html.option({"value": "A"}, "A"), html.option({"value": "C"}, "C"), html.option({"value": "S"}, "S"), ), html.div({"style": {}}, create_fig(plotly_data)), ) return layout
Beta Was this translation helpful? Give feedback.
All reactions
This is the full reproducible sample code. See some comments in create_fig().
It is better if the plotly html will work as the users can interact on the rendered plot. Currently only the image worked.
I test it on my windows 10 PC.
from io import StringIO import base64 from reactpy import component, html, utils, hooks from reactpy.backend.fastapi import configure, Options from fastapi import FastAPI import plotly import plotly.express as px import pandas as pd PLOTLY_JS = { 'src': 'https://cdn.plot.ly/plotly-latest.min.js', 'charset': 'utf-8' } DATA = [ {"Status": "Success", "Count": 200, "Quality": "A"}, {"Status": "Failed", "Count": 50, "Quality": "S"}, ...
Replies: 6 comments 5 replies
-
Could you format your code properly?
Beta Was this translation helpful? Give feedback.
All reactions
-
Edited original post to add code formatting.
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks @Archmonger for the edit.
Beta Was this translation helpful? Give feedback.
All reactions
-
This is what I found. That sample code is incomplete, there is no read_data.
I test it with a different code. The issue is in create_fig() specifically the line return html.div(utils.html_to_vdom(fig_html)).
The parameter data changes, the output fig.html also changes. But the displayed plot in the browser does not change.
Could there be an issue in html.div(utils.html_to_vdom(fig_html))?
def create_fig(data): fig = px.pie( data, values="Count", names="Status", hole=0.8, color="Status", color_discrete_map={"Success": "#D7BB0B", "Failed": "#F2F2F2"}, height=70, ) ... fig.write_html("fig.html", include_plotlyjs="cdn") fig_html = open("fig.html", "r", encoding="utf-8").read() return html.div(utils.html_to_vdom(fig_html)) # <==========
Beta Was this translation helpful? Give feedback.
All reactions
-
Based on my findings, I truly believe the issue is with utils.html_to_vdom(). When I inspect on the browser, the correct javascript code is appended within a separate script tag but for some reason it doesn't display on the page. My assumption is the new code is behind the initial script tag and needs to be overwritten instead of being appended.
Beta Was this translation helpful? Give feedback.
All reactions
-
I also tried rendering the image, by saving the fig to 'png'. It also does not work. But if I randomize the image filename, it will render. So this is my current workaround.
Sample read_data()
import pandas as pd app = FastAPI() app.mount("/static", StaticFiles(directory="static"), name="static") def read_data(filter: str) -> list[dict]: data = [ {"Status": "Success", "Count": 200, "Quality": "A"}, {"Status": "Failed", "Count": 50, "Quality": "S"}, {"Status": "Success", "Count": 50, "Quality": "C"}, {"Status": "Failed", "Count": 17, "Quality": "A"}, {"Status": "Success", "Count": 95, "Quality": "C"}, {"Status": "Failed", "Count": 85, "Quality": "A"}, {"Status": "Success", "Count": 4, "Quality": "S"} ] df = pd.DataFrame(data) df = df.loc[df['Quality'] == filter] return df.to_dict('records')
def create_fig(chart_data: list[dict]): ... imgfn = f'{str(uuid.uuid4())[:12]}_fig.png' fig.write_image(f"./static/{imgfn}") return html.img({'src': f'/static/{imgfn}', 'style': {'width': '50%'}})
Beta Was this translation helpful? Give feedback.
All reactions
-
It may not be necessary to save the image to disk. This also works.
import base64 img_bytes = fig.to_image(format="png") encoded = base64.b64encode(img_bytes).decode("utf-8") return html.img({'src': f'data:image/png;base64, {encoded}'})
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks @fsmosca I would try this but how I wish I got this suggestions much earlier. I was stuck on this for days and almost lost my mind so had to re-write my entire code using a different framework.
Beta Was this translation helpful? Give feedback.
All reactions
-
There is still one thing that I cannot solve. There is delay on the image shown and the option I selected.
Here I selected "A" But the image shown is from my previous selection.
I have been reading state and delayed reaction part of the documentation.
This problem is important as we like visual, users alter the input, we alter the visual.
Beta Was this translation helpful? Give feedback.
All reactions
-
Got it, we actually need a button to plot. Users will select "A" or "B" or "S". After the selection, we let users to press the button to plot data based on their filter.
The interaction component
@component def interactions_layout(): data_filter, set_data_filter = hooks.use_state("C") plotly_data, set_plotly_data = hooks.use_state(read_data(data_filter)) def plot_data(event): res = read_data(data_filter) # print(f'current data after filter: {res}') set_plotly_data(res) # html.div(create_fig(plotly_data)), def handle_event(event): set_data_filter(event["target"]["value"]) return html.div( Select(handle_event, data_filter), html.div( html.button( { "style": {"margin-top": "10px"}, "on_click": plot_data }, "Plot data" ), html.div(create_fig(plotly_data)), ) )
Separate component for select
@component def Select(handle_event, data_filter): return html.div( html.select( { 'style': { 'width': '100px' }, "value": data_filter, "on_change": handle_event }, html.option({"value": "A"}, "A"), html.option({"value": "C"}, "C"), html.option({"value": "S"}, "S") ) )
Beta Was this translation helpful? Give feedback.
All reactions
-
This is the full reproducible sample code. See some comments in create_fig().
It is better if the plotly html will work as the users can interact on the rendered plot. Currently only the image worked.
I test it on my windows 10 PC.
from io import StringIO import base64 from reactpy import component, html, utils, hooks from reactpy.backend.fastapi import configure, Options from fastapi import FastAPI import plotly import plotly.express as px import pandas as pd PLOTLY_JS = { 'src': 'https://cdn.plot.ly/plotly-latest.min.js', 'charset': 'utf-8' } DATA = [ {"Status": "Success", "Count": 200, "Quality": "A"}, {"Status": "Failed", "Count": 50, "Quality": "S"}, {"Status": "Success", "Count": 50, "Quality": "C"}, {"Status": "Failed", "Count": 17, "Quality": "A"}, {"Status": "Success", "Count": 95, "Quality": "C"}, {"Status": "Failed", "Count": 85, "Quality": "A"}, {"Status": "Success", "Count": 4, "Quality": "S"} ] def read_data(filter: str) -> list[dict]: df = pd.DataFrame(DATA) df = df.loc[df['Quality'] == filter] return df.to_dict('records') def get_fig(chart_data: list[dict]) -> plotly.graph_objs._figure.Figure: df = pd.DataFrame(chart_data) fig = px.pie( df, values="Count", names="Status", hole=0.8, color="Status", color_discrete_map={"Success": "#D7BB0B", "Failed": "#F2F2F2"}, height=300, width=400, title=f"Plot for filter {chart_data[0]['Quality']}" ) return fig def create_fig(chart_data: list[dict]): fig = get_fig(chart_data) # (1) fig->html_buffer->vdom does not work when data is updated. # buffer = StringIO() # fig.write_html(buffer, include_plotlyjs='cdn') # fig_html = buffer.getvalue() # return html.div(utils.html_to_vdom(fig_html)) # (2) fig->html_file->vdom does not work when data is updated. # htmlfn = 'fig.html' # fig.write_html(htmlfn, include_plotlyjs="cdn") # fig_html = open(htmlfn, "r", encoding='utf-8').read() # return html.div(utils.html_to_vdom(fig_html)) # (3) fig->bytes->img does work even if data is updated. # Disadvantage: Users cannot interact with the image. img_bytes = fig.to_image(format="png") encoded = base64.b64encode(img_bytes).decode("utf-8") return html.img({'src': f'data:image/png;base64, {encoded}'}) @component def Select(handle_event, data_filter): return html.div( html.select( { 'style': { 'width': '100px' }, "value": data_filter, "on_change": handle_event }, html.option({"value": "A"}, "A"), html.option({"value": "C"}, "C"), html.option({"value": "S"}, "S") ) ) @component def PlotButton(plot_data): return html.div( html.button( { "style": {"margin-top": "10px", "width": "100px"}, "on_click": plot_data }, "Plot data" ) ) @component def interactions_layout(): data_filter, set_data_filter = hooks.use_state("C") plotly_data, set_plotly_data = hooks.use_state(read_data(data_filter)) def plot_data(event): set_plotly_data(read_data(data_filter)) def handle_event(event): set_data_filter(event["target"]["value"]) return html.div( Select(handle_event, data_filter), PlotButton(plot_data), html.div(create_fig(plotly_data)) ) @component def Dashboard(): return interactions_layout() app = FastAPI() configure( app, Dashboard, Options( head=html.head( html.script(PLOTLY_JS) ) ) )
Beta Was this translation helpful? Give feedback.