Public
Edited
Jun 8
1 star
Insert cell
Insert cell
Insert cell
data = FileAttachment("polka_dot_plant_growth_data.csv").csv({typed: true})
Insert cell
plants = d3.group(data, d => d.Plant_ID)

Insert cell
transformed = {
const stageOrder = [
"Sowing/Cutting",
"Germination/Rooting",
"Seedling Stage",
"Early Vegetative",
"Mid Vegetative",
"Late Vegetative",
"Mature Stage",
"Light Adjustment",
"Pre-Flowering",
"Flowering",
"Post-Flowering",
"Growth Stagnation",
"Decline Stage",
"Dormant Stage",
"Post-Pruning Recovery"
];

const stageMap = Object.fromEntries(stageOrder.map((stage, i) => [stage, i]));

// 保证每个阶段绘制在同一个“日期段”内,形成连续视觉效果
const output = [];
for (const stage of stageOrder) {
const stageData = data.filter(d => d.Growth_Stage === stage);
stageData.forEach((d, i) => {
output.push({
name: stage,
date: new Date(2024, 0, 1 + stageMap[stage] * 2 + i), // 每个阶段占一周,内部均匀分布
value: d[variable]
});
});
}

return output;
}


Insert cell
viewof bands = Inputs.range([1, 10], {step: 1, value: 10, label: "Bands"})

Insert cell
viewof variable = Inputs.radio(
[
"Avg_Temperature_C",
"Light_Hours_Per_Day",
"Relative_Humidity_Percent",
"Fertilizer_Freq_Per_Week"
],
{
label: "Choose The Dependent Variable",
value: "Avg_Temperature_C"
}
)


Insert cell
colors = d3.schemeBuGn[bands] || d3.schemeBuGn[6]

Insert cell
chart = {

{
const style = document.createElement("style");
style.textContent = `
@font-face {
font-family: 'Elastre';
src: url('https://res.cloudinary.com/dtu3vkxv6/raw/upload/v1746886734/Elastre_HEXP_INKT_vfwhma.ttf') format('truetype');
}

label, input[type="range"], span {
font-family: 'Elastre', sans-serif !important;
font-size: 10px;
}
`;
document.head.appendChild(style);
}

const series = d3.rollup(transformed, v => d3.sort(v, d => d.date), d => d.name);

const marginTop = 30;
const marginRight = 10;
const marginBottom = 0;
const marginLeft = 100;
const width = 928;
const size = 25;
const height = series.size * size + marginTop + marginBottom;
const padding = 1;

const x = d3.scaleUtc()
.domain(d3.extent(transformed, d => d.date))
.range([0, width - marginLeft - marginRight]);

const y = d3.scaleLinear()
.domain([0, d3.max(transformed, d => d.value)])
.range([size, size - bands * (size - padding)]);

const area = d3.area()
.defined(d => !isNaN(d.value))
.x(d => x(d.date))
.y0(size)
.y1(d => y(d.value));

const uid = `O-${Math.random().toString(16).slice(2)}`;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; font-size: 8px; font-family: Elastre, sans-serif;");

// ✅ 添加字体样式到 SVG <style>
svg.append("style").text(`
@font-face {
font-family: 'Elastre';
src: url('https://res.cloudinary.com/dtu3vkxv6/raw/upload/v1746886734/Elastre_HEXP_INKT_vfwhma.ttf') format('truetype');
}
`);

const g = svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.attr("transform", (d, i) => `translate(0,${i * size + marginTop})`);

const defs = g.append("defs");

defs.append("clipPath")
.attr("id", (_, i) => `${uid}-clip-${i}`)
.append("rect")
.attr("x", marginLeft)
.attr("y", padding)
.attr("width", width - marginLeft - marginRight)
.attr("height", size - padding);

defs.append("path")
.attr("id", ([, values], i) => `${uid}-path-${i}`)
.attr("d", ([, values]) => area(values));

g.append("g")
.attr("clip-path", (_, i) => `url(#${uid}-clip-${i})`)
.selectAll("use")
.data((_, i) => new Array(bands).fill(i))
.enter().append("use")
.attr("xlink:href", i => `#${uid}-path-${i}`)
.attr("fill", (_, i) => colors[i % colors.length])
.attr("transform", (_, i) => `translate(${marginLeft},${i * size})`);

g.append("text")
.attr("x", 4)
.attr("y", (size + padding) / 2)
.attr("dy", "0.35em")
.text(([name]) => name);

svg.append("g")
.attr("transform", `translate(${marginLeft},${marginTop})`)
.call(d3.axisTop(x).ticks(width / 80).tickSizeOuter(0))
.call(g => g.selectAll(".tick")
.filter(d => x(d) < 0 || x(d) >= width - marginLeft - marginRight)
.remove())
.call(g => g.select(".domain").remove());

svg.selectAll(".tick text")
.style("font-family", "Elastre")
.style("font-size", "8px");

return svg.node();
}



Insert cell
html`<style>
@font-face {
font-family: 'Elastre';
src: url('https://res.cloudinary.com/dtu3vkxv6/raw/upload/v1746886734/Elastre_HEXP_INKT_vfwhma.ttf') format('truetype');
}

svg {
font-family: 'Elastre', sans-serif;
}
</style>`

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