Published
Edited
Jul 2, 2019
25 stars
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
{
const svgContainer = d3.select(animationSvg())
const rectangle = svgContainer.select(".rect-selector")
const duration = durationSelection || 200
const delay = delaySelection || 0

// MOVE
d3.select("#move").on("click", function () {
rectangle
.transition()
.duration(duration)
.delay(delay)
.attr("x", 250); // New Position
})


// RESIZE
d3.select("#resizeT").on("click", function () {
rectangle
.transition()
.duration(duration)
.delay(delay)
.attr("width", 100) // New Width
.attr("height", 100); // New Height
})

// OPACITY
d3.select("#opacityT").on("click", function () {
rectangle
.transition()
.duration(duration)
.delay(delay)
.attr("opacity", 0.5)
});

// OPACITY
d3.select("#colorT").on("click", function () {
rectangle
.transition()
.duration(duration)
.delay(delay)
.attr("fill", "blue")
})

// RESET
d3.select("#reset").on("click", function () {
rectangle
.transition()
.duration(duration)
.delay(delay)
.attr("x", 50) // Old Position
.attr("width", 50)
.attr("height", 50)
.attr("opacity", 1)
.attr("fill", "black")
})

return svgContainer.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3.select(".my-first-selection")
// .style("fill", "purple")
Insert cell
d3.selectAll(".my-first-selection")
// .style("fill", "orange")
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 400
const height = 30
const svg = d3.select(DOM.svg(width, height))
svg
.append("text")
.attr("x", 10)
.attr("y", 20 )
.attr("class", "my-text")
.attr("fill", "purple")
.text("This is Text with SVG")

return svg.node()
}
Insert cell
Insert cell
{
const width = 400
const height = 60
const svg = d3.select(DOM.svg(width, height))
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", 50)
.attr("width", 50)
.style("fill", "red")
svg.insert("circle")
.attr("cx", 300)
.attr("cy", 10)
.attr("r", 10)
svg.insert("circle")
.attr("cx", 330)
.attr("cy", 10)
.attr("r", 10)
// uncomment below to remove the circles
// svg.selectAll("circle").remove()
// uncomment below to remove everything!
// svg.selectAll("*").remove()

return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// on click add random circle to svg
pressAddCircle
if(pressAddCircle !== undefined) {
d3.select(primitiveSvg)
.append("circle")
.attr("r", 10)
.attr("cx", Math.floor(Math.random() * 400) + 100)
.attr("cy", Math.floor(Math.random() * 400) + 100)
.attr("class", "dynamic-circle")
return primitiveSvg
}
}
Insert cell
primitiveSvg = {
// ---- constants ----
const height = 600
const lineData = [{
x: 100,
y: 10
}, {
x: 200,
y: 100
}, {
x: 200,
y: 100
}, {
x: 300,
y: 100
}]
// ---- d3 GENERATOR functions ----
const line = d3.line()
.x((d) => d.x) // set the x values for the line generator
.y((d) => d.y) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line

const arc = d3.arc() //generator functions
.innerRadius(200)
.outerRadius(240)
.startAngle(0)
// ---- ----

const svg = d3.select(DOM.svg(width, height))
svg.selectAll(".dynamic-circle").remove()
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", 50)
.attr("width", 50)
.style("fill", "#dcf5f5")
.attr("stroke", "blue")
.attr("stroke-width", 1)
svg
.append("circle")
.attr("class", "first-circle")
.attr("cx", 75)
.attr("cy", 75)
.attr("r", 10)
svg
.append("text")
.attr("x", 70)
.attr("y", 60)
.style("font-size", "16px")
.text("Yay! Drawing text with svg!")

svg.append("g") // g is a group of elements
.append("path")
.attr("d", line(lineData))
.attr("fill", "none") // not so pretty with fill
.attr("stroke-width", 2)
.attr("stroke", "grey")
svg.append("path")
.datum({
endAngle: 2 * Math.PI
})
.style("fill", "#ddd")
.attr("d", arc)
.attr("transform", "rotate(0)translate(0,375)") // x,y translation from (0, 0)
return svg.node()
}

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// ---- constants ----
const width = 400
const height = 50
const myData = [10, 50, 30 ]
const colors = ["red", "green", "blue"]
const svg = d3.select(DOM.svg(width, height))
const nodes = svg.selectAll("circle")
.data(myData) // use .data to append myData to SVG
nodes
.enter() // apply data elements and specify how to handle them
.append("circle") // appends circle for each data point
.attr("cx", (d) => d) // <-------- access to data item
.attr("cy", 20)
.attr("r", 10)
.attr("fill", (d, i) => colors[i % 3])
// alternative :
// nodes
// .join("circle")
// .attr("cx", (d) => d)
// .attr("cy", 20)
// .attr("r", 10)
// .attr("fill", (d, i) => colors[i % 3])
return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
playUpdatePattern(true) //restart by pressing play ----->
Insert cell
Insert cell
Insert cell
{
// PATTERN 1 (.join)
let alpha = "abcdefghijklmnopqrstuvwxyz".split("")
const WIDTH = width
const height = 200

const svg = d3.select(DOM.svg(WIDTH, height))

update(alpha)
setInterval(() => {
update(shuffled(alpha))
}, 2000)

function update(arr) {
const t = svg.transition().duration(500)
const t2 = svg.transition().duration(1000)
svg
.selectAll(".alpha-text")
.data(arr, (d, i) => d) // KEY: Bind your data elements
.join(
enter => enter.append("text")
.text((d) => d)
.attr("fill", "green")
.attr("y", 40)
.attr("x", (d, i) => -100)
.call(enter => enter.transition(t)
.attr("x", (d, i) => i * 15 + 100)
.attr("y", 100)
),
update => update
.attr("fill", "blue")
.call(update => update.transition(t2)
.attr("x", (d, i) => i * 15 + 100),
),
exit => exit
.attr("fill", "red")
.call(exit =>
exit.transition(t)
.attr("x", 500)
.attr("y", 30)
.remove()
)
)
.attr("class", "alpha-text")
}
return svg.node()
}
Insert cell
Insert cell
{
// PATTERN 2 (update, enter(), exit())
let alpha = "abcdefghijklmnopqrstuvwxyz".split("")
const WIDTH = width
const height = 200

const svg = d3.select(DOM.svg(WIDTH, height))

update(alpha)
setInterval(() => {
update(shuffled(alpha))
}, 2000)

function update(arr) {
const t = svg.transition().duration(500)
const t2 = svg.transition().duration(1000)

// DATA BIND
const text = svg.selectAll(".alpha-text").data(arr, (d) => d)

// UPDATE
text.attr("fill", "blue")
.transition().duration(500)
.attr("x", (d, i) => i * 15 + 100)

// ENTER
text.enter().append("text")
.text((d) => d)
.attr("class", "alpha-text")
.attr("fill", "green")
.attr("y", 40)
.attr("x", (d, i) => -100)
.transition().duration(500)
.attr("x", (d, i) => i * 15 + 100)
.attr("y", 100)

// EXIT
text.exit()
.attr("fill", "red")
.transition().duration(1000)
.attr("x", 500)
.attr("y", 30)
.remove()
}
return svg.node()
}
Insert cell
Insert cell
{
// PATTERN 3 (.merge)
let alpha = "abcdefghijklmnopqrstuvwxyz".split("")
const WIDTH = width
const height = 200

const svg = d3.select(DOM.svg(WIDTH, height))

update(alpha)
setInterval(() => {
update(shuffled(alpha))
}, 2000)

function update(arr) {
const t = svg.transition().duration(500)
const t2 = svg.transition().duration(1000)

// DATA BIND
const text = svg.selectAll(".alpha-text").data(arr, (d) => d)

// // UPDATE
text.attr("fill", "blue")

// ENTER
const enterNodes = text.enter().append("text")
.text((d) => d)
.attr("class", "alpha-text")
.attr("fill", "green")
.attr("y", 40)
.attr("x", (d, i) => -100)

const update = enterNodes.merge(text) // apply to both new and exiting nodes
update
.transition().duration(500)
.attr("y", 100)
.attr("x", (d, i) => i * 15 + 100)
// EXIT
text.exit()
.attr("fill", "red")
.transition().duration(1000)
.attr("x", 500)
.attr("y", 30)
.remove()
}
return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
updateFruit2 = (svg, data) => {

// STEP 1: DATA JOIN
const node = svg
.selectAll("image")
.data(data, (d, i) => d.key) // KEY: Bind your data elements

// STEP 2: ENTER / MERGE
const nodeEnter = node
.enter()
.append("image")
.attr("class", "imgs")
.attr("href", (d) => `${imgRoot}${d.key}.svg`)
.attr("x", 0)
.attr("y", 0)
.attr("width", (d) => 10)
.attr("height", (d) => 10)

// UPDATE
const update = nodeEnter.merge(node) // anytime node updates so does nodeEnter
update
.transition()
.duration(750) // duration of the animation
.delay((d, i) => 100 * i) // stagger delay animation start
.attr("x", (d, i) => 10 + i * 100)
.attr("y", 50)
.attr("width", 100)
.attr("height", 100)

// add an exit transition all exiting nodes will animate
// STEP 3: REMOVE
const nodeExit = node.exit()
.transition()
.duration(750)
.attr("height", 10)
.remove()
return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
updateExercise(true)
Insert cell
{
const HEIGHT = 100
const WIDTH = width
const dynamic = false // change to true for dynamic data
const testData = [15, 45, 75]
const allDatas = [12, 8, 15, 45, 75, 33, 100, 190]
const transitionDuration = 1000
const svg = d3.select(DOM.svg(WIDTH, HEIGHT))
// Fill in update function
const update = (data) => {
// =========== Step 1: Data Join ============
const node = [`fill in`]

// =========== Step 2: Enter / Merge ============
const nodeEnter = [`fill in`]

// [`fill in`].merge([`fill in`])
// .transition()
// .duration(transitionDuration) // duration of the animation
// .delay((d, i) => 100 * i) // stagger delay animation start
//fill in new positions


// =========== Step 3: Exit / Remove ============
const nodeExit = ['fill in']
}
if(dynamic) {
const interval = d3.interval((t) => {
if (t > 1500 * 10) { // stop after 10 times
interval.stop()
return
}
let endIndex = Math.floor(Math.random() * allDatas.length - 1)
if (endIndex < 1) {
endIndex = 1
}
update(d3.shuffle([...allDatas]).slice(0, endIndex).sort())
}, 1500)
} else {
update(testData)
}
return svg.node()
}

// ========== common pitfall ==========

// const circles = svg
// .selectAll('circle')
// .data(data)
// .enter()
// .append('circle')
// .attr('r', MIN_RADIUS)

// what is the difference from above?
// circles
// .attr('cx', 50)
// .style('fill', 'grey')
// .attr('cy', 50)

// // exit is not defined?
// circles.exit().remove();

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const HEIGHT = 300
const WIDTH = 400
const MARGIN = 80
const svg = d3.select(DOM.svg(WIDTH, HEIGHT))
// --- linear scale ---
const linearscale = d3.scaleLinear().domain([0, 20]).range([MARGIN, WIDTH - MARGIN])
const yscale = d3.scaleLinear().domain([0, 30]).range([MARGIN, HEIGHT - MARGIN])

// --- time scale ---
const timescale = d3.scaleTime()
.domain([new Date("2011-12-17T03:24:00"), new Date()]).range([MARGIN, WIDTH - MARGIN])
// -- point scale ---
const pointScale = d3.scalePoint()
.domain(["a", "b", "c", "d"])
.range([MARGIN, WIDTH - MARGIN])

const selectedScale = scaleType === "linear" ? linearscale : (scaleType === "time" ? timescale : pointScale)
const xaxis = d3.axisBottom().scale(selectedScale)
const xaxis_container = svg.append("g")
.attr("class", "xaxis")
.attr("transform", `translate(0, ${HEIGHT - MARGIN})`)
.call(xaxis)

const yaxis = d3.axisLeft().scale(yscale)
const yaxis_container = svg.append("g")
.attr("class", "yaxis")
.attr("transform", `translate(${MARGIN}, 0)`)
.call(yaxis)

return svg.node()
}
Insert cell
Insert cell
Insert cell
{
const height = 300
const margin = {top: 30, bottom: 30, left: 40, right: 40}
const svg = d3.select(DOM.svg(width, height))
// === create x and y scales ===
// const xScale =
// const yScale =
// === create axis ===
// const yAxis =
// const xAxis =
// === Append each axis to svg ===
return svg.node()
}
Insert cell
Insert cell
Insert cell
{
const height = 300
const margin = {top: 30, bottom: 30, left: 40, right: 40}
const data = [{x:30, y:20}, {x:55, y:8},{x:79, y:24}] // <------ Circle Data
const radius = 10 // circle radius
const svg = d3.select(DOM.svg(width, height))
// YOUR AXIS HERE
// append circle data to the Axis
return svg.node()
}
Insert cell
Insert cell
Insert cell
{
// --- constants ----
const HEIGHT = 200
const WIDTH = 400
const MIN_RADIUS = 3
const MAX_RADIUS = 12
const circleData = [
{ weight: 30, name: "apple" },
{ weight: 10, name: "pear" },
{ weight: 100, name: "watermelon" }
]
const names = circleData.map((o) => o.name)
const svg = d3.select(DOM.svg(WIDTH, HEIGHT))
// --- setup radius scale ---
const maxWeight = d3.max(circleData, (d) => d.weight) // d3 finds the max for us!
const rscale = d3.scaleLinear()
.domain([0, maxWeight])
.range([MIN_RADIUS, MAX_RADIUS])
// --- setup color scale ---
const brewerscale = d3.scaleOrdinal().domain(names).range(d3.schemeCategory10)
const scale = d3.scaleLinear().domain([0, maxWeight]).range([0, 1])
const rainbow = (d) => d3.interpolateRainbow(scale(d))
const blueSeq = (d) => d3.interpolateBlues(scale(d))
const checkScaleType = (d, selection) => {
if(selection === "Brewer"){
return brewerscale(d.name)
} else {
return selection === "Rainbow" ? rainbow(d.weight) : blueSeq(d.weight)
}
}
svg
.selectAll(".circleNodes")
.data(circleData)
.join("circle")
.attr("class", "circleNode")
.attr("r", (d) => rscale(d.weight)) // map weights to rscale
.attr("cx", (d,i) => i * 20 + 20 )
.attr("cy", (d,i) => i * 20 + 20 )
.attr("fill", (d) => checkScaleType(d, colorSelection)) // use colorscale of selection

return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
update = (svg, rawData, dimensions, filter, type) => {
const padding = 100
const margin = { top: 100, bottom: 100, left: 50, right: 50 }
const paddingLeft = 50
const data = handleData(type, rawData, filter)
const t = svg.transition().duration(750)
// define our bands for bars
const x = d3.scaleBand()
.rangeRound([0, dimensions.width - margin.left])
.padding(0.1)

x.domain([...data].map((d) => d.name))

// update xAxis selector to have the updated axis
const xaxis = svg.selectAll(".xAxis")
.attr("opacity", 0.5)
.attr("transform", `translate(${margin.left}, ${(dimensions.height - margin.top)})`)
.call(d3.axisBottom(x))

const y = d3.scaleLinear()
.domain([0, 100])
.rangeRound([dimensions.height - margin.bottom, margin.top]) // margin value allows us to avoid getting "cut off"

// remove any existing data
const bars = svg.selectAll(".bar")
.data(data, (d, i) => (d.name + i))
.join(
enter => enter.append("rect")
.attr("y", dimensions.height - margin.top)
.attr("x", (d) => x(d.name))
.attr("label", (d) => `${d.name}, ${d[filter]}`)
.attr("width", () => x.bandwidth())
.attr("height", 0) // calculate new height relative to top
.attr("transform", `translate(${margin.left}, 0)`)
.attr("fill", (d) => d.color)
.call(enter => enter.transition(t)
.attr("y", (d) => y(d[filter]))
.attr("height", (d) => dimensions.height - y(d[filter]) - margin.top)
),
update => update
.call(update => update.transition(t)
.attr("fill", (d) => d.color) // update color to reflect type
.attr("stroke", (d) => d.color)
),
exit => exit
.call(exit => exit.transition(t)
.attr("height", 0)
.attr("fill", "grey")
.remove()
)
)
.attr("class", "bar")
.attr("fill-opacity", 0.5) // use fill-opacity instead of opacity to get stroke opacity changed
.attr("stroke-width", 2)
.attr("stroke-opacity", 0.7)
// update yAxis and text
svg.selectAll(".yaxis-text")
.remove()

svg.selectAll(".yAxis") // selectAll on the axis will update them from prev
.call(d3.axisLeft(y))
.attr("transform", `translate(${margin.left},0)`)
.attr("opacity", 0.5)
return svg.node()

}
Insert cell
{
// === OPTIMIZATION ===
const svg = d3.select(chart)
let maxTextLength = 100
const angleDegrees = -45
const angleRadians = angleDegrees * (Math.PI / 180)
const padding = 100
// Adjust axis text so that when we angle it, all text is visible
// hypotenuse = this.getBBox().width
// solve pythagorean theorem for opposite which is total distance from top
svg.selectAll(".xAxis text")
.style("font-family", "Segoe UI, Tahoma, Geneva, Verdana, sans-serif")
.attr("transform", function(d, n, i) {
maxTextLength = Math.max(maxTextLength + 0.1, this.getBBox().width)
return `translate( ${this.getBBox().height*-2}, ${-(this.getBBox().width * Math.sin(angleRadians))/2})rotate(${angleDegrees})`
})
// add extra padding to move svg up is needed so that text doesn't get cut off due to svg height constraint
svg.attr("transform", `translate( 0, ${padding - maxTextLength + 10})`)
return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const height = 700
const margin = ({top: 100, right: 10, bottom: 100, left: 40})
const colors = {"males": "#63a2cd", "females": "#e293ac"}
const data = processNamesData(unisexSelector)
const svg = d3.select(DOM.svg(width, height))
const columns = Object.keys(unisexNames[0]).slice(2)
const series = d3.stack().keys(columns)(data)
const yMax = d3.max(series, d => d3.max(d, d => d[1]))
// Data Format:
// seriesData = [seriesDataItem[], key: males | females, index: number]
// seriesDataItem = [yEnd: number, yStart: number, {data: {name: string, rank: number, males: number, females: number}}]
// ====== Create chart Title ======
svg.append("text")
.attr("class", "chart-title")
.attr("x", (width / 2) - (margin.left /2) )
.attr("y", margin.right * 2 )
.style("font-size", "20pt")
.style("font-family", "Segoe UI, Tahoma, Geneva, Verdana, sans-serif")
.text("Top Unisex Names")
// create x scale which domain is the name in data, and range accounts for margin
const x = d3.scaleBand()
// .domain(//fill-in)
// .range([//fill-in, //fill-in])
.padding(0.1)
// // create y scale which domain is from 0 to yMax and range accounts for margin
const y = d3.scaleLinear()
// .domain([//fill-in, //fill-in])
// .rangeRound([//fill-in, //fill-in])

const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.selectAll(".domain").remove())

// fill in enter() function so that you are appending a rectangle that is aligned on the x axis based on the name and aligns on the y-axis based on the yStart value. Dont forget to add a height and width to your rectangle
svg
.selectAll("g")
.data(series)
.enter().append("g")
.attr("fill", (d, i) => colors[d.key])
.selectAll("rect")
.data(d => d)
.enter()
//fill-in
// Add XAxis to the svg

// Add YAxis to the svg


return svg.node()
}
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
{
const root = tree(formatted_table)
const height = width
root.x0 = height / 2
root.y0 = 0

root.descendants().forEach((n) => {
if (n.children) {
if (n.depth >= 3) {
n._children = n.children
n.children = null
}
}
})
const svg = d3.select(DOM.svg(width, height))
svg
.style("width", width)
.style("height", height)

const updateNodes = (source) => {
const height = width
const scale = d3.scaleLinear().domain([0, 12]).range([0.2, 1.0])
const color = (d) => d3.interpolateRainbow(scale(d))
const duration = 600

const nodes = root.descendants()
const links = root.links()
const size = 0


let maxTextLength = 0
// Update the nodes...
const node = svg.selectAll("g.node")
.data(nodes, (d) => d.data.data.id)

// Enter any new modes at the parent"s previous position.
const nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", (d) => `
rotate(${source.x0 * 180 / Math.PI - 90})
translate(${source.y0},0)`)
.attr("fill", "none")

nodeEnter.selectAll(".textLabels").remove()
const offset = 2
nodeEnter.append("text")
.attr("dy", ".2em")
.attr("class", "textLabels")
.attr("font-size", "8px")
.attr("text-anchor", function (d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function (d) {
const distance = d.children ? 2.5 : 5 + offset
return d.x < 180 ? `translate(${distance})` : `rotate(180)translate(-${distance})`
})
.text((d) => d.data.data.id)
.each(function (d, i, n) {
if (d._children) {
const distance = d.children ? 2.5 : 5 + offset
const thisWidth = n[i].getComputedTextLength()
maxTextLength = Math.max(maxTextLength, d.y + thisWidth + distance)
}
})

const updateSvg = () => {
// THIS IS WHERE THE RESIZE MAGIC HAPPENS
const newSize = maxTextLength * 2
svg
.transition()
.duration(1500)
.style("width", width)
.style("height", height)
.style("padding", "10px")
.style("box-sizing", "border-box")
.style("font", "8px sans-serif")
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", `-${newSize / 2} -${newSize / 2} ${newSize} ${newSize}`)
}

updateSvg()
// ****************** nodes section ***************************
nodeEnter.append("circle")
.attr("class", "circleNode")
.attr("r", 1e-6)
.attr("title", (d) => d.depth)
.attr("cursor", "pointer")
.on("click", (d) => {
if (d.depth > 2) {
d.children = d.children ? null : d._children;
updateNodes(d)
}
})
const nodeUpdate = nodeEnter.merge(node)

nodeUpdate.transition()
.delay(800)
.duration(duration)
.attr("fill", (d) => d._children ? "white" : color(d.depth))
.attr("stroke", (d) => d._children ? color(d.depth) : "none")
.attr("stroke-width", (d) => d._children ? 1 : 0)
.attr("transform", (d) => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`)
.attr("cursor", "pointer")

nodeUpdate.selectAll(".circleNode")
.attr("r", (d) => d.children ? 2.5 : 5)
.attr("cursor", (d) => d.children ? "pointer" : "auto")

// Update the node attributes and style
nodeUpdate.selectAll(".textLabels")
.attr("stroke", "white")
.attr("stroke-width", 0)
.attr("fill", (d) => d._children ? color(d.data.level) : 0)
.style("fill-opacity", (d) => d._children ? 1 : 0)
.each(function (d, i, n) {
if (d._children) {
const distance = d.children ? 2.5 : 5 + offset
const thisWidth = n[i].getComputedTextLength()
maxTextLength = Math.max(maxTextLength, d.y + thisWidth + distance)
}
})
.call(updateSvg)

// Remove any exiting nodes
const nodeExit = node.exit().transition()
.duration(duration / 2)
.attr("d", d3.linkRadial()
.angle((d) => source.x0)
.radius((d) => source.y0))
.remove()

// On exit reduce the node circles size to 0
nodeExit.select("circle")
.attr("r", 1e-6)
.attr("fill", "none")


// On exit reduce the opacity of text labels
nodeExit.selectAll(".textLabels")
.attr("fill", "none")

// ****************** links section ***************************

// Update the links...
const link = svg.selectAll("path.link")
.data(links, function (d) {
return d.target.data.data.id;
})

// Enter any new links at the parent"s previous position.
const linkEnter = link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", d3.linkRadial()
.angle((d) => source.x0)
.radius((d) => source.y0))
.attr("fill", "none")
.attr("stroke-width", 1)
.attr("stroke", "grey")

// UPDATE
const linkUpdate = linkEnter.merge(link);

// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.delay(800)
.attr("d", d3.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y))

// Remove any exiting links
link.exit().transition()
.duration(duration)
.attr("d", d3.linkRadial()
.angle((d) => source.x0)
.radius((d) => source.y0))
.remove();


// Store the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
})
}
updateNodes(root)
return svg.node()

}
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

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