Spaces:
Sleeping
Sleeping
| """Bubble chart template — flow family. | |
| Substitution note: d3-graph-gallery.com's "circular packing" pages | |
| (https://d3-graph-gallery.com/graph/circularpacking_basic.html and | |
| siblings, confirmed by re-checking https://d3-graph-gallery.com/ | |
| circularpacking.html's link list) are all `d3.forceSimulation()` bubble | |
| demos with flat dummy data, not `d3.pack()`/`d3.hierarchy()` layouts — the | |
| same substitution note hier_circle_pack.py already made for its own nested | |
| version of this problem. `flow_bubble` needs a FLAT, already-packed bubble | |
| chart (not the nested tree hier_circle_pack.py uses), so this is adapted | |
| instead from a different, real, verified flat `d3.pack()` bubble-chart | |
| source: https://multimedia.report/classes/coding/2018/exercises/basicbubblepackchart/ | |
| — which already builds its hierarchy from a flat list exactly the way this | |
| template needs (`d3.hierarchy({children: data}).sum(d => d.value)` then | |
| `d3.pack().size([...]).padding(...)`, one circle + centered label per leaf), | |
| so no nested-to-flat adaptation was even needed here. Field names are wired | |
| to this template's own flat `BubbleData` schema (`label`/`value` instead of | |
| the fetched source's CSV `Fruit`/`Amount` columns); circle fill color and | |
| the radius-proportional label truncation follow the same conventions | |
| already used by hier_circle_pack.py for its own leaf circles. | |
| """ | |
| from __future__ import annotations | |
| from pydantic import BaseModel | |
| from app.agents.d3.registry import D3Template, register | |
| class Bubble(BaseModel): | |
| label: str | |
| value: float | |
| class BubbleData(BaseModel): | |
| title: str = "" | |
| data: list[Bubble] | |
| _HTML = """<!-- Adapted from https://multimedia.report/classes/coding/2018/exercises/basicbubblepackchart/ (flat-list d3.pack() bubble chart) --> | |
| <!-- d3-graph-gallery.com's circular-packing pages are force-simulation demos, not hierarchy layouts; see module docstring --> | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src="https://d3js.org/d3.v7.min.js"></script> | |
| <style> | |
| html, body { margin: 0; padding: 0; width: 100vw; height: 100vh; background: #FAF7F2; color: #1A3557; font-family: Georgia, 'Libre Caslon Text', serif; overflow: hidden; } | |
| #my_dataviz { width: 100vw; height: 100vh; } | |
| .chart-title { fill: #1A3557; font-size: 18px; text-anchor: middle; } | |
| .node-circle { stroke: #1A3557; stroke-width: 1px; } | |
| .node-label { fill: #1A3557; text-anchor: middle; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="my_dataviz"></div> | |
| <script> | |
| const data = __DATA__; | |
| const margin = {top: 50, right: 10, bottom: 10, left: 10}, | |
| width = 960 - margin.left - margin.right, | |
| height = 600 - margin.top - margin.bottom; | |
| const svg = d3.select("#my_dataviz") | |
| .append("svg") | |
| .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) | |
| .attr("preserveAspectRatio", "xMidYMid meet") | |
| .style("width", "100%") | |
| .style("height", "100%"); | |
| svg.append("text") | |
| .attr("class", "chart-title") | |
| .attr("x", (width + margin.left + margin.right) / 2) | |
| .attr("y", 25) | |
| .text(data.title); | |
| const g = svg.append("g") | |
| .attr("transform", `translate(${margin.left},${margin.top})`); | |
| const root = d3.hierarchy({ children: data.data }) | |
| .sum(d => d.value); | |
| d3.pack() | |
| .size([width - 4, height - 4]) | |
| .padding(3) | |
| (root); | |
| const palette = ['#4A7FB5', '#7FA9D6', '#3D6690', '#A7C6E8', '#2C4A66', '#B5764A']; | |
| const node = g.selectAll("g.node") | |
| .data(root.leaves()) | |
| .join("g") | |
| .attr("class", "node") | |
| .attr("transform", d => `translate(${d.x},${d.y})`); | |
| node.append("circle") | |
| .attr("class", "node-circle") | |
| .attr("r", d => d.r) | |
| .style("fill", (d, i) => palette[i % palette.length]); | |
| node.append("text") | |
| .attr("class", "node-label") | |
| .attr("dy", "0.31em") | |
| .style("font-size", d => Math.min(14, d.r / 2.5) + "px") | |
| .text(d => d.data.label.substring(0, d.r / 3)); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| golden_sample = BubbleData( | |
| title="Demo", | |
| data=[ | |
| Bubble(label="A", value=10), | |
| Bubble(label="B", value=20), | |
| Bubble(label="C", value=5), | |
| ], | |
| ) | |
| register( | |
| D3Template( | |
| id="flow_bubble", | |
| family="flow", | |
| title="Bubble chart", | |
| when_to_use="Show relative size of a flat set of named quantities as packed circles (not a hierarchy).", | |
| data_requirements="A flat list of labeled values (no nesting).", | |
| schema=BubbleData, | |
| html_template=_HTML, | |
| golden_sample=golden_sample, | |
| ) | |
| ) | |