Public
Edited
Nov 25, 2024
Fork of Simple D3
1 star
Insert cell
Insert cell
chart = {
//const width = 928; // uncomment for responsive width
const height = 360;
const marginTop = 25;
const marginRight = 40;
const marginBottom = 45
; const marginLeft = 40;

const ramp_semi_height = 22;

const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

// Gradient breaks
const breaks = [
{ start: 0, end: 15, color: "#00BB00" },
{ start: 15, end: 30, color: "#FFDF5F" },
{ start: 30, end: 45, color: "#FF8A0F" },
{ start: 45, end: 75, color: "#EE0000" },
{ start: 75, end: 100, color: "#555" }
];

// Sample data
let recipes = [
{ name: "Tartiflette", co2: 18.4 },
{ name: "Hamburger", co2: 64.3 },
{ name: "Chili con carne", co2: 46.4 },
{ name: "Salade composée", co2: 9.9 },
{ name: "Dahl de lentilles", co2: 8.6 },
{ name: "Poisson pané", co2: 18.2 },
{ name: "Saucisse frites", co2: 36.2 },
{ name: "Couscous poulet", co2: 24.3 },
{ name: "Pâtes à la carbonara", co2: 13.0 }
];
recipes.sort((x, y) => d3.ascending(x.co2, y.co2));

// Scales
const x_scale = d3
.scaleLinear()
.domain([0, 100])
.range([marginLeft, width - marginRight]);
const y_scale = d3
.scaleLinear()
.domain([-50, 50])
.range([height - marginBottom, marginTop]);
const xAxis = d3.axisBottom(x_scale).tickFormat((d) => `${d}%`);

const ramp_top = y_scale(0) - ramp_semi_height;
const ramp_bottom = y_scale(0) + ramp_semi_height;

// Gradients
let svg_defs = svg.append("defs");
let grad = svg_defs.append("linearGradient").attr("id", "grad");
grad
.selectAll("stop")
.data(breaks)
.join("stop")
.attr("offset", (d) => `${d.start}%`)
.attr("stop-color", (d) => d.color);
let lines_grad_top = svg_defs
.append("linearGradient")
.attr("id", "linesGradTop")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", 0)
.attr("y2", 1);
lines_grad_top
.append("stop")
.attr("offset", "0%")
.attr("stop-color", "white")
.attr("stop-opacity", "1");
lines_grad_top
.append("stop")
.attr("offset", "35%")
.attr("stop-color", "white")
.attr("stop-opacity", "0");
let lines_grad_bottom = svg_defs
.append("linearGradient")
.attr("id", "linesGradBottom")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", 0)
.attr("y2", 1);
lines_grad_bottom
.append("stop")
.attr("offset", "65%")
.attr("stop-color", "white")
.attr("stop-opacity", "0");
lines_grad_bottom
.append("stop")
.attr("offset", "100%")
.attr("stop-color", "white")
.attr("stop-opacity", "1");

// Ramp
svg
.append("rect")
.attr("x", x_scale(0))
.attr("y", ramp_top)
.attr("rx", 10)
.attr("width", x_scale(100) - x_scale(0))
.attr("height", ramp_semi_height * 2)
.attr("fill", "url(#grad)");

// Ramp percentages
svg
.append("g")
.selectAll("text")
.data([10, 20, 30, 40, 50, 60, 70, 80, 90])
.join("text")
.attr("x", (d) => x_scale(d))
.attr("y", y_scale(0))
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.style("fill", "white")
.text((d) => `${d}%`);

// Black recipe lines
svg
.append("g")
.selectAll("line")
.data(recipes)
.join("line")
.attr("x1", (d) => x_scale(d.co2))
.attr("x2", (d) => x_scale(d.co2))
.attr("y1", (_, i) => (i % 2 == 0 ? ramp_top : ramp_bottom))
.attr("y2", (_, i) => (i % 2 == 0 ? ramp_top - 10 : ramp_bottom + 10))
.attr("stroke", "black");

// Dotted white recipe lines
svg
.append("g")
.selectAll("rect")
.data(recipes)
.join("rect")
.attr("x", (d) => x_scale(d.co2) - 1)
.attr("y", ramp_top)
.attr("height", ramp_semi_height * 2)
.attr("width", 1)
//.attr("stroke", "url(#linesGrad)")
.attr("fill", (_, i) =>
i % 2 == 0 ? "url(#linesGradTop)" : "url(#linesGradBottom)"
)
.attr("stroke-width", 0);
//.attr("stroke-dasharray", "5,5");

// Recipe circles
svg
.append("g")
.selectAll("circle")
.data(recipes)
.join("circle")
.attr("cx", (d) => x_scale(d.co2))
.attr("cy", (d, i) => (i % 2 == 0 ? ramp_top - 15 : ramp_bottom + 15))
.attr("r", 6)
.attr("fill", "white")
.attr("stroke", "#55BBEE")
.attr("stroke-width", 5);

// Recipe texts
svg
.append("g")
.selectAll("text")
.data(recipes)
.join("text")
.attr("x", (d, i) => (i % 2 == 0 ? x_scale(d.co2) : x_scale(d.co2) + 5))
.attr("y", (d, i) => (i % 2 == 0 ? ramp_top - 30 : ramp_bottom + 38))
.attr(
"transform",
(d, i) =>
`rotate(-35, ${i % 2 == 0 ? x_scale(d.co2) : x_scale(d.co2) + 5}, ${
i % 2 == 0 ? ramp_top - 30 : ramp_bottom + 38
})`
)
.attr("text-anchor", (_, i) => (i % 2 == 0 ? "start" : "end"))
.append("tspan")
.text((d) => `${d.name}`);
// .append("tspan")
// .attr("x", (d) => x_scale(d.co2) + 15)
// .attr("dy", "1.2rem")
// .style("font-weight", "bold")
// .text((d) => `${d.co2}%`);

// Bottom label
svg
.append("text")
.attr("x", width / 2)
.attr("y", height - 10)
.attr("text-anchor", "middle")
.attr("font-style", "italic")
.text("Pourcentage du budget carbone quotidien total par personne");

return svg.node();
}
Insert cell
Insert cell
barchart = {
const data = barchart_data.map((d) => {
d.name = d.name.replace(/\s*\[.*\]/g, "");
return d;
});

const max_length = d3.max(data.map((d) => d.name.length));
const max_co2 = d3.max(data.map((d) => d.co2));

const barHeight = 35;
const marginTop = 35;
const marginRight = 40;
const marginBottom = 30;
const marginLeft = max_length * 7;
const width = 1100;
const height =
Math.ceil((data.length + 0.1) * barHeight) + marginTop + marginBottom;

// Create the SVG container.
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

// Scales
const x = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.co2)])
.range([marginLeft, width - marginRight]);

