2

Having some difficulty putting up a vertical line across three similar line charts (same dataset).

Var testdate1 contains a date, which is found on the chart (top of the lc1.on(renderlet) function) at index pos 4 (4th "circle.dot" on the linechart).

How would I add a vertical line to the chart at that position? (I wish to add vertical lines at the same position on all three line charts, but I expect it will be the same procedure for all three).

Also, for some reason, I can't get the circle at that position to style as a red or green dot, although it works in this SO answer by davcs86 -- but in his example we locate the dot using x-axis (date), and this code uses index number in the alldotsN collection -- can't see what I'm doing wrong here either.

jsFiddle to experiment with

var chartHeight = 250;
var chartWidth = 500;
var myCSV = [ 
  {"shift":"1","date":"01/01/2016/08/00/00/+0500","car":"178","truck":"125","bike":"317","moto":"237"},
  {"shift":"2","date":"01/01/2016/17/00/00/+0500","car":"125","truck":"189","bike":"125","moto":"273"},
  {"shift":"3","date":"02/01/2016/08/00/00/-0500","car":"140","truck":"219","bike":"328","moto":"1252"},
  {"shift":"4","date":"02/01/2016/17/00/00/+0500","car":"222","truck":"290","bike":"432","moto":"378"},
  {"shift":"5","date":"03/01/2016/08/00/00/+0500","car":"200","truck":"250","bike":"420","moto":"319"},
  {"shift":"6","date":"03/01/2016/17/00/00/+0500","car":"230","truck":"220","bike":"310","moto":"413"},
  {"shift":"7","date":"04/01/2016/08/00/00/+0500","car":"155","truck":"177","bike":"377","moto":"180"},
  {"shift":"8","date":"04/01/2016/17/00/00/+0500","car":"179","truck":"203","bike":"405","moto":"222"},
  {"shift":"9","date":"05/01/2016/08/00/00/+0500","car":"208","truck":"185","bike":"360","moto":"195"},
  {"shift":"10","date":"05/01/2016/17/00/00/+0500","car":"150","truck":"290","bike":"315","moto":"280"},
  {"shift":"11","date":"06/01/2016/08/00/00/+0500","car":"200","truck":"220","bike":"350","moto":"205"},
  {"shift":"12","date":"06/01/2016/17/00/00/+0500","car":"230","truck":"170","bike":"390","moto":"400"}
];
var testdate1 = +new Date('Sun Jan 03 2016 08:00:00 GMT-0500 (Eastern Standard Time)');
lc1 = dc.lineChart("#line1");
lc2 = dc.lineChart("#line2");
lc3 = dc.lineChart("#line3");

var dateFormat = d3.time.format("%d/%m/%Y/%H/%M/%S/%Z");
myCSV.forEach(function (d) {
 d.date = dateFormat.parse(d.date);
 d.car = +d.car;
 d.bike = +d.bike;
 d.moto = +d.moto;
});

var facts = crossfilter(myCSV);

var dateDim = facts.dimension(function (d) {return d.date});
var carDim = facts.dimension(function (d) {return d['car']});
var dgCar = dateDim.group().reduceSum(function (d) {return d['car']});
var bikeDim = facts.dimension(function (d) {return d['bike']});
var dgBike = dateDim.group().reduceSum(function (d) {return d['bike']});
var motoDim = facts.dimension(function (d) {return d['moto']});
var dgMoto = dateDim.group().reduceSum(function (d) {return d['moto']});

var minDate = myCSV[0].date; //new Date ("2016-01-01T08:00:00.000Z");
var maxDate = myCSV[myCSV.length-1].date; //new Date ("2016-01-06T17:00:00.000Z"); 

lc1
  .renderArea(false)
  .width(chartWidth)
  .height(chartHeight)
  .dimension(dateDim)
  .group(dgCar)
  .defined(function(d) {if(d.y !==null) {return d.y;}})
  .transitionDuration(1000)
  .margins({top: 30, right: 20, bottom: 35, left: 60})
  .yAxisLabel('Cars')
  .renderHorizontalGridLines(true)
  .brushOn(false)
  .x(d3.time.scale().domain([minDate,maxDate]));
lc1.yAxis().ticks(5);
lc1.xAxis().ticks(3);

