【问题标题】:Embed D3 + HTML with Dash Components使用 Dash 组件嵌入 D3 + HTML
【发布时间】:2021-07-26 01:44:07
【问题描述】:

我有 D3 + HTML 代码,我想将其嵌入 Plotly Dash 中。有人可以帮忙吗?

例如,如果这是使用 D3 填充散点图的代码,我应该如何使用 plotly dash 回调?我已经有一个使用 plotly dash 构建的仪表板。因此,当单击应用按钮时,应调用此 javascript 并显示散点图。


<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom;

function make_x_axis() {
    return d3.axisBottom(x)
        // .scale(x)
        //  .orient("bottom")
         .ticks(5)
}

function make_y_axis() {
    return d3.axisLeft(y)
        // .scale(y)
        // .orient("left")
        .ticks(5)
}

let points = d3.range(1, 10).map(function(i) {
    return [i * width / 10, 50 + Math.random() * (height - 100)];
});

var x = d3.scaleLinear()
    .rangeRound([0, width]);

var y = d3.scaleLinear()
    .rangeRound([height, 0]);

var xAxis = d3.axisBottom(x),
    yAxis = d3.axisLeft(y);

var line = d3.line()
    .x(function(d) { return x(d[0]); })
    .y(function(d) { return y(d[1]); });



let drag = d3.drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended);

svg.append('rect')
    .attr('class', 'zoom')
    .attr('cursor', 'move')
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .attr('width', width)
    .attr('height', height)
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')



// svg.append("g")
//         .attr("class", "grid")
//         .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
//         .call(make_x_axis()
//             .tickSize(-height, 0, 0)
//             .tickFormat("")
//         )
//
// svg.append("g")
//     .attr("class", "grid")
//     .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
//     // .attr("transform", "translate(0," + (height + margin.top) + ")")
//     .call(make_y_axis()
//     .tickSize(-width, 0, 0)
//     .tickFormat("")
//          )
svg.append("g")
      .attr("class", "grid")
      .attr("transform", `translate(${margin.left}, ${height + margin.top})`)
      .call(make_x_axis()
          .tickSize(-height)
          .tickFormat("")
      )

  // add the Y gridlines
  svg.append("g")
      .attr("class", "grid")
      .attr("transform", `translate(${margin.left}, ${margin.top})`)
      .call(make_y_axis()
          .tickSize(-width)
          .tickFormat("")
      )

 var focus = svg.append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

x.domain(d3.extent(points, function(d) { return d[0]; }));
y.domain(d3.extent(points, function(d) { return d[1]; }));

focus.append("path")
    .datum(points)
    .attr("fill", "none")
    .attr("stroke", "white")
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round")
    .attr("stroke-width", 1.5)
    .attr("d", line);

focus.selectAll('circle')
    .data(points)
    .enter()
    .append('circle')
    .attr('r', 5.0)
    .attr('cx', function(d) { return x(d[0]);  })
    .attr('cy', function(d) { return y(d[1]); })
    .style('cursor', 'pointer')
    .style('fill', 'steelblue');

focus.selectAll('circle')
        .call(drag);

focus.append('g')
    .attr('class', 'axis axis--x')
    .attr('transform', 'translate(0,' + height + ')')
    .call(xAxis);


focus.append('g')
    .attr('class', 'axis axis--y')
    .call(yAxis);


function dragstarted(d) {
    d3.select(this).raise().classed('active', true);
}

function dragged(d) {
    //d[0] = x.invert(d3.event.x);
    d[1] = y.invert(d3.event.y);
    d3.select(this)
        //.attr('cx', x(d[0]))
        .attr('cy', y(d[1]))
    focus.select('path').attr('d', line);
}

function dragended(d) {
    d3.select(this).classed('active', false);
}

</script>

用法.py

import dash
#import dash_alternative_viz as dav
import dash_html_components as html
from dash.dependencies import Input, Output
#import random

external_scripts = [
    "https://d3js.org/d3.v4.min.js"
    
]

