如何在 JS 客户端上正确显示使用 Python Plotly 在服务器上创建的图表?

Posted

技术标签:

【中文标题】如何在 JS 客户端上正确显示使用 Python Plotly 在服务器上创建的图表?【英文标题】:How to correctly display a chart on JS client, created on server using Python Plotly? 【发布时间】:2021-09-03 08:23:37 【问题描述】:

我想在我的前端(用 javascript 编写)显示使用 Python Plotly 创建的图表。该图表是在我的后端(在 Python Flask 上运行)创建的,具有以下结构:

#document                                // chartDoc
<div>                                    // outerDiv
   <div></div>                           // innerDiv
   <script> innerScriptText </script>    // innerScript
</div>

我将它作为 JSON 文件中的字符串发送:"chart": my_chart_str

问题: 我在我的 JS 中收到图表,我创建了一个新的 &lt;script&gt; 元素,我用代码填充它以显示图表(否则浏览器不执行脚本,仅将其打印为纯文本)并且我收到以下错误:

Uncaught TypeError: Cannot read property 'setProperty' of undefined
    at Element.<anonymous> (plotly-latest.min.js:20)
    at plotly-latest.min.js:20
    at ut (plotly-latest.min.js:20)
    at Array.Y.each (plotly-latest.min.js:20)
    at Array.Y.style (plotly-latest.min.js:20)
    at lt (plotly-latest.min.js:61)
    at Object.r.plot (plotly-latest.min.js:61)
    at Object.r.newPlot (plotly-latest.min.js:61)
    at <anonymous>:1:210
    at code.js:38

它来自 plotly.js 库,由 div 组件引起,该组件将 this.style 评估为 undefined

但是,如果我将收到的图表代码手动粘贴到.html 文件中,则图表显示正常。

基本上我想做的是自动化this answer中描述的过程。

这是重现我的错误的最少代码:

index.html

<html>
  <head>
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    <script src="./code.js"></script>
  </head>
  <body>
    <div id="graph-container">

    </div>
  </body>
</html>

code.js

window.onload = function () 
  displayChart();


function displayChart() 
  fetch("http://127.0.0.1:5000/chart", 
    headers:  'Accept': 'application/json', 'Content-Type': 'application/json' ,
    method: "GET"
  )
    .then(response => response.json())
    .then(response => 
      let chartString = response.chart;
      let chartDoc = new DOMParser().parseFromString(chartString, "text/xml"); // #document

      // get elements from received graph
      let outerDiv = chartDoc.firstElementChild;
      let innerDiv = outerDiv.firstElementChild.cloneNode();
      let innerScriptText = outerDiv.lastElementChild.innerHTML;

      // recreate same structure with new component and old content
      let newOuterDiv = document.createElement("div");

      let newInnerScript = document.createElement("script");
      newInnerScript.setAttribute("type", "text/javascript");

      let newInnerScriptText = document.createTextNode(innerScriptText);

      newInnerScript.appendChild(newInnerScriptText);

      newOuterDiv.appendChild(innerDiv);
      newOuterDiv.appendChild(newInnerScript);

      // insert graph in the page
      document.getElementById("graph-container").appendChild(newOuterDiv);
    );

server.py

from flask import Flask
from flask_restful import Api, Resource
from flask_cors import CORS

app = Flask(__name__)
api = Api(app)
CORS(app)

class Chart(Resource):
    def get(self):
        my_chart_str = str(get_chart())
        return "chart": my_chart_str

def get_chart():
    # taken from dash "getting started" guide
    import plotly.graph_objs as go
    from plotly.offline import plot

    x  = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    y1 = [9, 6, 2, 1, 5, 4, 6, 8, 1, 3]
    y2 = [19, 36, 12, 1, 35, 4, 6, 8, 1, 3]
    trace1 = go.Bar(x=x,
                    y=y1,
                    name='Boats')
    trace2 = go.Bar(x=x,
                    y=y2,
                    name='Cars')

    data = [trace1, trace2]
    layout = go.Layout(title='Title',
                    xaxis=dict(title='X axis',
                                tickfont=dict(size=14,
                                                color='rgb(107, 107, 107)'),
                                tickangle=-45),
                    yaxis=dict(title='Y axis',
                                titlefont=dict(size=16,
                                                color='rgb(107, 107, 107)'),
                                tickfont=dict(size=14,
                                                color='rgb(107, 107, 107)')),)

    fig = go.Figure(data=data, layout=layout)
    return plot(fig,
        include_plotlyjs=False,
        output_type='div')

api.add_resource(Chart, "/chart")

if __name__ == "__main__":
    app.run(debug=True, host="127.0.0.1", port=5000)

使用python server.py 启动服务器(我在Windows 上),在浏览器中打开index.html(双击它,而不是通过本地主机),打开开发者控制台,您应该会看到错误。

你知道如何解决吗?

【问题讨论】:

只是一个问题。为什么不直接使用破折号呢?要求了解实际用例 我无法控制此项目中使用的技术。此外,它必须由 JS 的前端开发人员和后端的 pyhton 数据科学家管理。 我明白了。我认为这是不正确的做法。只需使用 FE 库来绘制图表,如 charts.js 或 high-charts 或 w/e,您的 BE 应该只发送数据来填充图表。 我明白你的意思,但图表是可能数百万个数据点的固定大小结果,在这种情况下,发送图表比发送数据点更方便。 【参考方案1】:

我终于找到了解决方法。

我没有将图表作为 JSON 字符串发送,而是执行以下操作:

    index.html 一个 JQuery 函数调用服务器。 服务器创建图表(以div 的形式)并将其保存在一个名为chart.html 的文件中。 服务器将chart.html文件返回给JQuery。 JQuery 接收文件并将其插入到 index.html(this answer 帮助我弄清楚如何将 html 文件嵌入到另一个文件中)。

我的工作代码:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="./index.js"></script>
  </head>
  <body>
      <p>CHART WILL BE SHOWN BELOW</p>
      <div id="chart-container"></div>
  </body>
</html>

index.js

(function () 
    window.onload = function () 
        getChart();
    

    function getChart() 
        $("#chart-container").load("get_chart"); // chart will be inserted in "chart-container"
    
)();

server.py

from flask import Flask, render_template
from flask_cors import CORS

app = Flask(__name__, static_url_path="", static_folder='', template_folder='')
CORS(app)

def create_chart():
    # sample graph
    import plotly.express as px

    fig = px.imshow([[1, 20, 30],
                    [20, 1, 60],
                    [30, 60, 1]])
    fig.write_html("./chart.html", include_plotlyjs=False, full_html=False)

@app.route("/")
def index():
    return render_template("/index.html") # return "index.html" without chart

@app.route("/get_chart")
def get_chart():
    create_chart() # create "chart.html" file
    return render_template("/chart.html") # return chart to jquery

if __name__ == "__main__":
    app.run(debug=True, host="127.0.0.1", port=5000)

要运行它,首先启动服务器(windows:python server.py)然后打开浏览器并导航到127.0.0.1:5000

【讨论】:

以上是关于如何在 JS 客户端上正确显示使用 Python Plotly 在服务器上创建的图表?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Python、PyQt5 和 Pyinstaller 修复未正确显示的按钮

不和谐.JS |如何使用 **channel.client** 功能而不显示机器人客户端信息?

Clickhouse 客户端无法在 linux 屏幕上正确显示数据

如何在 web3.js 中正确使用还原原因在 UI 中显示有意义的错误消息

实例化的预制比例在客户端上未正确显示

用js在网页上显示当前日期和时间,并显示是星期几