Published
Edited
Mar 14, 2020
Insert cell
Insert cell
parsedDocs = ({
name: '__root',
description: '',
children: Array.from(docsMD.matchAll(/^## \[[\w -]+ \((d3-.+)\)\].+(((\n?\n(?!^## )).+)+)/gm))
.map(m => {
const libName = m[1]
const haystack = m[2].trim()
const libDescriptionMatch = haystack.match(/([^*](.+\n)+)/m)
const libDescription = libDescriptionMatch.index === 0 ? libDescriptionMatch[1].trim() : ''
const subcategories = Array.from(haystack.matchAll(/^### \[?([\w ()-]+)\]?.{0,}(((\n?\n(?!^### )).+)+)/gm))
.map(m => {
const subcategoryName = m[1];
const haystack = m[2].trim()
const subDescriptionMatch = haystack.match(/^(?!\*)((.+\n)+)/m)
const subDescription = subDescriptionMatch !== null ? subDescriptionMatch[1].trim() : ''
return { subcategoryName, subDescription, haystack }
})
const subsubcategories = (
subcategories.length !== 0
? subcategories
: [{ subcategoryName: "__general", subDescription: "", haystack }]
).map(cat => {
const { haystack } = cat
const functions = Array.from(haystack.matchAll(/^\* \[(d3\.[\w]+)\]\(.+\) -( (.+)){0,}((\n(?!\* \[d3).+){0,})/gm))
.map(m => {
const haystack = m[4].trim()
const children = Array.from(haystack.matchAll(/\* \[\*([\w]+)\*(\.[\w]+)?\]\(.+\) -( (.+))?/gm))
.map(m => ({ name: m[1]+(m[2] || ''), description: m[4] || '', children: Array() }))
return { name: m[1], description: m[3] || '', children, }
})
return { children: functions, name: cat.subcategoryName, description: cat.subDescription, m: haystack }
})
return { name: libName, description: libDescription, children: subsubcategories }
})
})
Insert cell
Insert cell
{
const height = 700;
const margin = { top: 20, left: 20, right: 20, bottom: 20 }
const innerWidth = width - margin.right - margin.left
const innerHeight = height - margin.top - margin.bottom
const svg = d3.select(DOM.svg(width, height))
.attr('overflow', 'hidden')
const tree = d3.tree()
.size([innerHeight, innerWidth])
const root = d3.hierarchy(parsedDocs)
const links = tree(root).links()
const descendants = tree(root).descendants()
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
const xValue = d => d.y
const yValue = d => d.x
const titleValue = d => d.data.name;
g.selectAll('path').data(links).enter()
.append('path')
.attr('class', 'link')
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('d', d3.linkHorizontal().x(xValue).y(yValue))
g.selectAll('circle').data(descendants).enter()
.append('circle')
.attr('class', 'point')
.attr('r', 5)
.attr('cx', xValue)
.attr('cy', yValue)
.append('title')
.text(titleValue)
const zoom = d3.zoom()
.on('zoom', handleZoomTree)
g.call(zoom)
.on('wheel.zoom', null)
return svg.node()
}
Insert cell
handleZoomTree = function() {
d3.select(this).attr('transform', d3.event.transform)
}
Insert cell
Insert cell
{
const height = 800;
const margin = { top: 20, left: 20, right: 20, bottom: 20 }
const innerWidth = width - margin.right - margin.left
const innerHeight = height - margin.top - margin.bottom
const svg = d3.select(DOM.svg(width, height))
.attr('overflow', 'hidden')
const sizeValue = d => d.children.length + 1
const colorValue = d => d.height
const nameValue = d => d.data.name !== '__general' ? d.data.name : d.parent.data.name
const titleValue = d => nameValue(d) +
( d.data.description !== '' ? '\n' + d.data.description : '' ) +
'\n' + d.ancestors().reverse().map(d => d.data.name).filter(s => s.indexOf('__') !== 0).join('/')
const filterLabel = d => d.height === 3
const colorScale = d3.scaleOrdinal()
.domain(d3.hierarchy(parsedDocs).descendants().map(colorValue))
.range(d3.schemeBlues[5])
const root = d3.hierarchy(parsedDocs)
.sum(sizeValue)
.sort((a, b) => b.value - a.value)
const pack = d3.pack()
.size([innerWidth, innerHeight])
.padding(3)
const packRoot = pack(root)
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
const nodes = g.selectAll('g')
.data(d3.nest().key(d => d.height).entries(packRoot.descendants()))
.join('g')
.selectAll('g').data(d => d.values, d => (d.nodeUid = DOM.uid('node')).id)
.join('g')
.attr('id', d => d.nodeUid.id)
.attr('transform', d => `translate(${d.x},${d.y})`)
nodes.append('circle')
.attr('fill', d => colorScale(colorValue(d)))
.attr('r', d => d.r)
.append('title')
.text(titleValue)
const pathGenerator = d3.arc()
.startAngle(- Math.PI / 2)
.endAngle(Math.PI / 2)
const labels = g.selectAll('.pathLabel').data(packRoot.descendants().filter(filterLabel)).enter().append('g')
.attr('class', 'pathLabel')
.attr('transform', d => `translate(${d.x},${d.y})`)
labels.append('path')
.attr('d', d => pathGenerator({ outerRadius: d.r }))
.attr('id', d => (d.pathUid = DOM.uid('path')).id)
.attr('fill', 'none')
labels.append('text')
.attr('font-size', d => d.r / 4)
.append('textPath')
.attr('href', d => "#" + d.pathUid.id)
.style("text-anchor","middle")
.attr("startOffset", "50%")
.text(nameValue)

const zoom = d3.zoom()
.on('zoom', handleZoomPack)
g.call(zoom)
.on('wheel.zoom', null)
return svg.node()
}
Insert cell
handleZoomPack = function() {
d3.select(this).attr('transform', d3.event.transform)
}
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more