D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
ChandrakantThakkarDigiCorp
Full window
Github gist
Stack Bar Chart With Custom Tool Tip D3 V4
<!DOCTYPE html> <meta charset="utf-8"> <head> <title>Chandrakant Bharatkumar Thakkar</title> <link rel="stylesheet" href="./styles.css"> </head> <body> <div id="charts" style="width:800px;"> <div class="cumulative-trend" style="width:800px;height:500px"> <svg width="800" height="500" /> </div> </div> </div> <a href="https://stackoverflow.com/users/7430694/chandrakant-thakkar" style="position: absolute;top: 87%;left: 77%;" target="_blank"> <img src="https://stackoverflow.com/users/flair/7430694.png" width="208" height="58" alt="profile for Chandrakant Thakkar at Stack Overflow, Q&A for professional and enthusiast programmers" title="profile for Chandrakant Thakkar at Stack Overflow, Q&A for professional and enthusiast programmers"> </a> <script src="./jquery-latest.min.js"></script> <script src="./d3.v4.min.js"></script> <script src="./constant.js"></script> <script> var svg = d3.select("svg"), width = 800, height = 440; const svgRef = "svg"; const mainDivRef = ".cumulative-trend"; const margin = { top: 90, right: 20, left: 50, bottom: 50 }; const ticksSize = 6; const minMaxValue = d3.extent(preparedChartData.map(data => data.value + data.pastValue)); const { minValue, maxValue } = calculateMinMaxValue(minMaxValue[0], minMaxValue[1], false, ticksSize); const minMaxValues = { minValue: Math.round(minValue), maxValue: Math.round(maxValue) }; let binSize = (minMaxValues.maxValue - minMaxValues.minValue) / (ticksSize - 1); binSize = Math.round(binSize); const tickValues = []; let currentValue = minMaxValues.minValue; while (currentValue <= minMaxValues.maxValue) { tickValues.push(currentValue); currentValue = currentValue + binSize; } const dataTips = [...preparedChartData].reverse().filter((d, index) => { d.index = index; return d.dataTip != null; }); const updatedMargin = { ...margin }; updatedMargin.top = (dataTips.length > 0 && updatedMargin.top < 50) ? 50 : updatedMargin.top; const yScale = d3.scaleLinear().domain([0, minMaxValues.maxValue]) .range([(height) - (updatedMargin.bottom + updatedMargin.top), 0]); var yAxis = d3.axisLeft(yScale).ticks(ticksSize).tickFormat(d => d); d3.select("svg") .append("g") .attr("class", "y-axis") .attr("transform", "translate(" + updatedMargin.left + "," + updatedMargin.top + ")").call(yAxis); const ticksCalculationData = getTicksCalculation(preparedChartData[0].date, preparedChartData[preparedChartData.length - 1].date, "day", "0"); const distinctTicks = ticksCalculationData.distinctTicks.map(d => d.data); const xScale = d3.scaleBand() .domain(distinctTicks) .range([0, width - (updatedMargin.left + updatedMargin.right)]); const groupKey = ["value", "pastValue"]; const layersActual = d3 .stack() .keys(groupKey) .offset(d3.stackOffsetDiverging)(preparedChartData); const xAxisG = d3.select("svg") .append("g") .attr("transform", "translate(" + updatedMargin.left + "," + (height - (updatedMargin.top - 40)) + ")") xAxisG.append("g") .attr("class", "range-slider-axis"); CustomXAxis(xScale, width - (updatedMargin.left + updatedMargin.right), ticksCalculationData.distinctTicks, height - (updatedMargin.top + updatedMargin.bottom), preparedChartData); let tooltipData = ""; let topLeftPositionOfTooltip = { topPosition: 100, leftPosition: 100 }; const onMouseEnterOnBar = (d) => { let data = d3.event.target.getBBox(); data = { x: data.x, y: data.y, width: data.width, height: data.height, ...d }; tooltipData = ( '<div className="tooltip-table">\ <div className="tooltip-table-row">\ <span className="header">Current Value:</span>\ <span>'+ data.data.value + '</span>\ </div>\ <div className="tooltip-table-row">\ <span className="header">Past Value:</span>\ <span>'+ data.data.pastValue + '</span>\ </div>\ </div>' ); topLeftPositionOfTooltip = { topPosition: data.topY + updatedMargin.top, leftPosition: data.x + xScale.bandwidth() / 2 + updatedMargin.left }; callShowTooltip(topLeftPositionOfTooltip.topPosition, topLeftPositionOfTooltip.leftPosition, tooltipData, mainDivRef); } const onMouseOutOnBar = () => { callHideTooltip(mainDivRef); } const stackBarG = d3.select("svg") .append("g") .attr("class", "stack-bar") .attr("transform", "translate(" + updatedMargin.left + "," + updatedMargin.top + ")").call(yAxis); layersActual.length > 0 && layersActual[0].forEach((dataPoint, index) => { const pastValue = [0, layersActual[1][index]["1"] - layersActual[1][index]["0"]]; const currentValue = [pastValue[1], pastValue[1] + dataPoint["1"] - dataPoint["0"]]; stackBarG.append("g") .append("rect") .datum({ topY: yScale(currentValue[1]), data: dataPoint.data }) .classed('past-value', true) .attr("x", xScale(ticksCalculationData.distinctTicks[index].data) + 1.5) .attr("y", yScale(pastValue[1])) .style("height", yScale(pastValue[0]) - yScale(pastValue[1])) .style("width", xScale.bandwidth() - 1.5) .style("fill", "#ccc") .style("stroke", "transparent") .on("mouseover", onMouseEnterOnBar) .on("mouseout", onMouseOutOnBar); stackBarG.append("g") .append("rect") .datum({ topY: yScale(currentValue[1]), data: dataPoint.data }) .classed('actual-value', true) .attr("x", xScale(ticksCalculationData.distinctTicks[index].data) + 1.5) .attr("y", yScale(currentValue[1])) .style("height", yScale(currentValue[0]) - yScale(currentValue[1])) .style("width", xScale.bandwidth() - 1.5) .style("fill", "#ccc") .style("stroke", "transparent") .on("mouseover", onMouseEnterOnBar) .on("mouseout", onMouseOutOnBar); }) </script>