Public
Edited
Nov 25
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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more