app = dash.Dash(external_scripts=external_scripts)
app.layout = html.Div(
        [html.Div(id="content", className="app-header"), 
         html.Button(id="button", children="Button")]
)

@app.callback(
    Output("content", "style"),
    [dash.dependencies.Input("button", "n_clicks")],
)
def update_output(n_clicks):
    if n_clicks:
        return {"display": "block"}
    return {"display": "no graph"}
    
if __name__ == "__main__":
   app.run_server( port = 8052, debug=True)

header.css

app-header {
  height: 350px;
  width: 50%;
  background-color: powderblue;
}

谢谢, 米拉

【问题讨论】:

    标签: html d3.js plotly-dash


    【解决方案1】:

    要在 Dash 应用程序中嵌入 Javascript,请参阅 this。包含 Javascript(和 CSS)的最简单方法是在应用的根目录中创建一个 assets 文件夹。

    要在 Dash 应用程序中执行示例中的代码,您可以将 Javascript 代码放在 assets 文件夹中的 .js 文件中。

    所以你可以有一个看起来像这样的script.js 文件:

    window.addEventListener("load", function () {
      const svg = d3
        .select("#content")
        .append("svg")
        .attr("width", "500")
        .attr("height", "350");
    
      const margin = { top: 20, right: 20, bottom: 30, left: 50 };
      const width = +svg.attr("width") - margin.left - margin.right;
      const height = +svg.attr("height") - margin.top - margin.bottom;
    
      function make_x_axis() {
        return (
          d3
            .axisBottom(x)
            // .scale(x)
            //  .orient("bottom")
            .ticks(5)
        );
      }
    
      function make_y_axis() {
        return (
          d3
            .axisLeft(y)
            // .scale(y)
            // .orient("left")
            .ticks(5)
        );
      }
    
      let points = d3.range(1, 10).map(function (i) {
        return [(i * width) / 10, 50 + Math.random() * (height - 100)];
      });
    
      var x = d3.scaleLinear().rangeRound([0, width]);
    
      var y = d3.scaleLinear().rangeRound([height, 0]);
    
      var xAxis = d3.axisBottom(x),
        yAxis = d3.axisLeft(y);
    
      var line = d3
        .line()
        .x(function (d) {
          return x(d[0]);
        })
        .y(function (d) {
          return y(d[1]);
        });
    
      let drag = d3
        .drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended);
    
      svg
        .append("rect")
        .attr("class", "zoom")
        .attr("cursor", "move")
        .attr("fill", "none")
        .attr("pointer-events", "all")
        .attr("width", width)
        .attr("height", height)
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
      svg
        .append("g")
        .attr("class", "grid")
        .attr("transform", `translate(${margin.left}, ${height + margin.top})`)
        .call(make_x_axis().tickSize(-height).tickFormat(""));
    
      // add the Y gridlines
      svg
        .append("g")
        .attr("class", "grid")
        .attr("transform", `translate(${margin.left}, ${margin.top})`)
        .call(make_y_axis().tickSize(-width).tickFormat(""));
    
      var focus = svg
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
      x.domain(
        d3.extent(points, function (d) {
          return d[0];
        })
      );
      y.domain(
        d3.extent(points, function (d) {
          return d[1];
        })
      );
    
      focus
        .append("path")
        .datum(points)
        .attr("fill", "none")
        .attr("stroke", "white")
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round")
        .attr("stroke-width", 1.5)
        .attr("d", line);
    
      focus
        .selectAll("circle")
        .data(points)
        .enter()
        .append("circle")
        .attr("r", 5.0)
        .attr("cx", function (d) {
          return x(d[0]);
        })
        .attr("cy", function (d) {
          return y(d[1]);
        })
        .style("cursor", "pointer")
        .style("fill", "steelblue");
    
      focus.selectAll("circle").call(drag);
    
      focus
        .append("g")
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
    
      focus.append("g").attr("class", "axis axis--y").call(yAxis);
    
      function dragstarted(d) {
        d3.select(this).raise().classed("active", true);
      }
    
      function dragged(d) {
        //d[0] = x.invert(d3.event.x);
        d[1] = y.invert(d3.event.y);
        d3.select(this)
          //.attr('cx', x(d[0]))
          .attr("cy", y(d[1]));
        focus.select("path").attr("d", line);
      }
    
      function dragended(d) {
        d3.select(this).classed("active", false);
      }
      
      document.querySelector("#content").style.display = "none" // Hide if button content if button has not yet been clicked.
    });
    

    我将您的代码放在load 事件侦听器中,以确保 Javascript 仅在页面加载后执行。

    我所做的另一项调整是在 Dash 布局中添加一个容器 div(我给它的 id 为 content),并将一个 svg 元素附加到这个容器中。您还可以选择 body 或从 Dash 布局呈现的任何其他元素,并将 svg 附加到该元素。

    据我所知,没有真正的 Dash svg 组件可以直接放入 Dash 布局中。

    所以想法是让 d3 为我们创建 svg 元素而不是 Dash。

    显示图表的最小 Dash 布局如下所示:

    external_scripts = [
        "https://d3js.org/d3.v4.min.js",
    ]
    
    app = dash.Dash(external_scripts=external_scripts)
    app.layout = html.Div(
            [html.Div(id="content"), html.Button(id="button", children="Button")]
    )
    

    使用Dash 的external_scripts 参数包含d3。

    为了在按钮点击时实际显示图表,我可以想到两种方法(略有不同)。

    您使用 Dash 回调在 Python 中处理按钮点击:

    app = dash.Dash(external_scripts=external_scripts)
    app.layout = html.Div(
        [
            html.Div(id="content"),
            html.Button(id="button", children="Button"),
        ]
    )
    
    
    @app.callback(
        Output("content", "style"),
        [dash.dependencies.Input("button", "n_clicks")],
    )
    def update_output(n_clicks):
        if n_clicks:
            return {"display": "block"}
        return {}
    

    如果至少在未显示图表时未单击按钮。如果单击该按钮,则会显示图形。您可以更改行为以切换图表的可见性或在单击一次后隐藏按钮,根据您的需要进行调整。

    另一种方法是添加click listener in your Javascript script。

    如果您遵循上述示例,请务必在 assets 文件夹中添加一个 CSS 文件,并将 width 和 height 添加到 div,否则不会显示任何内容。 em>


    另一种更复杂的方法是为您的图表创建一个React 组件,并将其转换为您可以在您的仪表板布局中使用的仪表板组件。请参阅我的回答 here 和官方文档 here 以获得一个想法。如果您打算使用大量这些 Javascript 库与 Dash 交互,我认为这种方法更可取。它为您提供更多控制权,并且您可以以更具声明性的方式与这些 Javascript 库进行交互。

    【讨论】:

    • 嗨@Bas van der Linden,非常感谢您的详细解释。我已经按照您的指示尝试了添加资产文件夹并复制了 script.js 和 css 文件。我仍然无法得到图表。你能帮忙吗。我已经编辑了添加到主要问题中的代码。
    • @MeeraS 您可以尝试将external_scripts 更改为我的答案吗?还可以尝试清空缓存和硬重新加载(如果您在 mac 上,则 ctrl + shift + r 或类似的东西)。可能是css没有应用于div。
    • 不,我没有更改任何 id,我只是为 CSS 添加了一个类名
    • @MeeraS 糟糕,我读错了。如果您删除破折号布局中的display: none 样式,您会看到图表吗?我认为由于添加了“显示:无”的方式,脚本在运行时找不到元素。我已经编辑了答案中的代码。
    • 运气不好,:(这一次也是..我也在探索使运行....
    猜你喜欢
    • 2020-09-10
    • 1970-01-01
    • 2021-08-09
    • 2016-11-16
    • 2020-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    相关资源
    最近更新 更多