I'm creating a stacked area chart using d3.js
. Right now I'm not able to figure out on (mousemove)
how to focus separate tooltip on the respective chart curve.
I'm trying to achieve this but for stacked area chart.
I have created a sandbox of my progress here: https://codesandbox.io/s/recursing-carlos-3t0lg
Relevant code:
function mouseMove() {
d3.event.preventDefault();
const mouse = d3.mouse(d3.event.target);
const [xCoord, yCoord] = mouse;
const mouseDate = x.invert(xCoord);
const mouseDateSnap = d3.timeDay.floor(mouseDate);
const bisectDate = d3.bisector((d) => d.date).left;
const xIndex = bisectDate(data, mouseDateSnap, 0);
const mouseHours = data[xIndex].hours;
let demandHours =
data[xIndex].resourceType === "DMND" ? data[xIndex].hours : "";
let supplyHours =
data[xIndex].resourceType === "SPLY" ? data[xIndex].hours : "";
if (x(mouseDateSnap) <= 0) return;
svg
.selectAll(".hoverLine")
.attr("x1", x(mouseDateSnap))
.attr("y1", margin.top)
.attr("x2", x(mouseDateSnap))
.attr("y2", height - margin.bottom)
.attr("stroke", "#69b3a2")
.attr("fill", "#cce5df");
svg
.select(".hoverPoint1")
.attr("cx", x(mouseDateSnap))
.attr("cy", y(supplyHours))
.attr("r", "7")
.attr("fill", "green");
svg
.select(".hoverPoint2")
.attr("cx", x(mouseDateSnap))
.attr("cy", y(demandHours))
.attr("r", "7")
.attr("fill", "yellow");
const isLessThanHalf = xIndex > data.length / 2;
const hoverTextX = isLessThanHalf ? "-0.75em" : "0.75em";
const hoverTextAnchor = isLessThanHalf ? "end" : "start";
svg
.selectAll(".hoverText")
.attr("x", x(mouseDateSnap))
.attr("y", y(mouseHours))
.attr("dx", hoverTextX)
.attr("dy", "-1.25em")
.style("text-anchor", hoverTextAnchor)
.text(
data[xIndex].resourceType === "DMND"
? demandHours + "sec"
: supplyHours + "sec"
);
}
svg.append("line").classed("hoverLine", true);
svg.append("circle").classed("hoverPoint1", true);
svg.append("circle").classed("hoverPoint2", true);
svg.append("text").classed("hoverText", true);
svg
.append("rect")
.attr("fill", "transparent")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
svg.on("mousemove", mouseMove);
In above code I'm creating 2 separate tooltips using selections hoverPoint1
(to show hours SPLY hours) and hoverPoint2
(to show DMND hours)
Expected:
on (mousemove)
, the green circle should move along the curve of blue plotted area AND at same time yellow circle should move along curve of gray plotted area. (please see this as example which shows single area tooltip https://observablehq.com/@elishaterada/simple-area-chart-with-tooltip)
thanks!
function stackedAreaPlot(data) {
// set the dimensions and margins of the graph
const margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3
.select("#stackedAreaPlot")
.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})`);
// Add X axis --> it is a date format
const x = d3
.scaleTime()
.domain(d3.extent(data, (d: any) => new Date(d.date)))
.range([0, width]);
svg
.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));
// Add Y axis
const y = d3.scaleLinear().domain([0, 2000]).range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
let sumstat: any = d3
.nest()
.key((d: any) => {
return d.date;
})
.entries(data);
// stacked groups
const myGroups = ["DMND", "SPLY"];
const stackedData: any = d3
.stack()
// .keys(myGroup)
.keys(myGroups)
.value((data: any, key: any) => {
return data &&
data.values &&
data.values.find((d) => d.resourceType === key) ?
+data.values.find((d) => d.resourceType === key).hours :
null;
})(sumstat);
const color = d3
.scaleOrdinal()
.domain(myGroups)
.range(["#a9b4bc", "#4b6f93"]);
// Show the areas
svg
.selectAll("mylayers")
.data(stackedData)
.enter()
.append("path")
.style("fill", (d: any) => color(d.key))
.attr(
"d",
d3
.area()
.x(function(d: any) {
return x(new Date(d.data.key));
})
.y0(function(d: any) {
return y(+d[0]);
})
.y1(function(d: any) {
return y(+d[1]);
})
);
// tooltip with hover point and line
function mouseMove() {
d3.event.preventDefault();
const mouse = d3.mouse(d3.event.target);
const [xCoord, yCoord] = mouse;
const mouseDate = x.invert(xCoord);
const mouseDateSnap = d3.timeDay.floor(mouseDate);
const bisectDate = d3.bisector((d) => d.date).left;
const xIndex = bisectDate(data, mouseDateSnap, 0);
const mouseHours = data[xIndex].hours;
let demandHours =
data[xIndex].resourceType === "DMND" ? data[xIndex].hours : "";
let supplyHours =
data[xIndex].resourceType === "SPLY" ? data[xIndex].hours : "";
if (x(mouseDateSnap) <= 0) return;
svg
.selectAll(".hoverLine")
.attr("x1", x(mouseDateSnap))
.attr("y1", margin.top)
.attr("x2", x(mouseDateSnap))
.attr("y2", height - margin.bottom)
.attr("stroke", "#69b3a2")
.attr("fill", "#cce5df");
svg
.select(".hoverPoint1")
.attr("cx", x(mouseDateSnap))
.attr("cy", y(supplyHours))
.attr("r", "7")
.attr("fill", "green");
svg
.select(".hoverPoint2")
.attr("cx", x(mouseDateSnap))
.attr("cy", y(demandHours))
.attr("r", "7")
.attr("fill", "yellow");
const isLessThanHalf = xIndex > data.length / 2;
const hoverTextX = isLessThanHalf ? "-0.75em" : "0.75em";
const hoverTextAnchor = isLessThanHalf ? "end" : "start";
svg
.selectAll(".hoverText")
.attr("x", x(mouseDateSnap))
.attr("y", y(mouseHours))
.attr("dx", hoverTextX)
.attr("dy", "-1.25em")
.style("text-anchor", hoverTextAnchor)
.text(
data[xIndex].resourceType === "DMND" ?
demandHours + " hours" :
supplyHours + " hours"
);
}
svg.append("line").classed("hoverLine", true);
svg.append("circle").classed("hoverPoint1", true);
svg.append("circle").classed("hoverPoint2", true);
svg.append("text").classed("hoverText", true);
svg
.append("rect")
.attr("fill", "transparent")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
svg.on("mousemove", mouseMove);
}
var data = [{
date: "2021-12-10T05:00:00.000Z",
hours: 388.5421,
resourceType: "SPLY"
},
{
date: "2021-12-11T05:00:00.000Z",
hours: 357.17214,
resourceType: "SPLY"
},
{
date: "2021-12-12T05:00:00.000Z",
hours: 194.80227,
resourceType: "SPLY"
},
{
date: "2021-12-12T05:00:00.000Z",
hours: 24.5,
resourceType: "DMND"
},
{
date: "2021-12-13T05:00:00.000Z",
hours: 466.68397,
resourceType: "SPLY"
},
{
date: "2021-12-13T05:00:00.000Z",
hours: 48,
resourceType: "DMND"
},
{
date: "2021-12-14T05:00:00.000Z",
hours: 591.11745,
resourceType: "SPLY"
},
{
date: "2021-12-14T05:00:00.000Z",
hours: 62.75,
resourceType: "DMND"
},
{
date: "2021-12-15T05:00:00.000Z",
hours: 631.64018,
resourceType: "SPLY"
},
{
date: "2021-12-15T05:00:00.000Z",
hours: 84.73,
resourceType: "DMND"
},
{
date: "2021-12-16T05:00:00.000Z",
hours: 175.7,
resourceType: "DMND"
},
{
date: "2021-12-16T05:00:00.000Z",
hours: 628.53835,
resourceType: "SPLY"
},
{
date: "2021-12-17T05:00:00.000Z",
hours: 240.24,
resourceType: "DMND"
},
{
date: "2021-12-17T05:00:00.000Z",
hours: 673.66929,
resourceType: "SPLY"
},
{
date: "2021-12-18T05:00:00.000Z",
hours: 256.68,
resourceType: "DMND"
},
{
date: "2021-12-18T05:00:00.000Z",
hours: 635.43202,
resourceType: "SPLY"
},
{
date: "2021-12-19T05:00:00.000Z",
hours: 634.73701,
resourceType: "SPLY"
},
{
date: "2021-12-19T05:00:00.000Z",
hours: 212.44,
resourceType: "DMND"
},
{
date: "2021-12-20T05:00:00.000Z",
hours: 604.94103,
resourceType: "SPLY"
},
{
date: "2021-12-20T05:00:00.000Z",
hours: 209.76,
resourceType: "DMND"
},
{
date: "2021-12-21T05:00:00.000Z",
hours: 618.83085,
resourceType: "SPLY"
},
{
date: "2021-12-21T05:00:00.000Z",
hours: 200.78,
resourceType: "DMND"
},
{
date: "2021-12-22T05:00:00.000Z",
hours: 580.31758,
resourceType: "SPLY"
},
{
date: "2021-12-22T05:00:00.000Z",
hours: 204.78,
resourceType: "DMND"
},
{
date: "2021-12-23T05:00:00.000Z",
hours: 231.15,
resourceType: "DMND"
},
{
date: "2021-12-23T05:00:00.000Z",
hours: 679.8791100000001,
resourceType: "SPLY"
},
{
date: "2021-12-24T05:00:00.000Z",
hours: 654.02485,
resourceType: "SPLY"
},
{
date: "2021-12-24T05:00:00.000Z",
hours: 281.28,
resourceType: "DMND"
}
];
stackedAreaPlot(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="stackedAreaPlot"></div>