Reputation: 469
Edit: Here's a fiddle of the problem.
https://jsfiddle.net/8tpz5s9w/1/
I am having an issue with implementing a simple D3 tooltip.
I have a multiline chart and it works well for ONE of the lines but only for the FIRST point of my second line. The X values are equivalent for all lines, just different y-values.
Tips? Portion of relevant code:
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
points.selectAll('.point')
.data(function(d) {
console.log('.point d is: ', d);
d.values.forEach(function(kv) {
console.log('kv is:', kv);
return kv.name = d.name;
})
return d.values;
})
.enter()
.append('circle')
.attr('circleId', function(d, i) {
return 'circleId-'+(i+1);
})
.attr('cx', function(d) {
return x(d.Period);
})
.attr('cy', function(d) {
return y(d.Value);
})
.attr('r', function(d) {
return dotRadius()
})
.on("mouseover", function(d) {
console.log('mouseover d is :', d);
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.name + ': ' + d.Value)
.style("left", (d3.event.pageX - 24) + "px")
.style("top", (d3.event.pageY - 56) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
Entire code minus CSS:
jQuery(document).ready(function ($) {
var margin = {top: 20, right: 30, bottom: 60, left: 45},
width = 375 - margin.left - margin.right,
height = 225 - margin.top - margin.bottom,
dotRadius = function() { return 3 };
// dispatch = d3.dispatch("pointMouseover", "pointMouseout");
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')
.tickFormat(d3.time.format('%b'));
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// This is a function that determines the colours of the lines drawn, up to 10.
var color = d3.scale.category10();
// This is used to format the time for our data.
var formatTime = d3.time.format("%Y-%m-%d");
var line = d3.svg.line()
.x(function(d) { return x(d.Period); })
.y(function(d) { return y(d.Value); });
var svg = d3.select("#pipeline-chart-render")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
// This separates the data into the lines we want, although the data is stored
// In the same original object.
color.domain(d3.keys(data[0].values[0]).filter(function(key) {
if (key === 'Amount'
|| key === 'Quantity') {
return key
}
}));
// This returns the data into two separate objects which can be graphed.
// In this case, Amount and Quantity.
var datasets = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
Period: formatTime.parse(d.values[0].Time),
Value: +d.values[0][name]};
})
};
});
console.log('datasets is: ', datasets);
// set the minYDomainValue to zero instead of letting it be a lingering magic number.
var minDomainValue = 0
var minDate = d3.min(datasets, function(d0){
return d3.min(d0.values, function(d1){
return d1.Period;
})
}),
maxDate = d3.max(datasets, function(d0){
return d3.max(d0.values, function(d1){
return d1.Period;
})
});
x.domain([minDate, maxDate]);
y.domain([
minDomainValue,
d3.max(datasets, function(c) { return d3.max(c.values, function(v) { return v.Value; }); })
])
// Append the x-axis class and move axis around.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append('text')
.attr('x', 150)
.attr('y', 36)
.style('text-anchor', 'middle')
.text('2015');
// Append the y-axis class.
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -30)
.attr('x', -30)
.style("text-anchor", "end")
.text("Quantity/Amount");
// The following is for defining the area BETWEEN graphs.
var areaAboveQuantity = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(0);
var areaBelowQuantity = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(height);
var areaAboveAmount = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(0);
var areaBelowAmount = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(height);
var pipeline = svg.selectAll('.pipeline')
.data(datasets);
pipeline.enter()
.append('g')
.attr('class', 'pipeline');
pipeline.append('path')
.attr('class', 'line')
.attr('id', function(d, i) {
return 'pipeline-'+(i+1);
})
.attr('d', function(d) { console.log('line d is: ', d); return line(d.values); })
.attr("data-legend",function(d) { return d.name})
.style("stroke", function(d) { return color(d.name); })
pipeline.exit().remove()
// Rendering the points on the graph.
var points = svg.selectAll('.pipelinePoint')
.data(datasets);
points
.enter()
.append('g')
.attr('class', 'pipelinePoint');
points.selectAll('.point')
.data(function(d) {
console.log('.point d is: ', d);
d.values.forEach(function(kv) {
console.log('kv is:', kv);
return kv.name = d.name;
})
return d.values;
})
.enter()
.append('circle')
.attr('circleId', function(d, i) {
return 'circleId-'+(i+1);
})
.attr('cx', function(d) {
return x(d.Period);
})
.attr('cy', function(d) {
return y(d.Value);
})
.attr('r', function(d) {
return dotRadius()
})
.on("mouseover", function(d) {
console.log('mouseover d is :', d);
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.name + ': ' + d.Value)
.style("left", (d3.event.pageX - 24) + "px")
.style("top", (d3.event.pageY - 56) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
var defs = svg.append('defs');
defs.append('clipPath')
.attr('id', 'clip-quantity')
.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaAboveQuantity(d[1].values);
});
defs.append('clipPath')
.attr('id', 'clip-amount')
.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaAboveAmount(d[0].values);
});
svg.append('path')
.datum(datasets)
.attr('class', 'area')
.attr('d', function(d) {
return areaBelowQuantity(d[1].values)
});
// Quantity IS ABOVE Amount
svg.append('path')
.datum(datasets)
.attr('d', function(d) {
areaBelowQuantity(d[1].values);
})
.attr('clip-path', 'url(#clip-amount)')
.style('fill', 'steelblue')
.style('opacity', '0.2');
// Amount IS ABOVE Quanity
svg.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaBelowAmount(d[0].values);
})
.attr('clip-path', 'url(#clip-quantity)')
.style('fill', 'steelblue')
.style('opacity', '0.2')
// .on("mouseover", function(d) {
// console.log('mouseover d is :', d);
// div.transition()
// .duration(200)
// .style("opacity", .9);
// div.html('Hello')
// .style("left", (d3.event.pageX) + "px")
// .style("top", (d3.event.pageY - 28) + "px");
// })
// .on("mouseout", function(d) {
// div.transition()
// .duration(500)
// .style("opacity", 0);
// });
legend1 = svg.append("g")
.attr("class","legend")
.attr("transform","translate(-20,180)")
.style("font-size","12px")
.call(d3Legend)
})
var d3Legend = function(g) {
g.each(function() {
var g= d3.select(this),
items = {},
svg = d3.select(g.property("nearestViewportElement")),
legendPadding = g.attr("data-style-padding") || 5,
lb = g.selectAll(".legend-box").data([true]),
li = g.selectAll(".legend-items").data([true])
lb.enter().append("rect").classed("legend-box",true)
li.enter().append("g").classed("legend-items",true)
svg.selectAll("[data-legend]").each(function() {
var self = d3.select(this)
items[self.attr("data-legend")] = {
pos : self.attr("data-legend-pos") || this.getBBox().y,
color : self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke")
}
})
items = d3.entries(items).sort(function(a,b) { return a.value.pos-b.value.pos})
li.selectAll("text")
.data(items,function(d) { return d.key})
.call(function(d) { d.enter().append("text")})
.call(function(d) { d.exit().remove()})
.attr("y",function(d,i) { return i+"em"})
.attr("x","1em")
.text(function(d) { ;return d.key})
li.selectAll("circle")
.data(items,function(d) { return d.key})
.call(function(d) { d.enter().append("circle")})
.call(function(d) { d.exit().remove()})
.attr("cy",function(d,i) { return i-0.25+"em"})
.attr("cx",0)
.attr("r","0.4em")
.style("fill",function(d) { return d.value.color})
// Reposition and resize the box
var lbbox = li[0][0].getBBox()
lb.attr("x",(lbbox.x-legendPadding))
.attr("y",(lbbox.y-legendPadding))
.attr("height",(lbbox.height+2*legendPadding))
.attr("width",(lbbox.width+2*legendPadding))
})
return g
};
var data = [
{
key: 1,
values: [
{
Amount: 33,
Quantity: 22,
Time: '2015-01-01'
}
]
},
{
key: 2,
values: [
{
Amount: 52,
Quantity: 20,
Time: '2015-02-01'
}
]
},
{
key: 3,
values: [
{
Amount: 63,
Quantity: 30,
Time: '2015-03-01'
}
]
},
{
key: 4,
values: [
{
Amount: 92,
Quantity: 60,
Time: '2015-04-01'
}
]
},
{
key: 5,
values: [
{
Amount: 50,
Quantity: 29,
Time: '2015-05-01'
}
]
},
{
key: 6,
values: [
{
Amount: 53,
Quantity: 25,
Time: '2015-06-01'
}
]
},
{
key: 7,
values: [
{
Amount: 46,
Quantity: 12,
Time: '2015-07-01'
}
]
},
{
key: 8,
values: [
{
Amount: 52,
Quantity: 15,
Time: '2015-08-01'
}
]
},
{
key: 9,
values: [
{
Amount: 55,
Quantity: 20,
Time: '2015-09-01'
}
]
},
{
key: 10,
values: [
{
Amount: 35,
Quantity: 17,
Time: '2015-10-01'
}
]
},
{
key: 11,
values: [
{
Amount: 80,
Quantity: 45,
Time: '2015-11-01'
}
]
},
{
key: 12,
values: [
{
Amount: 64,
Quantity: 24,
Time: '2015-12-01'
}
]
}
]
Upvotes: 1
Views: 351
Reputation: 10612
Its due to your paths getting in the way of being able to hover over the nodes. You need to set pointer-events
to none
on this like so :
I have created a class - noPointers :
.noPointers{
pointer-events:none;
}
And applied this to the path thats in the way (line 369) :
// Amount IS ABOVE Quanity
svg.append('path')
.datum(datasets).attr('class', 'noPointers')//added class here
Updated fiddle : https://jsfiddle.net/reko91/8tpz5s9w/3/
Upvotes: 1