Public
Edited
Oct 19, 2024
Fork of Simple D3
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const containerWidth = width; // total width including margins
const containerHeight = 350; // total height including margins
const margin = { top: 30, right: 30, bottom: 70, left: 50 };
const chartWidth = containerWidth - margin.left - margin.right; // chart width after margin-left and margin-right
const chartHeight = containerHeight - margin.top - margin.bottom; // chart height after margin-top and margin-bottom
let chartData = [...data]; // creating a deep clone of the current chartData for use.
// year wise performance indicator color palette
const indicatorColor = [
{
name: "Positive",
color: "#009A82"
},
{
name: "Negative",
color: "#FE3048"
},
{
name: "Average",
color: "#8B8000"
}
];

// create and configure svg element
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, containerWidth, containerHeight])
.attr("width", containerWidth)
.attr("height", containerHeight)
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");

// create a group within the svg element to apply margins
const chartGroup = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

// add xScale
const paddingValue = 0.3;
const xScale = d3
.scaleBand()
.domain(d3.map(chartData, (d) => d.date))
.range([0, chartWidth])
.padding(paddingValue);

// add xAxis
const yearTickSize = 50;
chartGroup
.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${chartHeight})`)
.call(
d3
.axisBottom(xScale)
.tickFormat((d, i) => {
return chartData[i].displayName;
})
.tickSizeOuter(yearTickSize)
);

// add yScale
const yScale = d3
.scaleLinear()
.domain([0, d3.max(chartData, (d) => d.value)])
.range([chartHeight, 0]);

// add y-axis
chartGroup
.append("g")
.attr("class", "y-axis")
.call(
d3
.axisLeft(yScale)
.tickFormat((d) => abbreviateNumber(d))
.tickSizeOuter(0)
)
.call((g) => g.select(".domain").remove())
// yAxis vertical line using svg
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", chartWidth)
.attr("stroke-opacity", 0.2)
);

// add yLabel Text
svg
.append("text")
.attr("x", 0)
.attr("y", margin.top)
.attr("dy", -10)
.attr("fill", "#2A457A")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.attr("font-size", 10)
.text("Rupees (Cr.)");

// add bar as rect element
const bar = chartGroup
.selectAll(".bar")
.data(chartData)
.join("rect")
.attr("class", "bar")
.attr("x", (d) => xScale(d.date))
.attr("y", (d) => yScale(0))
.attr("width", xScale.bandwidth())
.attr("height", (d) => chartHeight - yScale(0))
.attr("displayName", (d) => d.displayName)
.attr("date", (d) => d.date)
.attr("value", (d) => d.value)
.attr("color", (d, i) => {
return yearlyPerformanceData[d.year].indicator > 0
? indicatorColor[0].color
: yearlyPerformanceData[d.year].indicator < 0
? indicatorColor[1].color
: indicatorColor[2].color;
})
.attr("fill", "#263252");

// add animation to bar
svg
.selectAll(".bar")
.transition()
.duration(800)
.attr("y", (d) => yScale(d.value))
.attr("height", (d) => chartHeight - yScale(d.value))
.delay((d, i) => i * 100);

// filter quater end from chart data
const quarterEnd = d3.filter(chartData, (d) => d.quarter === 4);

// width of gap between two bar
const widthOfGapBetweenBar = xScale.step() * xScale.paddingInner();

// add line for year tick mark
chartGroup
.selectAll(".year-line")
.data(quarterEnd)
.join("line")
.attr("class", "year-line")
.attr(
"x1",
(d) => xScale(d.date) + xScale.bandwidth() + widthOfGapBetweenBar / 2
)
.attr(
"x2",
(d) => xScale(d.date) + xScale.bandwidth() + widthOfGapBetweenBar / 2
)
.attr("y1", chartHeight)
.attr("y2", (d) => chartHeight + yearTickSize)
.attr("stroke", "black")
.attr("fill", "none")
.attr("sharp-rendering", "crispEdges");

// add year text mark to xAxis ticks
chartGroup
.selectAll(".year-text")
.data(quarterEnd)
.join("text")
.attr("class", "year-text")
.attr(
"x",
(d) => xScale(d.date) + xScale.bandwidth() + widthOfGapBetweenBar / 2
)
.attr("y", chartHeight + yearTickSize * 1.3)
.attr("fill", "#2A457A")
.attr("font-weight", "bold")
.attr("font-size", "12px")
.attr("text-anchor", "middle")
.text((d) => +d.year + 1);

// create outerTick year object
const outerTickYear = [
{
date: `${+chartData[0].year}-12`,
year: `${+chartData[0].year}`,
x: 0,
y: chartHeight + yearTickSize * 1.3
},
{
date: `${+chartData[chartData.length - 1].year + 1}-03`,
year: `${+chartData[chartData.length - 1].year + 1}`,
x: chartWidth,
y: chartHeight + yearTickSize * 1.3
}
];

// add year text to xAxis ticks
chartGroup
.selectAll(".outer-tick-year-text")
.data(outerTickYear)
.join("text")
.attr("class", "outer-tick-year-text")
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
.attr("fill", "#2A457A")
.attr("font-weight", "bold")
.attr("font-size", "12px")
.attr("text-anchor", "middle")
.text((d) => d.year);

// find unique year
const uniqueYear = [...new Set(d3.map(chartData, (d) => d.year))];

// add rect element below xAxis for year wise performance indicator using color
chartGroup
.selectAll(".color-indicator")
.data(uniqueYear)
.join("rect")
.attr("class", "color-indicator")
.attr("x", (d) => {
const year = d3.filter(chartData, (el) => el.year === d);
return xScale(year[0].date) + xScale.bandwidth() / 2;
})
.attr("y", (d) => {
return chartHeight + yearTickSize * 0.5;
})
.transition()
.duration(800)
.attr("width", (d) => {
const year = d3.filter(chartData, (el) => el.year === d);
return xScale(year[year.length - 1].date) - xScale(year[0].date);
})
.attr("height", 4)
.attr("rx", 2)
.attr("ry", 2)
.attr("fill", (d, i) => {
return yearlyPerformanceData[d].indicator > 0
? indicatorColor[0].color
: yearlyPerformanceData[d].indicator < 0
? indicatorColor[1].color
: indicatorColor[2].color;
});

chartGroup
.selectAll(".legend-text")
.data(uniqueYear)
.join("text")
.attr("class", "legend-text")
.attr("x", (d) => {
const year = d3.filter(chartData, (el) => el.year === d);
return xScale(year[0].date) + xScale.bandwidth() / 2;
})
.attr("y", (d) => {
return 16 + chartHeight + yearTickSize * 0.5;
})
.attr("font-weight", "bold")
.transition()
.duration(800)
.text((d, i) => {
return yearlyPerformanceData[d].indicator > 0
? indicatorColor[0].name
: yearlyPerformanceData[d].indicator < 0
? indicatorColor[1].name
: indicatorColor[2].name;
});

// create tooltip container
const tooltipContainer = d3
.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("padding", "10px")
.style("font", "14px sans-serif")
.style("background", "lightsteelblue")
.style("border-radius", "4px")
.style("color", "#444");

// add tooltip on mousemove
bar.on("mouseover", function (e, d, i) {
// get value from DOM
const displayName = d3.select(this).attr("displayName");
const date = d3.select(this).attr("date");
const value = d3.select(this).attr("value");
const color = d3.select(this).attr("color");
d3.select(this).transition().attr("fill", color);
tooltipContainer
.html(
`Date:${displayName} (${date})<br/>Value: <strong>${value} K Cr.</strong>`
)
.style("visibility", "visible");
});
bar.on("mousemove", function (event) {
tooltipContainer
.style("left", event.pageX + 10 + "px")
.style("top", event.pageY + 10 + "px");
});
// hide tooltip in mouseout
bar.on("mouseout", function () {
tooltipContainer.html(``).style("visibility", "hidden");
d3.select(this).transition().attr("fill", "#263252");
});

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Type Markdown, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more