使用Dash组件嵌入D3 + HTML [英] Embed D3 + HTML with Dash Components

查看:78
本文介绍了使用Dash组件嵌入D3 + HTML的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have D3 + HTML code which I want to Embed in Plotly Dash . Can someone help ?

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

For Example if this the code that populates a scatter plot using D3 , how should I callback using plotly dash ? I already have a dashboard built using plotly dash . So when apply button is clicked this javascript should be called and wll display the scatter graph.


<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>

Usage.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

header.css

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

谢谢,米拉

推荐答案

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

For embedding Javascript in Dash apps see this. The easiest way to include Javascript (and CSS) is to make an assets folder in the root directory of your app.

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

To execute the code in your example in a Dash app you could put your Javascript code in a .js file in the assets folder.

因此您可能会有一个看起来像这样的 script.js 文件:

So you could have a script.js file that looks something like this:

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.

I put your code in a load event listener to make sure the Javascript only executes if the page has loaded.

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

The other adjustment I made was to add a container div to the Dash layout (which I gave an id of content) and append an svg element to this container. You could also select the body or any other element rendered from the Dash layout and append the svg to that.

据我所知,实际上并没有Dash svg组件,您可以直接将其放置在Dash布局中.

所以想法是让d3代替Dash为我们创建svg元素.

So the idea is to let d3 create the svg element for us instead of Dash.

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

A minimal Dash layout that shows the graph looks something like this:

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.

d3 is included using the external_scripts parameter of Dash.

要真正在按钮上显示图表,我可以想到两种方法(略有不同).

For actually showing the chart on button click I can think of two approaches (varying slightly).

您可以使用Dash回调处理Python中的按钮单击:

You handle a button click in Python using a Dash callback:

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 {}

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

If the button hasn't been clicked at least once the graph isn't shown. If the button is clicked the graph is shown. You could change the behavior to toggle the visibility of the graph or hide the button after it is clicked once, adjust according to your needs.

另一种方法是在Javascript脚本中添加点击侦听器.

The other approach is to add a click listener in your Javascript script.

请确保还要在 assets 文件夹中添加CSS文件,并在div中添加 width height 您按照上面的示例操作,否则将不会显示任何内容.

Be sure to also add a CSS file in the assets folder and add a width and a height to the div if you follow above example, or nothing is going to show up.

另一种涉及更多的方法是为图形创建一个 React 组件,并将其转换为可在仪表板布局中使用的Dash组件.在此处和我的官方文档

Another more involved approach would be to create a React component for your graph and convert this to a Dash component that you could use in your dash layout. See my answer here and the official documentation here to get an idea. If you plan on using a lot of these Javascript libraries to interact with Dash I think this approach is preferable. It gives you more control and you can interact with these Javascript libraries in a more declarative way.

这篇关于使用Dash组件嵌入D3 + HTML的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