D3: How do you highlight a range of dates on D3 a

2019-04-15 14:10发布

I’m building a simple line chart using D3. The chart will contain a range of dates on the X axis and a range of values on the Y axis.

I’d like to draw a couple of rectangles behind the line. These rect’s would span the height of the chart and span the width of the dates that I would define. Let’s say for example rect 1 being between Thu 18th to Wed 24th and rect 2 being between Wed 24th and Tue 30th.

These rect’s would also have a click event attached to them. So that if clicked, they would trigger a function that I would define. This function would need to know which rect was clicked.

Has anybody achieved something like this before? Can anybody help steer me in the right direction? Thanks so much.

I've created this Fiddle to get started (based on an mbostock example):

http://jsfiddle.net/Critter/TJqE6/2/

var data = [
  {"date":"1-May-13","close":58.13},
  {"date":"30-Apr-13","close":53.98},
  {"date":"27-Apr-13","close":67.00},
  {"date":"26-Apr-13","close":89.70},
  {"date":"25-Apr-13","close":99.00},
  {"date":"24-Apr-13","close":130.28},
  {"date":"23-Apr-13","close":166.70},
  {"date":"20-Apr-13","close":234.98},
  {"date":"19-Apr-13","close":345.44},
  {"date":"18-Apr-13","close":443.34},
];

var margin = {top: 20, right: 50, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var parseDate = d3.time.format("%d-%b-%y").parse,
    bisectDate = d3.bisector(function(d) { return d.date; }).left,
    formatValue = d3.format(",.2f"),
    formatCurrency = function(d) { return "$" + formatValue(d); };

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;

  data.sort(function(a, b) {
    return a.date - b.date;
  });

  x.domain([data[0].date, data[data.length - 1].date]);
  y.domain(d3.extent(data, function(d) { return d.close; }));

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Price ($)");

  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);

  var focus = svg.append("g")
      .attr("class", "focus")
      .style("display", "none");

  focus.append("circle")
      .attr("r", 4.5);

  focus.append("text")
      .attr("x", 9)
      .attr("dy", ".35em");

  svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function() { focus.style("display", null); })
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mousemove", mousemove);

  function mousemove() {
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectDate(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
    focus.select("text").text(formatCurrency(d.close));
  }
});

1条回答
甜甜的少女心
2楼-- · 2019-04-15 14:47

The main issue is putting the rectangles behind the lines. Given a date you can calculate its position on the corresponding axis like this - say you want to put a bar starting on 04/19/2013 and ending on 04/21/2013.

    //here x is already the x axis time scale you have defined in your code
    var left = x(new Date("Apr 19 2013"));
    var right = x(new Date("Apr 22 2013")); //one more day
    var wid = right - left;
    svg.append("rect")
      .attr("x", left)
      .attr("width", wid)
      .attr("height", height)

Now you can also add a style class to fill or stroke or set opacity etc. Adding click events should be easy but since there is no z-index for SVG which only relies on DOM order you can see if you can insert the rectangles before you insert the chart lines.

查看更多
登录 后发表回答