Published
Edited
Feb 17, 2021
1 fork
Insert cell
md`# Week 2, Tuesday`
Insert cell
md`#### Line Plot of Covid *${choices.column}* for state *${choices.state}*.`
Insert cell
viewof state = select({
options: states,
value: "Florida",
description: "Choose state"
})
Insert cell
state
Insert cell
viewof choices = columns({
state: select({
options: states,
value: "Florida",
description: "Choose state"
}),
column: radio({
options: ["deaths", "cases"],
value: "cases",
description: "choose data column"
})
})
Insert cell
linePlot(stateData, 600, 300)
Insert cell
stateData = USdata.filter(d => d.state == choices.state).map(d => [
d.date,
d[choices.column]
])
Insert cell
stateData_1 = USdata.reduce((accmulator, currentElement) => {
if (currentElement.state == choices.state)
accmulator.push([currentElement.date, currentElement[choices.column]]);
return accmulator;
}, [])
Insert cell
md`#### Multi Line Plot of Covid *${
choices.column
}* for states: *${choices_multiline.join(", ")}*.`
Insert cell
viewof choices_multiline = columns([
select({
options: states,
value: "Florida",
description: "Choose state 1"
}),
select({
options: states,
value: "Georgia",
description: "Choose state 2"
}),
select({
options: states,
value: "New York",
description: "Choose state 3"
}),
select({
options: states,
value: "Washington",
description: "Choose state 3"
})
])
Insert cell
multilinePlot(
choices_multiline.map(s =>
USdata.filter(d => d.state == s).map(d => [d.date, d[choices.column]])
),
600,
400,
choices_multiline
)
Insert cell
multilinePlot = (valuesArrays, width, height, legends, areaFlag) => {
const svg = d3.select(DOM.svg(width, height));
//svg.append("rect").attr("width",width).attr("height",height).style("fill","none").style("stroke","black");

let W = width - 2 * margin,
H = height - 2 * margin;
//chart.append("rect").attr("width",W).attr("height",H).style("fill","none").style("stroke","black");

const xDomain = [
d3.min(valuesArrays, d => d3.min(d, c => c[0])),
d3.max(valuesArrays, d => d3.max(d, c => c[0]))
];

const xScale = d3
.scaleTime()
.domain(xDomain)
.range([0.5, W - 0.5])
.nice();
const yDomain = [
d3.min(valuesArrays, d => d3.min(d, c => c[1])),
d3.max(valuesArrays, d => d3.max(d, c => c[1]))
];
const yScale = d3
.scaleLinear()
.domain(yDomain)
.range([H - 0.5, 0.5])
.nice();

const colorScale = d3
.scaleOrdinal()
.domain(legends)
.range(d3.schemeCategory10);

const generator = areaFlag
? d3
.area()
.curve(curveType)
.defined(d => d[1])
.x(d => xScale(d[0]))
.y1(d => yScale(d[1]))
.y0(d => yScale(yDomain[0]))
: d3
.line()
.curve(curveType)
.defined(d => d[1])
.x(d => xScale(d[0]))
.y(d => yScale(d[1]));

const chart = svg
.append("g")
.attr("transform", `translate(${margin},${margin})`);

const lines = chart
.selectAll("path")
.data(valuesArrays)
.join("path")
.attr("d", generator)
.style("stroke", areaFlag ? "none" : (d, i) => colorScale(legends[i]))
.style("fill", areaFlag ? (d, i) => colorScale(legends[i]) : "none")
.style("opacity", areaFlag ? 0.4 : 1);
addLegend(
chart,
legends,
valuesArrays.map(d => [
xScale(d[d.length - 1][0]),
yScale(d[d.length - 1][1])
]),
legends.map(d => colorScale(d))
);
addAxesWithGridLines(chart, xScale, yScale, W, H);
return svg.node();
}
Insert cell
addAxesWithGridLines = (g, xScale, yScale, W, H) => {
g.append("g").call(d3.axisLeft(yScale).ticks(5, "s"));
g.append("g")
.attr("transform", `translate(0,${H})`)
.call(d3.axisBottom(xScale).ticks(5, d3.timeFormat("%x")));

// Grid lines
g.append("g")
.style("stroke-opacity", 0.1)
.call(
d3
.axisLeft(yScale)
.tickSize(-W)
.tickFormat("")
);
g.append("g")
.style("stroke-opacity", 0.1)
.call(
d3
.axisTop(xScale)
.tickSize(-H)
.tickFormat("")
);
}
Insert cell
md`#### Area Chart of Covid *${choices.column}* for state *${choices.state}*.`
Insert cell
areaPlot(stateData, 500, 300)
Insert cell
areaPlot = (values, width, height) => {
const svg = d3.select(DOM.svg(width, height));
//svg.append("rect").attr("width",width).attr("height",height).style("fill","none").style("stroke","black");

const chart = svg
.append("g")
.attr("transform", `translate(${margin},${margin})`);
let W = width - 2 * margin,
H = height - 2 * margin;
//chart.append("rect").attr("width",W).attr("height",H).style("fill","none").style("stroke","black");

const xScale = d3
.scaleTime()
.domain(d3.extent(values, d => d[0]))
.range([0.5, W - 0.5])
.nice();
const yDomain = d3.extent(values, d => d[1]);
const yScale = d3
.scaleLinear()
.domain(yDomain)
.range([H - 0.5, 0.5])
.nice();

const areaGenerator = d3
.area()
.defined(d => d[1])
.x(d => xScale(d[0]))
.y0(d => yScale(yDomain[0]))
.y1(d => yScale(d[1]));

chart
.append("path")
.datum(values)
.attr("d", areaGenerator)
.style("stroke", "black")
.style("fill", "steelblue");

chart.append("g").call(d3.axisLeft(yScale).ticks(5, "s"));
chart
.append("g")
.attr("transform", `translate(0,${H})`)
.call(d3.axisBottom(xScale).ticks(5, d3.timeFormat("%x")));

return svg.node();
}
Insert cell
viewof choices_twoArea = columns([
select({
options: states,
value: "Florida",
description: "Choose state 1"
}),
select({
options: states,
value: "New York",
description: "Choose state 3"
})
])
Insert cell
multilinePlot(
choices_twoArea.map(s =>
USdata.filter(d => d.state == s).map(d => [d.date, d[choices.column]])
),
600,
400,
choices_twoArea,
1
)
Insert cell
md`#### Difference area plot
`
Insert cell
md`#### Method 1: Difference area plot
Area plotting twice, once for postive differenc, once for negative difference`
Insert cell
Insert cell
twoStateDiffData = {
const twoStateData = choices_twoArea.map(s =>
USdata.filter(d => d.state == s).map(d => [d.date, d[choices.column]])
);
return twoStateData[0].map((d, i) => ({
date: d[0],
value0: d[1],
value1: twoStateData[1][i][1]
}));
}
Insert cell
Insert cell
curveType = d3[curveOption]
Insert cell
differencePlot_1(diffChartData, 900, 600, {
legends: diffColumnNames,
// pointSize: 3,
textAlongPath: { texts: diffColumnNames, shifts: [200, 700] }
})
Insert cell
differencePlot_1 = (dataArray, width, height, options) => {
const svg = d3.select(DOM.svg(width, height));
//svg.append("rect").attr("width",width).attr("height",height).style("fill","none").style("stroke","black");

const chart = svg
.append("g")
.attr("transform", `translate(${margin},${margin})`)
.datum(dataArray);
let W = width - 2 * margin,
H = height - 2 * margin;
//chart.append("rect").attr("width",W).attr("height",H).style("fill","none").style("stroke","black");

const xDomain = d3.extent(dataArray, d => d.date);
const yDomain = [
d3.min(dataArray, d => Math.min(d.value0, d.value1)),
d3.max(dataArray, d => Math.max(d.value0, d.value1))
];

const xScale = d3
.scaleTime()
.domain(xDomain)
.range([0.5, W - 0.5])
.nice();

const yScale = d3
.scaleLinear()
.domain(yDomain)
.range([H - 0.5, 0.5])
.nice();

const positiveAreaGenerator = d3
.area()
.curve(curveType)
.defined(d => d.value0 > d.value1)
.x(d => xScale(d.date))
.y0(d => yScale(d.value1))
.y1(d => yScale(d.value0));

const negativeAreaGenerator = d3
.area()
.curve(curveType)
.defined(d => d.value0 <= d.value1)
.x(d => xScale(d.date))
.y0(d => yScale(d.value0))
.y1(d => yScale(d.value1));
const line0Generator = d3
.line()
.curve(curveType)
.x(d => xScale(d.date))
.y(d => yScale(d.value0));
const line1Generator = d3
.line()
.curve(curveType)
.x(d => xScale(d.date))
.y(d => yScale(d.value1));

chart
.append("path")
.attr("d", negativeAreaGenerator)
.style("stroke", "none")
.style("fill", diffColors[0]);
chart
.append("path")
.attr("d", positiveAreaGenerator)
.style("stroke", "none")
.style("fill", diffColors[1]);

addPaths(
chart,
xScale,
yScale,
[
dataArray.map(d => [d.date, d.value0]),
dataArray.map(d => [d.date, d.value1])
],
options
);
if (options && options.legends)
addLegend(chart, options.legends, [
[
xScale(dataArray[dataArray.length - 1].date),
yScale(dataArray[dataArray.length - 1].value0)
],
[
xScale(dataArray[dataArray.length - 1].date),
yScale(dataArray[dataArray.length - 1].value1)
]
]);

addAxesWithGridLines(chart, xScale, yScale, W, H);

return svg.node();
}
Insert cell
addLegend = (g, texts, positions, colors) => {
const textLayer = g
.append("g")
.selectAll("g")
.data(positions)
.join("g");
textLayer
.append("text")
.attr("x", d => d[0])
.attr("dx", 5)
.attr("y", d => d[1])
.style("fill", "black")
.style("font-size", 10)
.style("fill", (d, i) => (colors ? colors[i] : "black"))
.text((d, i) => texts[i]);
if (colors)
textLayer
.append("circle")
.attr("r", 2.5)
.attr("cx", d => d[0])
.attr("cy", d => d[1])
.style("fill", "red");
}
Insert cell
{
const svgElement = html`<svg width= 200 height = 200 viewBox="0 0 100 100"/>`;
const svg = d3.select(svgElement);
addTextAlongApath(
svg,
"M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30",
"01234567"
);
return svgElement;
}
Insert cell
md`#### Method 2: Difference area plot
The chart makes use of *SVG:clipath*.
The **clippath** is:
> [a clipping path that restricts the region to which paint can be applied. Conceptually, parts of the drawing that lie outside of the region bounded by the clipping path are not drawn](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath).`
Insert cell
md` Clip Path`
Insert cell
{
const svgElement = html`<svg width= 200 height = 200 viewBox="0 0 20 20"/>`;
const svg = d3.select(svgElement);
svg
.append("clipPath")
.attr("id", "myClip")
.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5);
svg
.append("rect")
.attr("x", "5")
.attr("y", "5")
.attr("width", "8")
.attr("height", "8")
.style("stroke", "green")
.attr("clip-path", "url(#myClip)");
return svgElement;
}
Insert cell
drawDiff_2(diffChartData, 900, 600, {
legends: diffColumnNames,
pointSize: 3,
textAlongPath: { texts: diffColumnNames, shifts: [50, 650] }
})
Insert cell
drawDiff_2 = (dataArray, width, height, options) => {
const svg = d3.select(DOM.svg(width, height));
//svg.append("rect").attr("width",width).attr("height",height).style("fill","none").style("stroke","black");

const chart = svg
.append("g")
.attr("transform", `translate(${margin},${margin})`)
.datum(dataArray);

let W = width - 2 * margin,
H = height - 2 * margin;
//chart.append("rect").attr("width",W).attr("height",H).style("fill","none").style("stroke","black");
const xDomain = d3.extent(dataArray, d => d.date);
const yDomain = [
d3.min(dataArray, d => Math.min(d.value0, d.value1)),
d3.max(dataArray, d => Math.max(d.value0, d.value1))
];

const xScale = d3
.scaleTime()
.domain(xDomain)
.range([0.5, W - 0.5])
.nice();

const yScale = d3
.scaleLinear()
.domain(yDomain)
.range([H - 0.5, 0.5])
.nice();
if (options && options.legends)
addLegend(chart, options.legends, [
[
xScale(dataArray[dataArray.length - 1].date),
yScale(dataArray[dataArray.length - 1].value0)
],
[
xScale(dataArray[dataArray.length - 1].date),
yScale(dataArray[dataArray.length - 1].value1)
]
]);
chart
.append("clipPath")
.attr("id", "above")
.append("path")
.attr(
"d",
d3
.area()
.curve(curveType)
.x(d => xScale(d.date))
.y0(0)
.y1(d => yScale(d.value1))
);

chart
.append("clipPath")
.attr("id", "below")
.append("path")
.attr(
"d",
d3
.area()
.curve(curveType)
.x(d => xScale(d.date))
.y0(H)
.y1(d => yScale(d.value1))
)
.attr("fill", diffColors[0]);

chart
.append("path")
.attr("clip-path", "url(#above)")
.attr("fill", diffColors[1])
.attr(
"d",
d3
.area()
.curve(curveType)
.x(d => xScale(d.date))
.y0(H)
.y1(d => yScale(d.value0))
);

chart
.append("path")
.attr("clip-path", "url(#below)")
.attr("fill", diffColors[0])
.attr(
"d",
d3
.area()
.curve(curveType)
.x(d => xScale(d.date))
.y0(0)
.y1(d => yScale(d.value0))
);

addPaths(
chart,
xScale,
yScale,
[
dataArray.map(d => [d.date, d.value0]),
dataArray.map(d => [d.date, d.value1])
],
options
);
addAxesWithGridLines(chart, xScale, yScale, W, H);
return svg.node();
}
Insert cell
addPaths = (chart, xScale, yScale, data, options) => {
const path = chart
.append("g")
.selectAll("g")
.data(data)
.join("g");
path
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr(
"d",
d3
.line()
.curve(curveType)
.x(d => xScale(d[0])) // d.date
.y(d => yScale(d[1])) // d.value0
);
if (options.textAlongPath)
path.each(function(d, i) {
addTextAlongApath(
d3.select(this),
d3
.line()
.curve(curveType)
.x(d => xScale(d[0])) // d.date
.y(d => yScale(d[1])),
options.textAlongPath.texts[i],
options.textAlongPath.shifts[i]
);
});
if (options.pointSize)
path
.selectAll("circles")
.data(d => d)
.join("circle")
.attr("cx", d => xScale(d[0]))
.attr("cy", d => yScale(d[1]))
.attr("r", options.pointSize)
.style("fill", "none")
.style("stroke", "black");
}
Insert cell
addTextAlongApath = (g, dPath, text, shift) => {
const myId = DOM.uid("myPath");
g.append("defs")
.append("path")
.attr("id", myId.id)
// .style("fill", "blue")
// .style("stroke", "red")
.attr("d", dPath);
g.append("text")
.attr("x", shift)
.attr("dy", -5)
.append("textPath")
.attr("href", `#${myId.id}`)
.text(text);
}
Insert cell
md` Text Path`
Insert cell
{
const svgElement = html`<svg width= 200 height = 200 viewBox="0 0 100 100"/>`;
const svg = d3.select(svgElement);
svg
.append("defs")
.append("path")
.attr("id", "myPath")
.style("fill", "blue")
.style("stroke", "red")
.attr(
"d",
"M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30"
);
svg
.append("text")
.attr("y", 100)
.append("textPath")
.attr("href", "#myPath")
.text("012345679abcdefghijklmnopqrstucvwxyz");

return svg.node();
}
Insert cell
diffColumnNames = playFair.columns.slice(1)
Insert cell
playFair = d3.csv(
await FileAttachment("PlayFairExportImport.csv").url(),
d3.autoType
)
Insert cell
diffChartData = playFair
.filter(d => d.Year != null)
.map(d => ({
date: d3.timeParse("%Y")(d.Year),
value0: d.Imports,
value1: d.Exports
}))
Insert cell
md`### Bar Plot of Covid *${choices.column}* for state *${choices.state}* since the begin of the year.`
Insert cell
hbarPlot = {
const W = width - 2 * margin;
const H = height - 2 * margin;

const yScale = d3
.scaleBand()
.domain(dates)
.range([0, H])
.padding(0.1);
const xScale = d3
.scaleLinear()
.domain([0, d3.max(stateDataJan.map(d => d[1]))])
.range([0, W])
.nice();

const svgElement = DOM.svg(width, height);
const svg = d3.select(svgElement);
const plot = svg
.append("g")
.attr("transform", `translate(${margin}, ${margin})`);
plot
.selectAll("rect")
.data(stateDataJan)
.join("rect")
.attr("height", yScale.bandwidth())
.attr("width", d => xScale(d[1]))
.attr("y", d => yScale(d[0]))
.style("fill", "steelblue");

const xAxis = d3.axisTop(xScale);

const yAxis = d3.axisLeft(yScale).tickFormat(d3.timeFormat("%a %d"));

plot.append("g").call(xAxis);
plot.append("g").call(yAxis);

return svgElement;
}
Insert cell
vbarPlot = {
const W = width - 2 * margin;
const H = height - 2 * margin;

const xScale = d3
.scaleBand()
.domain(dates)
.range([0, W])
.padding(0.2);
const yScale = d3
.scaleLinear()
.domain([0, d3.max(stateDataJan.map(d => d[1]))])
.range([H, 0])
.nice();

const svgElement = DOM.svg(width, height);
const svg = d3.select(svgElement);
const plot = svg
.append("g")
.attr("transform", `translate(${margin}, ${margin})`);
const yAxis = d3
.axisLeft(yScale)
.tickSize(-W)
.tickFormat(d3.format(".0s"));
function customYAxis(g) {
g.call(yAxis);
g.select(".domain").remove();
g.selectAll(".tick line")
.style("stroke-opacity", 0.2)
.attr("stroke-dasharray", "2,2");
g.selectAll(".tick text").attr("dx", -2);
}
const xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%a %d"));

plot
.append("g")
.attr("transform", `translate(0,${H})`)
.call(xAxis);

plot.append("g").call(customYAxis);

plot
.selectAll("rect")
.data(stateDataJan)
.join("rect")
.attr("y", d => yScale(d[1]))
.attr("x", d => xScale(d[0]))
.attr("height", d => H - yScale(d[1]))
.attr("width", xScale.bandwidth())
.style("fill", "lightgray")
.append("title")
.text(d => `${d3.timeFormat("%x")(d[0])}: ${choices.column} ${d[1]}`);

return svgElement;
}
Insert cell
`translate(0,${height - margin})`
Insert cell
d3.timeFormat("%a %d")(fromDate)
Insert cell
dates.map(d => d3.timeFormat("%a %d")(d))
Insert cell
stateDataJan
Insert cell
fromDate = new Date("January 01, 2021")
Insert cell
today = new Date()
Insert cell
stateDataJan = stateData.filter(d => d[0] >= fromDate)
Insert cell
dates = d3.map(stateDataJan, d => d[0])
Insert cell
states = [...new Set(USdata.map(d => d.state))]
Insert cell
USdata = d3.csv(
"https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-states.csv",
d3.autoType
)
Insert cell
width = 600
Insert cell
height = 400
Insert cell
margin = 50
Insert cell
md`### Extenal Library and imports`
Insert cell
import { colors as diffColors } from "@d3/difference-chart"
Insert cell
import { linePlot } from "09ae345d3c79563a"
Insert cell
import {columns} from "@bcardiff/observable-columns"
Insert cell
import { select, radio } from "@jashkenas/inputs"
Insert cell
d3 = require("d3@6", "d3-regression")
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