lc2
.renderArea(false)
.width(chartWidth)
.height(chartHeight)
.dimension(dateDim)
.group(dgBike)
.defined(function(d) {if(d.y !==null) {return d.y;}})
.transitionDuration(1000)
.margins({top: 30, right: 20, bottom: 35, left: 60})
.yAxisLabel('Bikes')
.renderHorizontalGridLines(true)
.brushOn(false)
.x(d3.time.scale().domain([minDate,maxDate]));
lc2.yAxis().ticks(5);
lc2.xAxis().ticks(3);

lc3
.renderArea(false)
.width(chartHeight)
.height(250)
.dimension(dateDim)
.group(dgMoto)
.defined(function(d) {if(d.y !==null) {return d.y;}})
.transitionDuration(1000)
.margins({top: 30, right: 20, bottom: 35, left: 60})
.yAxisLabel('Motos')
.renderHorizontalGridLines(true)
.brushOn(false)
.x(d3.time.scale().domain([minDate,maxDate]));
lc3.yAxis().ticks(5);
lc3.xAxis().ticks(3);


lc1.on('renderlet', function(lc1) {
   var thespot;
   var allDots1 = lc1.selectAll('circle.dot');
    allDots1.filter(function(d,i){ //d==datum (obj), i==index (of datapoint on line)
      if (+d.x===testdate1) thespot = i;
   });
    console.log('found spot: ' +thespot); //== 4th position on line
    //display red circle - NOT WORKING
    alldots1.filter((d,i) => i === thespot).classed('reddot',true);
    alldots2.filter((d,i) => i === thespot).classed('greendot',true);
    alldots3.filter((d,i) => i === thespot).classed('greendot',true);
    //display vertical line on all 3 graphs at same point - NOT WORKING
    alldots1
     .filter((d,i) => +i === +thespot)
     .append('line')
      .attr('x1', +testdate1)
      .attr('y1', chartHeight - margins.top)
      .attr('x2', +testdate1)
      .attr('y2', 0 + margins.top)
     .style("stroke-width", 2)
     .style("stroke", "red")
     .style("fill", "none");
    alldots2
     .filter((d,i) => +i === +thespot)
     .append('line')
      .attr('x1', +testdate1)
      .attr('y1', chartHeight - margins.top)
      .attr('x2', +testdate1)
      .attr('y2', 0 + margins.top)
     .style("stroke-width", 2)
     .style("stroke", "red")
     .style("fill", "none");
    alldots3
     .filter((d,i) => +i === +thespot)
     .append('line')
      .attr('x1', +testdate1)
      .attr('y1', chartHeight - margins.top)
      .attr('x2', +testdate1)
      .attr('y2', 0 + margins.top)
     .style("stroke-width", 2)
     .style("stroke", "red")
     .style("fill", "none");
});//END lc1.renderlet

dc.renderAll();
dc.redrawAll();
.reddot {
  stroke: red !important;
  fill: red !important;
  fill-opacity: 1 !important;
}

