Published
Edited
Jun 5, 2020
Importers
Insert cell
Insert cell
// chartFn = {
// const chartChoices = [barChart, lolipopChart]
// return chartChoices[Math.floor(Math.random() * chartChoices.length)]
// }

chartFn= ({'barChart': horizontalBarChart,
'lolipopChart': horizontalLolipopChart,
'dotplotChart': horizontalDotPlot}[chartVariant])
Insert cell
horizontalBarChart= (data, options)=> {
options = options || {}
const axisInc = options["axisInc"] || 10
const spacing = options["spacing"] || 6
const title = options["title"] || ""
const description = options["description"] || ""
const lineHeight = 8
const xAxisTickFormatFn = options["xAxisTickFormatFn"] || ((x)=>(x))
const betweenSectionPadding = 0.1
const barFn = options["barFn"] || ((data, params) => {
const {x, y, margin} = params;
return data.map( (d,i) => (
svg`
<rect
fill="#A3A3A3"
x="${x(0)}"
y="${y(d.names[1])}"
height="${y.bandwidth() - margin.spacing}"
width="${x(d.value) - x(0)}"/>`))})
const margin = ({top: 30, right: 20, bottom: 0, left: 110, spacing: spacing})
const width = 320;
const height = 3*spacing * data.length;
const xMax = Math.ceil(d3.max(data, d => d.value) / 10.0) * 10;
const x = d3.scaleLinear()
.domain([0, xMax])
.range([margin.left, width - margin.right]);

const axisTop= d3.axisTop(x).tickFormat( xAxisTickFormatFn )
const xAxis = (g) => (
g
.attr("transform", `translate(0,${margin.top-margin.spacing})`)
.call(axisTop.tickValues(d3.range(0, xMax+1, axisInc)))
.call(axisTop.tickSizeOuter(0).tickSize(4))
.call(g => (g.selection ? g.selection() : g).select(".domain").remove()))


const y0Values = Array.from(new Set(data.map(d => d.names[0])))
const y0 = d3.scaleBand()
.paddingInner(betweenSectionPadding)
.domain(y0Values)
.range([margin.top, height-margin.bottom])
// .align(0);
const axisLeft1 = d3.axisLeft(y0)
const y0Axis = (g) => g
.attr("transform", `translate(${margin.spacing},
${-y0.step()*0.5 + (y0.step()*betweenSectionPadding*0.5) + margin.spacing*0.7 })`)
.call(axisLeft1.tickSize(0))
.call(g => (g.selection ? g.selection() : g).selectAll("text").style("text-anchor", "start"))
.call(g => (g.selection ? g.selection() : g).select(".domain").remove())

const background = y0Values.map( (n,i) => {
if (i!==0) {
return svg`
<line
fill="#F6F6F600"
stroke="#E3E3E3"
x1="${-2}"
y1="${y0(n)-(y0.step()*betweenSectionPadding + margin.spacing)*0.5}"
x2="${width+4}"
y2="${y0(n)-(y0.step()*betweenSectionPadding + margin.spacing)*0.5}"
/>`}})


const rollup= d3.nest()
.key(d=>d.names[0])
.object(data)

const sections = Object.keys(rollup).map(name0=>{

const yValues = Array.from(new Set(rollup[name0].map(d => d.names[1])))
const y = d3.scaleBand()
// .padding(0).paddingOuter(margin.spacing / y0.bandwidth())
.domain(yValues)
.range([0, y0.bandwidth()]);

// return y0.bandwidth()

const axisLeft = d3.axisLeft(y).tickFormat((x)=>x)
const yAxis = (g) => (
g
.attr("transform", `translate(${margin.left-margin.spacing*0.5},
${-margin.spacing*0.5})`)
.call(axisLeft.tickSizeOuter(0).tickSize(0))
.call(g => (g.selection ? g.selection() : g).select(".domain").remove())
)

return svg`
<g transform="translate(${0}, ${y0(name0)})">
${barFn(rollup[name0], {x, y, margin, width})}
${d3.select(svg`<g>`).call(yAxis).node()}
</g>`})

// return sections
return html`
<style>
.tick line {
stroke-width: 1;
stroke: #A3A3A3;
}
</style>
<div style="background-color: #FCFCFC; max-width: ${width}px;">
<h2>${title}</h2>
<span>${description}</span>
<br/>
<svg viewBox="0 0 ${width} ${height}" style="max-width: ${width}px;">
<g class="bars">
${background}
${sections}
</g>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(y0Axis).node()}
</svg>
</div>`
}
Insert cell
horizontalLolipopChart = (data, params) => {

const lolipopBarFn = ((data, params) => {
const {x, y, margin} = params;
return data.map( (d,i) => (
svg`
<rect
fill="#A3A3A3"
x="${x(0)}"
y="${y(d.names[1])+y.step()*0.25}"
height="${2}"
width="${x(d.value) - x(0)}"/>
<circle
fill="#777"
cx="${x(d.value)}"
cy="${y(d.names[1])+y.step()*0.25+1}"
r="4"/>`))})
params= params || {}
params['barFn'] = params['barFn'] || lolipopBarFn
return horizontalBarChart(data, params)
}
Insert cell
horizontalDotPlot = (data, params) => {

const dotPlotBarFn = ((data, params) => {
const {x, y, width, margin} = params;

return data.map( (d,i) => (
svg`
<line
stroke="#BBB"
stroke-width="1"
stroke-dasharray="6 3"
x1="${x(0)}"
y1="${y(d.names[1])+y.step()*0.25}"
x2="${x(d3.max(x.domain()))}"
y2="${y(d.names[1])+y.step()*0.25}"
height="${3}"
width="${x(d.value) - x(0)}"/>
<circle
fill="#777"
cx="${x(d.value)}"
cy="${y(d.names[1])+y.step()*0.25}"
r="4"/>`))})
params= params || {}
params['barFn'] = params['barFn'] || dotPlotBarFn
return horizontalBarChart(data, params)
}
Insert cell
Insert cell
chartFn(data, {"title": "Hello World", "description": "I'm a description of this chart"})
Insert cell
horizontalDotPlot(topSellingTeachers, {"title": "Hello World", "description": "I'm a description of this chart", spacing: 8})
Insert cell
horizontalLolipopChart(topSellingTeachers, {axisInc: 20, spacing: 8, title: "Lolipop Chart"})
Insert cell
chartFn(kidsData)
Insert cell
Insert cell
Insert cell
Insert cell
isNumber=(x)=>{
return !isNaN(parseFloat(x)) && isFinite(x)
}

Insert cell
isArray=(x)=>{
return Array.isArray(x)
}
Insert cell
d3 = require("d3@5")
Insert cell
import { radio } from "@meetamit/multiple-choice-inputs"
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