Published
Edited
Sep 12, 2021
1 fork
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
//Diamond dataset
diamond = Object.assign(d3.csvParse(await FileAttachment("diamonds.csv").text(), d3.autoType))
Insert cell
Insert cell
Insert cell
Insert cell
//Calculates the count of each cut-type in each color
diamondData = {
let arrayData = []
let c = 0;
for (const i in colorNames){
let count = {};
count['color'] = colorNames[i];
diamond.filter(c => c.color === colorNames[i]).forEach(function(d) {
if (!count[d.cut]) {
count[d.cut] = 0;
}
count[d.cut]++;
});
arrayData[c++] = count;
}
return arrayData
}
Insert cell
cutTypes = Object.keys(diamondData[0]).slice(1)
Insert cell
Insert cell
x0 = d3.scaleBand()
.domain(diamondData.map(d => d["color"]))
.rangeRound([margin.left, width - margin.right])
.paddingInner(0.1) //makes a noticiable gap between each bar-groups
Insert cell
x1 = d3.scaleBand() //inner axis
.domain(cutTypes)
.rangeRound([0, x0.bandwidth()]) //.bandwidth() gives the width of x0 and this makes sure that the extent of x1-scale is restricted within the group
.padding(0.05) //sets gap between inner bars of each group
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(diamondData, d => d3.max(cutTypes, cut => d[cut]))]).nice() //Here the right component of domain finds the maximum cut-count of each color and choses the maximum among them to set the upper limit of domain.
.range([height - margin.bottom, margin.top])
Insert cell
//can put any color of one's choice
color = d3.scaleOrdinal()
.domain([ "Very Good", "Good", "Premium", "Ideal", "Fair"])
.range(["rgb(203, 203, 227)","rgb(160, 182, 215)","rgb(114, 162, 202)","rgb(65, 137, 186)","rgb(35, 89, 135)"])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x0))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 45)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Color →"))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(null, "s")) //formats the large numbers to nicer Million or Billions...
.call(g => g.select(".domain")) //remove() removes the vertical line of the y-axis
.call(g => g.select(".tick:last-of-type text").clone()
//The :last-of-type selector allows you to move the position to the last occurence of yAxis label (or tick), while clone() makes a copy of the selected elements and inserts the duplicate of the selection so that the upper limit of the axis-scale is properly inserted
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("↑ Cuts Counts"))
Insert cell
legend = svg => {
const g = svg
.attr("transform", `translate(${width},25)`)
.attr("text-anchor", "end")
.attr("font-size", 10)

.selectAll("g")
.data(color.domain())
.join("g")
.attr("transform", (d, i) => `translate(0,${i * 20})`); //puts legends seperately from each other (since the width of the rectangular label is 19, its nice option to start another from 20px offset

g.append("rect")
.attr("x", -19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", color);

g.append("text")
.attr("x", -24)
.attr("y", 9.5)
.attr("dy", "0.35em") //em is simply the font size, it defines the proportion of the letter width and height with respect to the point size of the current font
.text(d => d); //displays each component of color.domain()
}
Insert cell
legendTitle = g => g.append("text")
.attr("transform", `translate(${width},15)`)
.attr("text-anchor", "end")
.attr("font-size", 15)
.attr("font-weight", "bold")
.text("Cuts")
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height));

svg.append("g")
.selectAll("g")
.data(diamondData)
.join("g")
.attr("transform", d => `translate(${x0(d["color"])},0)`) //puts each groups of bars in distinct positions
.selectAll("rect")
.data(d => cutTypes.map(cut => ({cut, count: d[cut], color: d.color}))) //extracts the cut count for each color
.join("rect")
.attr("x", d => x1(d.cut))
.attr("y", d => y(d.count))
.attr("width", x1.bandwidth()) //width of each bar
.attr("height", d => y(0) - y(d.count))
.attr("fill", d => color(d.cut))
.append("title") //similar to tooltip feature of vegalite; it shows the detail on the plot on mouse hovering
.text(d => `For ${d.color}, ${d.cut} = ${d.count}`);

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

svg.append("g")
.call(legend);
svg.append("g")
.call(legendTitle);

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
xStacked = d3.scaleBand()
.domain(diamondData.map(d => d.color))
.range([margin.left, width - margin.right])
.padding(0.1)
Insert cell
xAxisStacked = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xStacked)) //substitute x by xStacked
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 45)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Color →"))
Insert cell
Insert cell
yStacked = d3.scaleLinear()
.domain([0, d3.max(diamondData, d => d3.sum(cutTypes, count => d[count]))]).nice() //modified
.range([height - margin.bottom, margin.top])
Insert cell
yAxisStacked = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yStacked).ticks(null, "s")) //substitute y by yStacked
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("↑ Cuts Counts"))
Insert cell
Insert cell
stackedData = d3.stack()
.keys(Object.keys(diamondData[0]).slice(1))
(diamondData)
.map(d => (d.forEach(v => v.key = d.key), d))
Insert cell
Insert cell
import {legend as legendStacked} from "@d3/color-legend"
Insert cell
Insert cell
stackedChart = {
const svg = d3.select(DOM.svg(width, height));

svg.append("g")
.selectAll("g")
.data(stackedData)
.join("g")
.attr("fill", d => color(d.key))
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", (d, i) => xStacked(d.data.color))
.attr("y", d => yStacked(d[1]))
.attr("height", d => yStacked(d[0]) - yStacked(d[1]))
.attr("width", xStacked.bandwidth())
.append("title")
.text(d => `For ${d.data.color}, ${d.key} = ${d.data[d.key]}`);

svg.append("g")
.call(xAxisStacked);

svg.append("g")
.call(yAxisStacked);

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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