.greendot {
  stroke: green;
  fill: green;
  fill-opacity: 1 !important;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.1/crossfilter.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
<script src="//dc-js.github.io/dc.js/js/dc.js"></script>

<link href="//dc-js.github.io/dc.js/css/dc.css" rel="stylesheet" />


<svg id="line1"></svg>
<svg id="line2"></svg>
<svg id="line3"></svg>
davcs86
  • 3,926
  • 2
  • 18
  • 30
crashwap
  • 2,846
  • 3
  • 28
  • 62
  • Some first ideas: does the on(renderlet) run at all? Didn't on my iPad. As a consequence, also didn't the stuff inside you expected. The circle formatting looks okay to me, that should work. The line appending looks wrong. Shouldn't you append the line to the svg? The way it's now it would be appended inside the selected circle. – Florian May 27 '17 at 06:56

4 Answers4

3

http://jsfiddle.net/v6ehemnq/1/

Here's a summary of changes that I incorporated from the other two answers.

First, I made the red dot render correctly.

// fixed `alldots1` to `allDots1`, now the red point renders correctly
allDots1.filter((d, i) => i === thespot).classed('reddot', true);

Then I drew in the lines by eyeballing where the screenMinX and screenMaxX are. You may want to use a more precise measurement depending on your needs.

var screenMinX = margins.left; // left most pixel coordinate of graph
var screenMaxX = margins.left + 408; // right most pixel coordinate of graph
var xScale = d3.time.scale().domain([minDate, maxDate]).range([screenMinX, screenMaxX]);
var vertLineXCoord = xScale(testdate1);

d3.select('#line1')
  .append('line')
  .attr('x1', vertLineXCoord)
  .attr('y1', chartHeight - margins.top)
  .attr('x2', vertLineXCoord)
  .attr('y2', 0 + margins.top)
  .style("stroke-width", 2)
  .style("stroke", "red")
  .style("fill", "none");

Note that var margins never existed before, so I created it to allow margins.left and margins.top to be available.

therobinkim
  • 2,500
  • 14
  • 20
3

It has a simpler (and re-usable) solution by using the dc.js API:

/* draw vertical lines code */
var line = d3.svg.line().interpolate('linear');
function draw_verticals(chart, points){
    // merge
    var selection = chart.g()
      .select('g.chart-body')
      .selectAll('path.horizontal')
      .data(points)
    // append
    selection.enter()
      .append('path')
      .attr('class', 'horizontal reddot')
      .attr('d', function(d) {
        var x = chart.x()(d);
        return line([
          [x, chart.y().range()[0]],
          [x, chart.y().range()[1]]
        ]);
      });
    // remove
    selection.exit().remove();
}
/*  ends here */

with this, you only pass the chart and an array of points where you want to draw the vertical lines, e.g.

draw_verticals(lc1, [testdate1, testdate2]);
draw_verticals(lc2, [testdate1, testdate2]);
draw_verticals(lc3, [testdate1, testdate2]);

Working demo

Reference

Reference's source code

Additional

For using together with the brush, you have to

1) For each chart remove the line(s) before the animation, with

lcX.on('pretransition', function(c){
  draw_verticals(c, []);
});

2) Redraw the line(s) in the leaflet event

lcX.on('renderlet', function(c) {
  var thespot;
  var allDots = c.selectAll('circle.dot');
  allDots.filter(function(d, i) { //d==datum (obj), i==index (of datapoint on line)
    if (+d.x === testdate1) thespot = i;
  });
  // fixed `alldots1` to `allDots1`, now the red point renders correctly
  allDots.filter((d, i) => i === thespot).classed('reddot', true);
  draw_verticals(c, (thespot?[testdate1]:[]));
});
davcs86
  • 3,926
  • 2
  • 18
  • 30
  • 1
    That is fantastic! One problem, though -- it does not move with the brush. If brush is used to zoom in on a time window, the datapoints spread out, and the pinkdot moves, but the vertical lines remain where they are. Any suggestions for that? – crashwap May 31 '17 at 02:55
  • Ok, send me the jsfiddle when you finish it. – davcs86 May 31 '17 at 04:16
  • Sorry took so long, had some difficulty with it. [Here is the jsFiddle](http://jsfiddle.net/e4mn9ptr/) - note that when you select, say, one-quarter of the dates via the brush up top, and then slide the brushed area around, the vertical line will not update. – crashwap May 31 '17 at 04:52
  • Check out the changes I made in your fiddle [http://jsfiddle.net/davcs86/e4mn9ptr/3/](http://jsfiddle.net/davcs86/e4mn9ptr/3/) – davcs86 May 31 '17 at 16:34
  • That looks great. Thanks Davcs. I'll run with this and see if I can apply it to all the use cases I have on the table. Much appreciated, and well earned! – crashwap Jun 01 '17 at 02:21
  • You're welcome, thanks for the bounty points. BTW, I'm David. – davcs86 Jun 01 '17 at 14:01
2
var xScale = d3.time.scale().domain([minDate,maxDate]).range([screenMinX, screenMaxX]);

var vertLineXCoord = xScale(myCSV[pointYouWantToDrawLineAt].car); // or bike, or moto

vertLineXCoord should be the x coordinate of the your vertical line.

[screenMinX, screenMaxX] are the pixel coordinates of the left most and right most points of the graph. See here for more explanation of how scales work.

ToxicTeacakes
  • 1,124
  • 1
  • 10
  • 19
2

Typo in your script: alldots1 should read allDots1. When corrected, red dot appears.

Florian
  • 4,821
  • 2
  • 19
  • 44