Published
Edited
May 4, 2021
1 fork
Importers
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function TargetVsActuals(
data, {
date = "date", // the field name for the date
target = "target",
actual = "actual",
width=700,
height=250,
label="Targets vs. Actuals",
...options}) {
data = data.map(d => ({
date: new Date(maybeAccessor(date)(d)),
target: maybeAccessor(target)(d),
actual: maybeAccessor(actual)(d)
}))
return Plot.plot({
width,
height,
x: {tickFormat: dateFormat, label: null},
y: {tickFormat: d3.format(".2s"), label, grid:true},
marks: [
Plot.barY(data, {x: "date", y: "actual", fill: "steelblue"}),
Plot.barY(data.filter(d => d.target > 0).filter(d => d.actual > d.target), {x: "date", y2: "actual", y1: "target", fill: "green"}),
Plot.barY(data.filter(d => d.actual < d.target), {x: "date", y: "target", stroke: "#999", fill: "none"}),
Plot.tickY(data.filter(d => d.actual > d.target), {x: "date", y: "target", stroke: "white"}),
Plot.text(data, {
x: "date",
y: d => Math.max(d.actual, d.target),
text: d => d.actual && d.target ? d3.format(".0%")(d.actual / d.target) : "",
dy: -5
}),
Plot.ruleY([0])
],
...options
})
}
Insert cell
PercentGrowth = ({
data=[{target:10, actual:8}, {target:12, actual:9}],
title="Monthly growth",
width=700,
height=20,
titleWidth=100}) => {
const perc = (a, b) => (b-a)/a;
const target=perc(data[0].target, data[1].target), actual=perc(data[0].actual, data[1].actual);
const wTitle=titleWidth, wLegend=117, wActual=140;
const legendSize=(height-3)/2;
const wBar=width-wTitle-wActual-wLegend;
const sign = actual>=0 ? 1 : -1;
const range = actual>=target? actual : (actual>=0) ? target : (target-actual);
const xScale = (x) => (x - (actual<0 ? actual : 0))/range * wBar;
const opacity = 0.3;
const style = svg.fragment`<style>
.small, .legendTitle{ font: 10px sans-serif; font-weight:600; }
.legendTitle {font-weight:300; }
.title { font: 14px sans-serif; color: white}
.big { font: 14px sans-serif; color: white}
</style>`

const Title = svg.fragment`<text y=${height/2} dominant-baseline="central" class="title">${title}</text>`
const Actuals = svg.fragment`
<text x=${width-wActual} y=${height/2} dominant-baseline="central" class="legendTitle">actuals</text>
<text x=${width} y=${height/2} dominant-baseline="central" class="big" text-anchor="end">${d3.format(".3s")(data[0].actual)} → ${d3.format(".3s")(data[1].actual)}</text>`

const Target = svg.fragment`<rect x=${wTitle+xScale(0)} width=${xScale(target)-xScale(0)} height=${height} style="fill:none; stroke:gray; stroke-width:1; stroke-opacity:0.3;" />
<line x1=${wTitle+xScale(target)} x2=${wTitle+xScale(target)} y2=${height} style="stroke:black; stroke-width:2; stroke-opacity:0.9;" />`
const Actual = svg.fragment`<g transform="translate(${wTitle},0)">
<polygon points="
${xScale(0)},0
${xScale(actual)-sign*height/2},0
${xScale(actual)},${height/2}
${xScale(actual)-sign*height/2},${height}
${xScale(0)},${height}"
style="fill:${actual>=0 ? "steelblue" : "red"}; fill-opacity:${opacity};" />
<polyline points="
${xScale(actual)-sign*height/2},0
${xScale(actual)},${height/2}
${xScale(actual)-sign*height/2},${height}"
fill=none stroke=${actual>=0 ? "steelblue" : "red"} style="stroke-width:2;" />
</g>`

const Percent = svg.fragment`
<text x=${wTitle+50} y=${height/2} dominant-baseline="central" text-anchor="end" class="big">${d3.format(".0%")(actual/target)}</text>
<text x=${wTitle+55} y=${height/2} dominant-baseline="central" class="legendTitle">of target growth rate</text>`

const Legend = svg.fragment`<g transform="translate(${wTitle+wBar+30},0)">
<rect width=${legendSize} height=${legendSize} style="fill:none; stroke:gray; stroke-width:1; stroke-opacity:0.3;" />
<rect y=${legendSize+3} width=${legendSize} height=${legendSize} style="fill:${actual>=0 ? "steelblue" : "red"}; stroke:gray; stroke-width:1; fill-opacity:${opacity}; stroke-opacity:0.3;" />
<text x=${legendSize+3} y=${legendSize/2} dominant-baseline="central" class="legendTitle">target</text>
<text x=${legendSize+3} y=${3 + 3*legendSize/2} dominant-baseline="central" class="legendTitle">actual</text>
<text x=${legendSize+60} y=${legendSize/2} dominant-baseline="central" text-anchor="end" class="small">${d3.format(".0%")(target)}</text>
<text x=${legendSize+60} y=${3 + 3*legendSize/2} dominant-baseline="central" text-anchor="end" class="small">${d3.format(".0%")(actual)}</text>
</g>`
return html`<svg width=${width} height=${height} style="border: 0px solid">
${style} ${Title} ${Percent} ${Actuals} ${Target} ${Legend} ${Actual} </svg>`
}
Insert cell
Insert cell
Insert cell
data = Object.assign(d3.csvParse(await FileAttachment("demo@4.csv").text(), d3.autoType))
Insert cell
margin = ({top: 10, right: 10, bottom: 20, left: 40})
Insert cell
width = 700
Insert cell
height = 250
Insert cell
Plot = require("@observablehq/plot@0.1.0/dist/plot.umd.min.js")
Insert cell
d3=require("d3@6")
Insert cell
svg=htl.svg
Insert cell
html = htl.html
Insert cell
htl = require("htl@0.2.5/dist/htl.min.js")
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