const y = d3
.scaleBand()
.domain(d3.sort(data, (d) => -d.co2).map((d) => d.name))
.rangeRound([marginTop, height - marginBottom])
.padding(0.1);

// Values format
const format = d3.format(",.2f");

// Bars
svg
.append("g")
.attr("fill", "#C2185B")
.selectAll()
.data(data)
.join("rect")
.attr("x", x(0))
.attr("y", (d) => y(d.name))
.attr("width", (d) => x(d.co2) - x(0))
.attr("height", y.bandwidth());

// Add values to bars
svg
.append("g")
.attr("fill", "white")
.attr("text-anchor", "end")
.attr("font-size", "0.9em")
.selectAll()
.data(data)
.join("text")
.attr("x", (d) => x(d.co2))
.attr("y", (d) => y(d.name) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("dx", -4)
.text((d) => format(d.co2))
.call((text) =>
text
.filter((d) => x(d.co2) - x(0) < 50) // short bars
.attr("dx", +4)
.attr("fill", "black")
.attr("text-anchor", "start")
);

// Axes
// svg
// .append("g")
// .attr("transform", `translate(0,${marginTop})`)
// .call(d3.axisTop(x))
// .attr("font-size", "0.8em")
// .call((g) => g.select(".domain").remove());
svg
.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickSizeOuter(0))
.attr("font-size", "0.8em");
svg
.append("g")
.append("text")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "0.8em")
.attr("x", x(max_co2 / 2))
.attr("y", 20)
.text("Émissions de CO2 (en kg CO2e)");

