Arak Tai'Roth
Arak Tai'Roth

Reputation: 408

Using d3-tip on line graph without defined points

So I've gone and implemented something similar to this using that as a tutorial. However, where it reaches the point about a tooltip I want to do something slightly different, I really want to use d3-tip. So I've spent a bunch of time learning how to use d3-tip but it seems that everywhere I look people are defining elements and then attaching mouseover and mouseout events to them in order to make the tooltip appear. I don't believe I can do that because I'm using the previous method of drawing a circle over the line based on where my mouse is on the graph. So I'm wondering what I can do to get d3-tip to work with this implementation, or if it's even possible?

The following is my code:

var margin = {
  top: 20,
  left: 50,
  right: 50,
  bottom: 50
},
width = $element.innerWidth() - margin.left - margin.right,
height = 0.2 * width;

var parseTime = d3.timeParse('%m/%d/%Y');
data.forEach(function(d) {
  d.date = parseTime(d.date);
  d.price = +d.price;
});
data.sort(function(a, b) {
  return d3.ascending(a.date, b.date);
});
var formatDate = d3.timeFormat('%b %-d / %Y');
var bisectDate = d3.bisector(function(d) { return d.date; }).left;

var x = d3.scaleTime()
  .domain(d3.extent(data, function(d, i) {
    return d.date;
  }))
  .range([0, width]);
var y = d3.scaleLinear()
  .domain(d3.extent(data, function(d, i) {
    return d.price;
  }))
  .range([height, 0]);

var xAxis = d3.axisBottom(x)
  .tickSizeOuter(0);
var yAxis = d3.axisLeft(y)
  .ticks(5)
  .tickSizeOuter(0);

var area = d3.area()
  .x(function(d) { return x(d.date); })
  .y0(height)
  .y1(function(d) { return y(d.price); });

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

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(d) {
    return 'Closing: ' + d.price +
      '<br />' +
      'Date: ' + formatDate(d.date);
});

var svg = d3.select('#priceChart')
  .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 + ')');
svg.call(tip);

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

svg.append('g')
  .attr('class', 'y axis')
  .call(yAxis);

var areaSvg = svg.append('g');
areaSvg.append('path')
  .attr('class', 'area')
  .attr('d', area(data))
  .style('opacity', 0.3);

var lineSvg = svg.append('g');
lineSvg.append('path')
  .attr('class', 'line')
  .attr('d', line(data));

var focus = svg.append('g')
  .style('display', 'none');
focus.append('line')
    .attr('class', 'x dash')
    .attr('y1', 0)
    .attr('y2', height);
focus.append('line')
    .attr('class', 'y dash')
    .attr('x1', width)
    .attr('x2', width);
focus.append('circle')
  .attr('class', 'y circle')
  .attr('r', 5);

svg.append('rect')
  .attr('width', width)
  .attr('height', height)
  .style('fill', 'none')
  .style('pointer-events', 'all')
  .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.select('circle.y')
    .attr('transform', 'translate(' + x(d.date) + ',' + y(d.price) + ')');
  focus.select('.x')
    .attr('transform', 'translate(' + x(d.date) + ',' + y(d.price) + ')')
    .attr('y2', height - y(d.price));
  focus.select('.y')
    .attr('transform', 'translate(' + width * -1 + ',' + y(d.price) + ')')
    .attr('x2', width + width);
}

Also, I've got this jsFiddle here that shows all my current work. I've got everything in place and it all works great, minus actually showing the tooltip.

Upvotes: 2

Views: 263

Answers (1)

mkaran
mkaran

Reputation: 2718

I changed your fiddle a bit so that the tooltip is shown and the text is updated accordingly:

.on('mouseover', function(d) {
    focus.style('display', null);
    if(d!=undefined){
       tip.show(d);// give the tip the data it needs to show
    }
  })
  .on('mouseout', function() {
    focus.style('display', 'none');
    tip.hide();
  })

I also changed mousemove function to update the tip

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.select('circle.y')
    .attr('transform', 'translate(' + x(d.date) + ',' + y(d.price) + ')');
  focus.select('.x')
    .attr('transform', 'translate(' + x(d.date) + ',' + y(d.price) + ')')
    .attr('y2', height - y(d.price));
  focus.select('.y')
    .attr('transform', 'translate(' + width * -1 + ',' + y(d.price) + ')')
    .attr('x2', width + width);
  // we need to update the offset so that the tip shows correctly
  tip.offset([y(d.price) - 20, x(d.date) - (width/2)] ) //  [top, left]
  tip.show(d);

}

Also updated the css:

.d3-tip:after {
    box-sizing: border-box;
    display: inline;
    font-size: 15px;
    width: 1%;
    line-height: 4;
    color: rgba(0, 0, 0, 0.8);
    content: "\25BC";
    position: absolute;
    text-align: center;
    pointer-events: none;
    left: 45%;
}

https://jsfiddle.net/mkaran/5t3ycyxs/1/

There could be a better way though.Hope this helps! Good luck!

Upvotes: 3

Related Questions