Public
Edited
Jun 12
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof chartDisplay = {
if (powerControls.chartType === "Sunburst") {
// Return sunburst chart
const { width, radius, partition, arc, mousearc, color, patternColors } = chartConfig;
const root = partition(powerData);
const svg = d3.create("svg");
const element = svg.node();
element.value = { sequence: [], percentage: 0.0, power: 0.0 };
const label = svg
.append("text")
.attr("text-anchor", "middle")
.attr("fill", "#333")
.style("visibility", "hidden");
label
.append("tspan")
.attr("class", "percentage")
.attr("x", 0)
.attr("y", 0)
.attr("dy", "-0.5em")
.attr("font-size", "3em")
.attr("font-weight", "bold")
.text("");
label
.append("tspan")
.attr("class", "power-value")
.attr("x", 0)
.attr("y", 0)
.attr("dy", "1em")
.attr("font-size", "1.2em")
.attr("fill", "#666")
.text("");
label
.append("tspan")
.attr("x", 0)
.attr("y", 0)
.attr("dy", "2.2em")
.attr("font-size", "0.9em")
.attr("fill", "#888")
.text("of total power consumption");

svg
.attr("viewBox", `${-radius} ${-radius} ${width} ${width}`)
.style("max-width", `${width}px`)
.style("font", "12px sans-serif");

const getColor = (d) => {
if (d.depth === 1) {
return color(d.data.name);
} else if (d.depth === 2) {
return patternColors[d.data.pattern] || color(d.data.name);
}
return color(d.data.name);
};

const path = svg
.append("g")
.selectAll("path")
.data(
root.descendants().filter(d => {
return d.depth && d.x1 - d.x0 > 0.001;
})
)
.join("path")
.attr("fill", d => getColor(d))
.attr("stroke", "white")
.attr("stroke-width", 1)
.attr("d", arc);

svg
.append("g")
.attr("fill", "none")
.attr("pointer-events", "all")
.on("mouseleave", () => {
path.attr("fill-opacity", 1);
label.style("visibility", "hidden");
element.value = { sequence: [], percentage: 0.0, power: 0.0 };
element.dispatchEvent(new CustomEvent("input"));
})
.selectAll("path")
.data(
root.descendants().filter(d => {
return d.depth && d.x1 - d.x0 > 0.001;
})
)
.join("path")
.attr("d", mousearc)
.on("mouseenter", (event, d) => {
const sequence = d.ancestors().reverse().slice(1);
path.attr("fill-opacity", node => {
if (d.depth === 1) {
return node === d ? 1.0 : 0.3;
} else {
return sequence.indexOf(node) >= 0 ? 1.0 : 0.3;
}
});
const percentage = ((100 * d.value) / root.value).toPrecision(3);
const powerValue = d.value.toFixed(1);
label
.style("visibility", null)
.select(".percentage")
.text(percentage + "%");
label
.select(".power-value")
.text(powerValue + " kVA");
element.value = { sequence, percentage, power: powerValue };
element.dispatchEvent(new CustomEvent("input"));
});

return element;
} else {
// Return pie chart
const { width, radius, pieArc, pieGenerator, patternColors } = chartConfig;
// Flatten data for pie chart
const flatData = [];
powerData.children?.forEach(site => {
site.children?.forEach(equipment => {
flatData.push({
name: equipment.name,
value: equipment.value,
pattern: equipment.pattern,
site: site.name
});
});
});
const svg = d3.create("svg");
const element = svg.node();
element.value = { sequence: [], percentage: 0.0, power: 0.0 };
svg
.attr("viewBox", `${-radius} ${-radius} ${width} ${width}`)
.style("max-width", `${width}px`)
.style("font", "12px sans-serif");

const data = pieGenerator(flatData);
const total = d3.sum(flatData, d => d.value);

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

const path = g.selectAll("path")
.data(data)
.join("path")
.attr("fill", d => patternColors[d.data.pattern] || "#95a5a6")
.attr("stroke", "white")
.attr("stroke-width", 2)
.attr("d", pieArc);

// Center text
const centerText = svg.append("g")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central");
centerText.append("text")
.attr("y", -10)
.style("font-size", "18px")
.style("font-weight", "bold")
.style("fill", "#333")
.text("Total Power");
centerText.append("text")
.attr("y", 15)
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "#e74c3c")
.text(`${total.toFixed(1)} kVA`);

// Add interactivity
path.on("mouseover", function(event, d) {
d3.select(this).attr("stroke-width", 4);
const percentage = ((100 * d.data.value) / total).toPrecision(3);
// Update center text
centerText.selectAll("text").remove();
centerText.append("text")
.attr("y", -20)
.style("font-size", "14px")
.style("fill", "#666")
.text(d.data.name.length > 15 ? d.data.name.substring(0, 15) + "..." : d.data.name);
centerText.append("text")
.attr("y", 0)
.style("font-size", "28px")
.style("font-weight", "bold")
.style("fill", "#e74c3c")
.text(`${d.data.value.toFixed(1)} kVA`);
centerText.append("text")
.attr("y", 20)
.style("font-size", "16px")
.style("fill", "#333")
.text(`${percentage}%`);
// Set element value for potential breadcrumb
element.value = {
sequence: [{data: {name: d.data.site}}, {data: d.data}],
percentage: percentage,
power: d.data.value.toFixed(1)
};
element.dispatchEvent(new CustomEvent("input"));
})
.on("mouseout", function() {
d3.select(this).attr("stroke-width", 2);
// Restore center text
centerText.selectAll("text").remove();
centerText.append("text")
.attr("y", -10)
.style("font-size", "18px")
.style("font-weight", "bold")
.style("fill", "#333")
.text("Total Power");
centerText.append("text")
.attr("y", 15)
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "#e74c3c")
.text(`${total.toFixed(1)} kVA`);
element.value = { sequence: [], percentage: 0.0, power: 0.0 };
element.dispatchEvent(new CustomEvent("input"));
});

// Add labels
const labelArc = d3.arc()
.innerRadius(radius - 80)
.outerRadius(radius - 80);

g.selectAll("text")
.data(data.filter(d => d.endAngle - d.startAngle > 0.1)) // Only label larger segments
.join("text")
.attr("transform", d => `translate(${labelArc.centroid(d)})`)
.attr("text-anchor", "middle")
.style("font-size", "11px")
.style("font-weight", "bold")
.style("fill", "#333")
.text(d => d.data.value > total * 0.05 ? `${d.data.value.toFixed(0)} kVA` : "");

return element;
}
}
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