return svg.node();
}
Insert cell
barchart_plot = {
const data = barchart_data.map((d) => {
d.short_name = d.name.replace(/\s*\[.*\]/g, "");
return d;
});

const n_bars = data.length;
const max_length = d3.max(data.map((d) => d.name.length));
const max_co2 = d3.max(data.map((d) => d.co2));

if (width > 600) {
return Plot.plot({
marginLeft: max_length * 6,
marginRight: 30,
x: { axis: null },
y: { label: null },
marks: [
Plot.barX(data, {
y: "short_name",
x: "co2",
tip: true,
sort: { y: "-x" },
fill: "#C2185B"
}),
Plot.text(data, {
y: "short_name",
x: "co2",
fill: "black",
text: (d) => d.co2.toFixed(2),
dx: 15
})
]
});
} else {
return Plot.plot({
width: n_bars * 50,
marginBottom: max_length * 4 + 110,
marginTop: 30,
marginRight: 110,
y: { axis: null },
x: { label: null, tickRotate: 40 },
marks: [
Plot.barY(data, {
x: "short_name",
y: "co2",
tip: true,
sort: { x: "-y" },
fill: "#C2185B"
}),
Plot.text(data, {
x: "short_name",
y: "co2",
fill: "black",
text: (d) => d.co2.toFixed(2),
dy: -10
})
]
});
}
}
Insert cell
barchart_plot2 = {
const data = barchart_data.map((d) => {
d.short_name = d.name.replace(/\s*\[.*\]/g, "");
return d;
});

const n_bars = data.length;
const max_length = d3.max(data.map((d) => d.name.length));
const max_co2 = d3.max(data.map((d) => d.co2));

return Plot.plot({
width: n_bars * 50,
marginBottom: max_length * 4,
marginTop: 30,
y: { axis: null },
x: { label: null, tickRotate: 40 },
marks: [
Plot.barY(data, {
x: "short_name",
y: "co2",
tip: true,
sort: { x: "-y" },
fill: "#C2185B"
}),
Plot.text(data, {
x: "short_name",
y: "co2",
fill: "black",
text: (d) => d.co2.toFixed(2),
dy: -10,
sort: { x: "-y" }
})
]
});
}
Insert cell
waffle_chart = {
let sum = d3.sum(barchart_data.map((d) => d.co2));
let data = barchart_data.map((d) => {
d.co2_percent = (d.co2 / sum) * 100;
return d;
});
return Plot.plot({
color: { legend: true },
marks: [
Plot.waffleY(
data,
Plot.groupZ(
{ y: "identity" },
{ fill: "name", y: "co2_percent", tip: true }
)
),
Plot.ruleY([0])
]
});
}
Insert cell
barchart_data = [
{
co2: 0.30556994,
name: "Frites de pommes de terre, surgelées, cuites en friteuse [ 1.527 kg CO2 eq/kg ] "
},
{
co2: 3.3230120000000003,
name: "Bœuf, steak haché 10% MG, cru [ 33.2 kg CO2 eq/kg ] "
},
{
co2: 0.134905416,
name: "Pain pour hamburger ou hot dog (bun), préemballé [ 1.686 kg CO2 eq/kg ] "
},
{
co2: 0.0422036802,
name: "Tomate, crue [ 0.7033 kg CO2 eq/kg ] "
},
{
co2: 0.0194768065,
name: "Oignon, cru [ 0.3895 kg CO2 eq/kg ] "
},
{
co2: 0.01223386086,
name: "Salade verte, crue, sans assaisonnement [ 0.9410 kg CO2 eq/kg ] "
},
{
co2: 0.004723389599999999,
name: "Ketchup [ 0.9446 kg CO2 eq/kg ] "
}
]
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