如何使用图例在折线图中切换折线

时间:2019-11-06 07:16:26

标签: javascript d3.js

这个问题实际上是here之后问题的延续。从那时起,我对小提琴和代码进行了一些更改,但是我仍然面临着同样的问题。链接到我的小提琴,可以找到here的代码。

我正在使用for循环绘制线条,因为我希望图表是动态的,这意味着线条数是根据数据数组中的数组数绘制的。在这种情况下,我的数据数组中有2个数组,如下所示。

var data = [[{x: 0, y: 0}, {x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 40, y: 40}], 
[{x: 0, y: 0}, {x: 10, y: 200}, {x: 20, y: 300}, {x: 30, y: 400}, {x: 40, y: 500}]];

从我的小提琴中,当我同时单击“ Y轴1”和“ Y轴2”时,蓝线将被打开和关闭。但是,我希望在单击Y轴2时打开和关闭红线。这是因为我在这段代码中为两行都分配了相同的ID。

    //************* Plotting of graph ***************
    var colors = ["blue", "red"];
    //plot of chart
    for (var i = 0; i < 2; i++){
      var lineFunction = d3.line()
      .x(function(d) {return x(d.x); })
      .y(function(d) {return yScale[i](d.y); })
      .curve(d3.curveLinear);

      //plot lines
      var paths = g.append("path")
      .attr("class", "path1")
      .attr("id", "blueLine")
      .attr("d", lineFunction(data[i]))
      .attr("stroke", colors[i])
      .attr("stroke-width", 2)
      .attr("fill", "none")
      .attr("clip-path", "url(#clip)")

      //plot a circle at each data point
      g.selectAll(".dot")
        .data(data[i])
        .enter().append("circle")
        .attr("cx", function(d) { return x(d.x)} )
        .attr("cy", function(d) { return yScale[i](d.y); } )
        .attr("r", 3)
        .attr("class", "blackDot")
        .attr("clip-path", "url(#clip)")
        .on("mouseover", mouseover )
        .on("mouseleave", mouseleave )
    }

是否有更好的方法来绘制线条,以便可以为每个要绘制的线条分配特定的ID,并根据图例切换线条?我试过使用forEach(),但似乎无法正常工作。任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:2)

首先:您不应使用循环(forwhileforEach等)在D3代码中附加元素。那不是惯用的,最终您会弯腰去解决问题,就像这个问题将证明的那样。

最简单的解决方法是使用索引来设置行的ID,而无需将代码重构为更惯用的代码,这将需要大量工作。

var paths = g.append("path")
    .attr("class", "path1")
    .attr("id", "blueLine" + i)

...,然后在点击侦听器中使用此笨拙且笨拙的window属性,即元素的ID:

.on("click", function(d, i) {
  var active = window["blueLine" + i].active ? false : true,
    newOpacity = active ? 0 : 1;
  d3.select("#blueLine" + i).style("opacity", newOpacity);
  window["blueLine" + i].active = active;
});

这是您所做的更改的代码:

var xValueArray = [0, 10, 20, 30, 40];
var arr = [
  [0, 10, 20, 30, 40],
  [0, 200, 300, 400, 500]
];
//data array is obtained after structuring arr array
var data = [
  [{
    x: 0,
    y: 0
  }, {
    x: 10,
    y: 10
  }, {
    x: 20,
    y: 20
  }, {
    x: 30,
    y: 30
  }, {
    x: 40,
    y: 40
  }],
  [{
    x: 0,
    y: 0
  }, {
    x: 10,
    y: 200
  }, {
    x: 20,
    y: 300
  }, {
    x: 30,
    y: 400
  }, {
    x: 40,
    y: 500
  }]
];

const margin = {
  left: 20,
  right: 20,
  top: 20,
  bottom: 80
};

const svg = d3.select('svg');
svg.selectAll("*").remove();

const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;

//const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);

//************* Axes and Gridlines ***************

const xAxisG = g.append('g');
const yAxisG = g.append('g');

xAxisG.append('text')
  .attr('class', 'axis-label')
  .attr('x', width / 3)
  .attr('y', -10)
  .style('fill', 'black')
  .text(function(d) {
    return "X Axis";
  });

yAxisG.append('text')
  .attr('class', 'axis-label')
  .attr('id', 'primaryYLabel')
  .attr('x', -height / 2)
  .attr('y', -15)
  .attr('transform', `rotate(-90)`)
  .style('text-anchor', 'middle')
  .style('fill', 'black')
  .text(function(d) {
    return "Y Axis 1";
  });

// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
  .domain([0, d3.max(xValueArray)])
  .range([0, width])
  .nice();

var yScale = new Array();
for (var i = 0; i < 2; i++) {
  // interpolator for Y axis -- inner plot region
  var y = d3.scaleLinear()
    .domain([0, d3.max(arr[i])])
    .range([0, height])
    .nice();
  yScale.push(y);
}

const xAxis = d3.axisTop()
  .scale(x)
  .ticks(5)
  .tickPadding(2)
  .tickSize(-height)

const yAxis = d3.axisLeft()
  .scale(yScale[0])
  .ticks(5)
  .tickPadding(2)
  .tickSize(-width);

yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
  var yAxisSecondary = d3.axisLeft()
    .scale(yScale[i])
    .ticks(5)
  yAxisArray.push(yAxisSecondary);
}

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", `translate(80,${height-80})`)
  .call(xAxis);

svg.append("g")
  .attr("class", "y axis")
  .attr("transform", "translate(80,20)")
  .call(yAxis);

//************* Mouseover ***************
var tooltip = d3.select("body")
  .append("div")
  .style("opacity", 0)
  .attr("class", "tooltip")
  .style("background-color", "white")
  .style("border", "solid")
  .style("border-width", "1px")
  .style("border-radius", "5px")
  .style("padding", "10px")
  .style("position", "absolute")

// A function that change this tooltip when the user hover a point.
// Its opacity is set to 1: we can now see it. Plus it set the text and position of tooltip depending on the datapoint (d)
var mouseover = function(d) {
  tooltip
    .html("x: " + d.x + "<br/>" + "y: " + d.y)
    .style("opacity", 1)
    .style("left", (d3.mouse(this)[0] + 90) + "px")
    .style("top", (d3.mouse(this)[1]) + "px")
}

// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
  tooltip
    .transition()
    .duration(200)
    .style("opacity", 0)
}

//************* Plotting of graph ***************
var colors = ["blue", "red"];
//plot of chart
for (var i = 0; i < 2; i++) {
  var lineFunction = d3.line()
    .x(function(d) {
      return x(d.x);
    })
    .y(function(d) {
      return yScale[i](d.y);
    })
    .curve(d3.curveLinear);

  //plot lines
  var paths = g.append("path")
    .attr("class", "path1")
    .attr("id", "blueLine" + i)
    .attr("d", lineFunction(data[i]))
    .attr("stroke", colors[i])
    .attr("stroke-width", 2)
    .attr("fill", "none")
    .attr("clip-path", "url(#clip)")

  //plot a circle at each data point
  g.selectAll(".dot")
    .data(data[i])
    .enter().append("circle")
    .attr("cx", function(d) {
      return x(d.x)
    })
    .attr("cy", function(d) {
      return yScale[i](d.y);
    })
    .attr("r", 3)
    .attr("class", "blackDot")
    .attr("clip-path", "url(#clip)")
    .on("mouseover", mouseover)
    .on("mouseleave", mouseleave)
}

var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];

//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
  svg.append("g")
    .attr("transform", "translate(" + translation + "," + 20 + ")")
    .call(yAxisArray[i]);

  yAxisG.append('text')
    .attr('x', -height / 2)
    .attr('y', -60)
    .attr('transform', `rotate(-90)`)
    .style('text-anchor', 'middle')
    .style('fill', 'black')
    .text(yLabelArray[i]);

  translation -= 20;
  textTranslation += 20;
}

//************* Legend ***************
var legend = svg.selectAll('.legend')
  .data(data)
  .enter()
  .append('g')
  .attr('class', 'legend');

legend.append('rect')
  .attr('x', width - 5)
  .attr('y', function(d, i) {
    return (i * 20) + 120;
  })
  .attr('width', 18)
  .attr('height', 4)
  .attr("fill", function(d, i) {
    return colors[i]
  });

legend.append('text')
  .attr('x', width - 10)
  .attr('y', function(d, i) {
    return (i * 20) + 120;
  })
  .attr("dy", ".35em")
  .style("text-anchor", "end")
  .text(function(d, i) {
    return yLabelArray[i]
  })
  .on("click", function(d, i) {
    //Determine if current line is visible
    var active = window["blueLine" + i].active ? false : true,
      newOpacity = active ? 0 : 1;
    //Hide or show the elements
    d3.select("#blueLine" + i).style("opacity", newOpacity);
    //Update whether or not the elements are active
    window["blueLine" + i].active = active;
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>