d3.js - 多线图不更新圈子

时间:2017-02-28 22:03:14

标签: javascript jquery d3.js svg charts

我正在制作d3图表(多线图) 我试图表示股票预测,所以基本上图表包含两行:股票价值线和另一条我的预测。

预测是按月计算的,每月的所有日期都会在图表中显示。
为了选择月份,我添加了一个下拉菜单。

我在每个日常数据上添加了一个圆圈,并且第一次运行良好。当用户尝试更改月份时,旧圈子不会更新,但会添加新圈子。

按照关于圈子的代码:

topicEnter.append("g").selectAll("circle")
    .data(function(d){return d.values})
    .enter()
    .append("circle")
    .attr("r", 5)
    .attr("cx", function(dd){return x(dd.date)})
    .attr("cy", function(dd){return y(dd.probability)})
    .attr("fill", "none")
    .attr("stroke", "black");

我已经完成fiddle以更好地了解情况并显示代码。

我在这里缺少什么?为什么不让圈子用线条更新自己?

2 个答案:

答案 0 :(得分:1)

问题在于您的更新周期,但在d3中有很多输入,更新,退出流程的示例。

但基本上是:

  1. 您为每批圆圈附加一个新的g元素,这意味着您每次都附加一个空的选择(该g中没有圆圈)并且每个数据点都被追加(并且没有一个是去除)。你不需要这个额外的附加物。查看现有代码中每个附加的DOM结构。

  2. 您的enter()选择会返回新元素 - 而不是已修改的元素。因此,如果元素的总数保持不变,则会有一个空的enter()选择。您需要单独更新现有元素(或者,将它们全部删除并每次都附加它们)。

  3. 你会想要更接近this的东西:

    // set the data
       circles =  topic.selectAll("circle")
          .data(function(d){return d.values});
    // update existing circles      
       circles.attr("cx", function(dd){return x(dd.date)})
          .attr("cy", function(dd){return y(dd.probability)});
    // add new circles     
       circles.enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(dd){return x(dd.date)})
          .attr("cy", function(dd){return y(dd.probability)})
          .attr("fill", "none")
          .attr("stroke", "black");
       // remove excess circles   
       circles.exit().remove();
    

    您可能还想修改附加行的行以反映d3中的输入,更新,退出周期。

答案 1 :(得分:1)

要解决有关未更新的圈子的问题,您可以执行以下操作:

function update(topics) {
    // Calculate min and max values with arrow functions
  const minValue = d3.min(topics, t => d3.min(t.values, v => v.probability));
  const maxValue = d3.max(topics, t => d3.max(t.values, v => v.probability));
  y.domain([minValue, maxValue]);
  x2.domain(x.domain());
  y2.domain(y.domain());
  // update axes
  d3.transition(svg).select('.y.axis').call(yAxis);
  d3.transition(svg).select('.x.axis').call(xAxis);
  // Update context
  var contextUpdate = context.selectAll(".topic").data(topics);
  contextUpdate.exit().remove();
  contextUpdate.select('path')
  .transition().duration(600)
  .call(drawCtxPath);
  contextUpdate.enter().append('g') // append new topics
    .attr('class', 'topic')
    .append('path').call(drawCtxPath);
  // New data join
  var focusUpdate = focus.selectAll('.topic').data(topics);
  // Remove extra topics not found in data
  focusUpdate.exit().remove(); //remove topics
  // Update paths
  focusUpdate.select('path')
  .transition().duration(600)
  .call(drawPath)
  // Update circles
  var circlesUpdate = focusUpdate
    .selectAll('.topic-circle')
    .data(d => d.values);
  circlesUpdate.exit().remove();
  circlesUpdate.transition().duration(600).call(drawCircle);
  circlesUpdate.enter().append('circle').call(drawCircle);
  // Add new topics
  var newTopics = focusUpdate.enter().append('g') // append new topics
    .attr('class', 'topic');
  // Add new paths
  newTopics.append('path').call(drawPath)
  // Add new circles
  newTopics.selectAll('.topic-circle')
    .data(d => d.values)
    .enter()
    .append('circle')
    .call(drawCircle);
}

使用这些辅助函数来减少代码重复:

function drawCtxPath(path) {
    path.attr("d", d => line2(d.values))
    .style("stroke", d => color(d.name));
}
function drawPath(path) {
    path.attr("d", d => line(d.values))
    .attr('clip-path', 'url(#clip)')
    .style("stroke", d => color(d.name));
}
function drawCircle(circle) {
    circle.attr('class', 'topic-circle')
    .attr('clip-path', 'url(#clip)')
    .attr("r", d => 5)
    .attr("cx", d => x(d.date))
    .attr("cy", d => y(d.probability))
    .attr("fill", "none")
    .attr("stroke", "black");
}

我认为您的代码中存在一些其他问题,当您选择同一个月两次出现错误时,我们可以通过执行以下操作来解决此问题:

d3.select('#month_chart').on("change", function() {
  // Get selected value of the select
  var month = this.options[this.selectedIndex].value;
  // Since you have hardcoded data we need to return a new array
  // This is why if you select the same month twice your code breaks
  // since parseDate will fail since the data will be already parsed
  // the second time
  var monthData = get_monthly_data(month).map(d => {
    return {
      date: parseDate(d.date),
      predicted_bool: d.predicted_bool,
      target: d.target
    };
  });
  // Lets use arrow functions!
  var keys = d3.keys(monthData[0]).filter(k => k !== 'date');
  color.domain(keys);
  // More arrow functions!
  var topics = keys.map(key => {
    return {
      name: key,
      values: monthData.map(d => {
        return {
          date: d.date,
          probability: +d[key]
        };
      })
    };
  });
  x.domain(d3.extent(monthData, d => d.date));
  update(topics);
});

// A good ol' switch 
function get_monthly_data(month) {
  switch (month) {
    case 'gennaio':
      return data_1;
    case 'febbraio':
      return data_2;
    case 'marzo':
      return data_3;
    default:
      return data_1;
  }
}

工作jsfiddle:

https://jsfiddle.net/g699scgt/37/