Public
Edited
Mar 6, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// this line is a little bit of Observable magic to make sure that every time
// our filter cells update, we use the existing SVG element instead of trashing it and creating a new one
// if you're working in your index.html, you'd create your svg element and select it as we did before
const svg = this || html`<svg width=${width} height=${width / 2}></svg>`
mutable graph = calculateGraph(graph)
// ✨ OUR CODE HERE

const link = d3.select(svg)
//.append("g")
.selectAll(".link")
.data(graph.links, d => d.id)
.join("line")
.classed('link', true)
.attr('stroke', '#ccc')
.attr('opacity', 0.5)
const flower = d3.select(svg).selectAll('g')
.data(graph.nodes, d => d.title)
.join(
enter => {
const g = enter.append('g')

g.selectAll('path')
.data(d => _.times(d.numPetals, i => ({rotate: i * (360 / d.numPetals), ...d})))
.join('path')
.attr('transform', d => `rotate(${d.rotate}) scale(${d.scale})`)
.attr('d', d => d.path)
.attr('fill', d => d.color)
.attr('fill-opacity', 0.5)
.attr('stroke-width', 2)
.attr('stroke', d => d.color)

g.append('text')
.text(d => _.truncate(d.title, {length: 18}))
.style('font-size', '.7em')
.style('font-style', 'italic')
.attr('text-anchor', 'middle')
.attr('dy', '0.35em')

return g
}
)
const genre = d3.select(svg).selectAll('.genre')
.data(graph.genres, d => d.label)
.join('text')
.classed('genre', true)
.text(d => d.label)
.attr('text-anchor', 'middle')

const simulation = d3.forceSimulation(_.union(graph.nodes, graph.genres))
.force("link", d3.forceLink(graph.links)
//.id(d => d.id)
)
.force("collide", d3.forceCollide(d => d.scale * 100))
//.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, width / 4))
.on("tick", () => {
// update node and link positions
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)

flower
.attr('transform', d => `translate(${d.x} ${d.y})`)

genre
.attr('transform', d => `translate(${d.x} ${d.y})`)
})
invalidation.then(() => simulation.stop())
return svg
}
Insert cell
Insert cell
final = {
// this line is a little bit of Observable magic to make sure that every time
// our filter cells update, we use the existing SVG element instead of trashing it and creating a new one
// if you're working in your index.html, you'd create your svg element and select it as we did before
const svg = this || html`<svg width=${width} height=${width}></svg>`
mutable graph = calculateGraph(graph)
// create links
const link = d3.select(svg).selectAll('.link')
.data(graph.links, d => d.id)
.join('line')
.classed('link', true)
.attr('stroke', '#ccc')
.attr('opacity', 0.5)
// create flowers
const flower = d3.select(svg).selectAll('.flower')
.data(graph.nodes, d => d.title)
.join(
enter => {
const g = enter.append('g')
.classed('flower', true)
// create petals & titles
g.selectAll('path')
.data(d => _.times(d.numPetals, i =>
Object.assign({}, d, {rotate: i * (360 / d.numPetals)})))
.join('path')
.attr('transform', d => `rotate(${d.rotate})scale(${0.5 * d.scale})`)
.attr('fill-opacity', 0.5)
.attr('d', d => d.path)
.attr('fill', d => d.color)
.attr('stroke', d => d.color)
g.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.style('font-size', '.5em')
.style('font-style', 'italic')
.text(d => _.truncate(d.title, {length: 20}))
return g
}
)
// create genres
const genre = d3.select(svg).selectAll('.genre')
.data(graph.genres, d => d.label)
.join('text')
.classed('genre', true)
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text(d => d.label)
// now create force simulation
const nodes = _.union(graph.nodes, graph.genres)
const simulation = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody(-300))
.force('collide', d3.forceCollide(d => 150 * d.scale || 50))
.force('link', d3.forceLink(graph.links).distance(100))
.force('center', d3.forceCenter(width / 2, width / 2))
.alpha(0.5).alphaMin(0.1)
.on('tick', () => {
console.log('tick')
// update flower position
flower.attr('transform', d => `translate(${d.x}, ${d.y})`)
// genre position
genre.attr('transform', d => `translate(${d.x}, ${d.y})`)
// and links
link.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y)
})
invalidation.then(() => simulation.stop())
return svg
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {
movies, slide, slide_style,
svgHeight, scrollSVG, textWidth,
topGenres, rated,
} from '@sxywu/workshop-utility-functions'
Insert cell
import {calculateData} from '2683357905679f61'
Insert cell
topGenresOther = _.union(topGenres, ['Other'])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {checkbox} from "@jashkenas/inputs"
Insert cell
slide_style
Insert cell
d3 = require('d3')
Insert cell
_ = require('lodash')
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