Public
Edited
Oct 16, 2023
Fork of Circle Pack
Insert cell
Insert cell
parsed_painting
Insert cell
_data = d3.stratify()
.id(d => d.n)
.parentId(d => d.parent)(parsed_painting)
Insert cell
d3.hierarchy(_data)
Insert cell
{
const svg = DOM.svg(width, height)
const sel = d3.select(svg)
// convert the flat data into a hierarchy
const data = d3.stratify()
.id(d => d.n)
.parentId(d => d.parent)(parsed_painting)

// console.log('data', data)
const packLayout = d3.pack()
.size([width, height])
.radius((d) => {
// console.log('d', Math.sqrt(d.data.data.v/5) )
return Math.sqrt(d.data.data.v/5)
})
.padding(padding)

const rootNode = d3.hierarchy(data)

// run .sum() on the hierarchy; return 1 for size of leaf nodes
rootNode.sum(() => 1)

packLayout(rootNode)

renderCircles(rootNode.descendants())

function renderCircles (nodes) {
console.log('nodes:', nodes)
const circles = sel
.selectAll('circle')
.data(nodes, d => d.data.id)
// circles.exit().remove()
circles.enter().append('circle')
.attr('class', d => {
if (d.depth === 0) {
return 'root'
} else if (d.depth === 1) {
return 'cluster'
} else {
return 'item'
}
})
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', d => d.r)
.attr('fill', 'none')
.attr('stroke', 'gray')
.on('mouseover', function (d) {
console.log(d.data.id)
})

sel.selectAll('.root').attr('opacity', 0)

const node_label_bg = sel.selectAll('.node_label_bg')
.data(nodes.filter(a=> a.depth===1), d => d.data.id+'_label_bg')

node_label_bg.enter().append('text')
.attr('class', 'node_label_bg')
.attr('x', d=> d.x)
.attr('y', d=> d.y-d.r-5)
.text(d=> d.data.id)
.attr('text-anchor', 'middle')
.style('font-size', '12px')
.style('font-family', 'Arial')
.attr('stroke', 'white')
.attr('fill', 'white')
.attr('stroke-width', '4px')
const node_label = sel.selectAll('.node_label')
.data(nodes.filter(a=> a.depth===1), d => d.data.id+'_label')

node_label.enter().append('text')
.attr('class', 'node_label')
.attr('x', d=> d.x)
.attr('y', d=> d.y-d.r-5)
.text(d=> d.data.id)
.attr('text-anchor', 'middle')
.style('font-size', '12px')
.style('font-family', 'Arial')
}
return svg
}
Insert cell
Insert cell
Insert cell
Insert cell
chart = BubbleChart(prospectCounts, {
label: d => [...d.Species.split(",").pop().split(/(?=[A-Z][a-z])/g), d.Count.toLocaleString("en")].join("\n"),
value: d => d.Count,
group: d => d.Category,
width: 1152
})
Insert cell
prospectCounts
Insert cell
ProspectRangerCounts2018-2021.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
// Copyright 2021 Observable, Inc.
// https://observablehq.com/@d3/bubble-chart
function BubbleChart(data, {
name = ([x]) => x, // alias for label
label = name, // given d in data, returns text to display on the bubble
value = ([, y]) => y, // given d in data, returns a quantitative size
group, // given d in data, returns a categorical value for color
width = 640, // outer width, in pixels
height = width, // outer height, in pixels
padding = 3, // padding between circles
margin = 1, // default margins
marginTop = margin, // top margin, in pixels
marginRight = margin, // right margin, in pixels
marginBottom = margin, // bottom margin, in pixels
marginLeft = margin, // left margin, in pixels
groups, // array of group names (the domain of the color scale)
colors = d3.schemeTableau10, // an array of colors (for groups)
fill = "#ccc", // a static fill color, if no group channel is specified
fillOpacity = 0.7, // the fill opacity of the bubbles
stroke, // a static stroke around the bubbles
strokeWidth, // the stroke width around the bubbles, if any
strokeOpacity, // the stroke opacity around the bubbles, if any
} = {}) {
// Compute the values.
const D = d3.map(data, d => d);
const V = d3.map(data, value); // radius
const G = group == null ? null : d3.map(data, group); // color and species group
const I = d3.range(V.length).filter(i => V[i] > 0);

// Unique the groups.
if (G && groups === undefined) groups = I.map(i => G[i]);
groups = G && new d3.InternSet(groups);

// Construct scales.
const color = G && d3.scaleOrdinal(groups, colors);

// Compute labels and titles.
const L = label == null ? null : d3.map(data, label);
// const T = title === undefined ? L : title == null ? null : d3.map(data, title);

// Compute layout: create a 1-deep hierarchy, and pack it.
const root = d3.pack()
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
.padding(padding)
(d3.hierarchy({children: I})
.sum(i => V[i]));

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("fill", "currentColor")
.attr("font-size", 10)
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle");

const leaf = svg.selectAll("a")
.data(root.leaves())
.join("a")
.attr("transform", d => `translate(${d.x},${d.y})`);

leaf.append("circle")
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("stroke-opacity", strokeOpacity)
.attr("fill", G ? d => color(G[d.data]) : fill == null ? "none" : fill)
.attr("fill-opacity", fillOpacity)
.attr("r", d => d.r);

if (L) {
// A unique identifier for clip paths (to avoid conflicts).
const uid = `O-${Math.random().toString(16).slice(2)}`;

leaf.append("clipPath")
.attr("id", d => `${uid}-clip-${d.data}`)
.append("circle")
.attr("r", d => d.r);

leaf.append("text")
.attr("clip-path", d => `url(${new URL(`#${uid}-clip-${d.data}`, location)})`)
.selectAll("tspan")
.data(d => `${L[d.data]}`.split(/\n/g))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i, D) => `${i - D.length / 2 + 0.85}em`)
.attr("fill-opacity", (d, i, D) => i === D.length - 1 ? 0.7 : null)
.text(d => d);
}

return Object.assign(svg.node(), {scales: {color}});
}
Insert cell
function parse_data_for_circlepack(data){
const _filtered = data.filter(a=> a['c'])
const all_item = _filtered.map(item =>{
const all_inner_item = item.c.map(t=>{
return ({...t, 'parent': item.n })
} )
// item.c = null;
item['parent']='root';
const _new_item = JSON.parse(JSON.stringify(item))
_new_item.c = null;
return [_new_item, ...all_inner_item ]
} )
const _root = {n: "root", parent: null}
return [_root].concat(...all_item )
}
Insert cell
parsed_painting = parse_data_for_circlepack(painting)
Insert cell
// painting = phrasesTree.c.filter( t=> ['lighting', 'shadow'].indexOf(t.n)>-1 )[0].c
painting = phrasesTree.c.filter( t=> ['painting'].indexOf(t.n)>-1 )[0].c
Insert cell
phrasesTree = FileAttachment("phrases-tree.json").json()
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