Published
Edited
May 14, 2021
1 star
Insert cell
md`# Change in Inferred Distance Traveled`
Insert cell
colors
Insert cell
colorsReverse = ["#08306b", "#08519c", "#2171b5", "#4292c6", "#6baed6", "#9ecae1", "#c6dbef", "#deebf7", "#f7fbff"]
Insert cell
colorScaleQuantile= d3.scaleQuantile([d3.min(data.values), d3.max(data.values)], colors)
Insert cell
colorScaleQuantileReverse= d3.scaleQuantile([d3.min(data.values), d3.max(data.values)].reverse(), colorsReverse)
Insert cell
colorScaleQuantile(-0.02495722139243994)
Insert cell
colorsReverse
Insert cell

legend({
color: colorScaleQuantileReverse,
tickFormat: d3.tickFormat(-1, 1, 5, "+%"),
title: "Change in avg. distance traveled per trip (%, intra-district)"
})
Insert cell
viewof file = html`<select>
<option>week_end.csv
<option>week_day.csv
</select>`
Insert cell
changefebaprilpostinter
= {
const svg = d3.create("svg")
.attr("viewBox", [0,-70,975, 660])
.attr("text-anchor", "middle")
.attr("font-family", "Source Sans Pro")
.attr("font-size", 10)
.call(addWebFont);
/*svg.append("g")
.attr("transform", "translate(0,20)")
.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (50 / 2))
.attr("dy", -20)
.attr("text-anchor", "middle")
.style("font-size", "28px")
.style("font-weight", 600)
.text("Change in Intra-District travel between February and April");*/
svg.append("g")
.attr("transform", "translate(330,-60)")
.attr("dy", -20)
.append(() => legend({
color: colorScaleQuantileReverse,
tickFormat: d3.tickFormat(-1, 1, 5, "+%"),
title: "Change in avg. distance traveled per trip (%, inter-district)"
}));
svg.append("g")
.selectAll("path")
.data(topojson.feature(sl, sl.objects.district).features)
.join("path")
.attr("fill", d => colorScaleQuantileReverse(data.objects[d.properties['New_Dist']]))
.attr("d", path)
svg.append("path")
.datum(topojson.mesh(sl, sl.objects.district))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);
svg.append("g")
.selectAll("text")
.data(topojson.feature(sl, sl.objects.district).features)
.join("text")
.attr('style', "font-family: 'Source Sans Pro'; font-size: 14px;")
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("y", d => path.centroid(d)[1])
.attr("x", d => path.centroid(d)[0])
.text(d => d.properties['New_Dist'])
.clone(true).lower()
.attr("fill", "none")
.attr("stroke-width", 0)
.attr("stroke-linejoin", "round")
.attr("stroke", "white");
svg.append("g")
.selectAll("text")
.data(topojson.feature(sl, sl.objects.district).features)
.join("text")
.attr('style', "font-family: 'Source Sans Pro'; font-size: 12px;")
.attr("text-anchor", "middle")
.attr("y", d => path.centroid(d)[1])
.attr("dy", d => "1.2em")
.attr("dx", d => (d.properties['New_Dist'] == "Western Area Urb" | d.properties['New_Dist'] == "Western Area Rur") ? "-4em" : "0em")
.attr("x", d => path.centroid(d)[0])
.text(d => `${Number.parseInt(clamp(d, 3)*100)}%`)
.clone(true).lower()
.attr("fill", "none")
.attr("stroke-width", d => clamp(d, 4) <= -0.17 ? 0 : 0)
.attr("stroke-linejoin", "round")
.attr("stroke", "white");
return svg.node();
}
Insert cell
clamp = (a, c) => Number.parseFloat(data.objects[a.properties['New_Dist']]).toPrecision(c)
Insert cell
colorScaleQuantile(-8)
Insert cell
color = d3.scaleQuantize([0, 160], d3.schemeBlues[9])
Insert cell
topojson.feature(sl, sl.objects.district).features
Insert cell
ft = topojson.feature(sl, sl.objects.district)
Insert cell
path = d3.geoPath(d3.geoMercator().center([8.4844, 11.7744]).fitSize([975, 610], ft))
Insert cell
districts = new Map(sl.objects.district.geometries.map(d => [d.properties["New_Dist"], d.properties]))
Insert cell
sl = await FileAttachment("district-topo.json").json()
Insert cell
colors = d3.schemeBlues[9]//["#053F5D", "#43476B", "#824E7A", "#C05688", "#D37063", "#E68A3F", "#F9A41A"]//"#F1F1F2"

Insert cell
data = {
FileAttachment("relative_dist_heat_intra_pre_post.csv"), FileAttachment("relative_dist_heat_inter_pre_post.csv")
FileAttachment("change_feb_apr_intra@5.csv"), FileAttachment("change_apr_post_inter@6.csv"), FileAttachment("change_feb_apr_inter@5.csv"), FileAttachment("change_apr_post_intra@5.csv")
const data = d3.csvParse(await FileAttachment("change_feb_apr_inter@5.csv").text())
//const data2 = d3.csvParse(await FileAttachment("week_day.csv").text())
const values = data.map(d => +d["0"])
const names = data.map(d => d["New_Dist_ori"])
const objects = Object.assign({}, ...data.map(d => ({[d["New_Dist_ori"]]: +d["0"]})));

return {values, names, objects};
}
Insert cell
data
Insert cell
colorScaleLinear = d3.scaleLinear()
.domain([d3.min(data.values), d3.max(data.values)]).nice()
.range(colors)
Insert cell
j = d3.interpolate("purple", "orange")

Insert cell

colorScaleQuantize= d3.scaleQuantize([d3.min(data.values), d3.max(data.values)], colors)
Insert cell
accent = d3.scaleOrdinal(colors)
Insert cell
d3.schemeAccent
Insert cell
threshold = [-0.21359217773593073, -0.18513269368638288, -0.15667320963683506, -0.12821372558728722, -0.0997542415377394, -0.07129475748819158]
Insert cell
colorScaleQuantile(-8)
Insert cell
colors
Insert cell

customColorScaleQuantile= customQuantile([d3.min(data.values), d3.max(data.values)].reverse(), colorsReverse)
Insert cell
function customQuantile() {
var domain = [],
range = [],
thresholds = [],
unknown;

function rescale() {
var i = 0, n = Math.max(1, range.length);
thresholds = new Array(n - 1);
while (++i < n) thresholds[i - 1] = d3Array.quantile(domain, i / n);
return scale;
}

function scale(x) {
return isNaN(x = +x) ? unknown : range[d3Array.bisect(thresholds, x)];
}

scale.invertExtent = function(y) {
var i = range.indexOf(y);
return i < 0 ? [NaN, NaN] : [
i > 0 ? thresholds[i - 1] : domain[0],
i < thresholds.length ? thresholds[i] : domain[domain.length - 1]
];
};

scale.domain = function(_) {
if (!arguments.length) return domain.slice();
domain = [];
for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d);
domain.sort(d3Array.ascending);
return rescale();
};

scale.range = function(_) {
return arguments.length ? (range = Array.from(_), rescale()) : range;
};

scale.unknown = function(_) {
return arguments.length ? (unknown = _, scale) : unknown;
};

scale.quantiles = function() {
return thresholds.slice();
};

scale.copy = function() {
return customQuantile()
.domain(domain)
.range(range)
.unknown(unknown);
};

return initRange.apply(scale, arguments);
}
Insert cell
function initRange(domain, range) {
switch (arguments.length) {
case 0: break;
case 1: this.range(domain); break;
default: this.range(range).domain(domain); break;
}
return this;
}
Insert cell
function initInterpolator(domain, interpolator) {
switch (arguments.length) {
case 0: break;
case 1: {
if (typeof domain === "function") this.interpolator(domain);
else this.range(domain);
break;
}
default: {
this.domain(domain);
if (typeof interpolator === "function") this.interpolator(interpolator);
else this.range(interpolator);
break;
}
}
return this;
}
Insert cell
legend({
color: colorScaleQuantileReverse,
tickFormat: d3.tickFormat(-1, 1, 5, "+%"),
title: "Change in avg. distance traveled (%, weekday)"
})
Insert cell
function customLegend({
color,
title,
tickSize = 6,
width = 320,
height = 44 + tickSize,
marginTop = 18,
marginRight = 0,
marginBottom = 16 + tickSize,
marginLeft = 0,
ticks = width / 64,
tickFormat,
tickValues
} = {}) {

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.style("overflow", "visible")
.style("display", "block");

let tickAdjust = g => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height);
let x;
// Continuous
if (color.interpolate) {
const n = Math.min(color.domain().length, color.range().length);

x = color.copy().rangeRound(d3.quantize(d3.interpolate(marginLeft, width - marginRight), n));

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.copy().domain(d3.quantize(d3.interpolate(0, 1), n))).toDataURL());
}

// Sequential
else if (color.interpolator) {
x = Object.assign(color.copy()
.interpolator(d3.interpolateRound(marginLeft, width - marginRight)),
{range() { return [marginLeft, width - marginRight]; }});

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.interpolator()).toDataURL());

// scaleSequentialQuantile doesn’t implement ticks or tickFormat.
if (!x.ticks) {
if (tickValues === undefined) {
const n = Math.round(ticks + 1);
tickValues = d3.range(n).map(i => d3.quantile(color.domain(), i / (n - 1)));
}
if (typeof tickFormat !== "function") {
tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
}
}
}

// Threshold
else if (color.invertExtent) {
const thresholds
= color.thresholds ? color // scaleQuantize
: color.quantiles ? color.quantiles().reverse() // scaleQuantile
: color.domain(); // scaleThreshold

const thresholdFormat
= tickFormat === undefined ? d => d
: typeof tickFormat === "string" ? d3.format(tickFormat)
: tickFormat;

x = d3.scaleLinear()
.domain([-1, color.range().length - 1])
.rangeRound([marginLeft, width - marginRight]);

svg.append("g")
.selectAll("rect")
.data(color.range())
.join("rect")
.attr("x", (d, i) => x(i - 1))
.attr("y", marginTop)
.attr("width", (d, i) => x(i) - x(i - 1))
.attr("height", height - marginTop - marginBottom)
.attr("fill", d => d);

tickValues = d3.range(thresholds.length);
tickFormat = i => thresholdFormat(thresholds[i], i);
}

// Ordinal
else {
x = d3.scaleBand()
.domain(color.domain())
.rangeRound([marginLeft, width - marginRight]);

svg.append("g")
.selectAll("rect")
.data(color.domain())
.join("rect")
.attr("x", x)
.attr("y", marginTop)
.attr("width", Math.max(0, x.bandwidth() - 1))
.attr("height", height - marginTop - marginBottom)
.attr("fill", color);

tickAdjust = () => {};
}

svg.append("g")
.attr('style', "font-family: 'Source Sans Pro'; font-size: 12px;")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x)
.ticks(ticks, typeof tickFormat === "string" ? tickFormat : undefined)
.tickFormat(typeof tickFormat === "function" ? tickFormat : undefined)
.tickSize(tickSize)
.tickValues(tickValues))
.call(tickAdjust)
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr('style', "font-family: 'Source Sans Pro'; font-size: 12px;")
.attr("x", marginLeft)
.attr("y", marginTop + marginBottom - height - 6)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(title));

return svg.node();
}
Insert cell
function ramp(color, n = 256) {
const canvas = DOM.canvas(n, 1);
const context = canvas.getContext("2d");
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(i, 0, 1, 1);
}
return canvas;
}
Insert cell
url = "https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;600&display=swap"
Insert cell
res = await fetch(url)
Insert cell
res
Insert cell
async function addWebFont(selection) {
//const fontData = await toDataURL(fontURL);
return selection.append('style').text(`
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmhdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwkxdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmxdu3cOWxy40.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlBdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmBdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmRdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu3cOWxw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmhdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwkxdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmxdu3cOWxy40.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlBdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmBdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmRdu3cOWxy40.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v13/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlxdu3cOWxw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
`);
};

Insert cell
import {rasterize} from "@mbostock/saving-svg"
Insert cell
import {toDataURL} from "@mootari/embedding-fonts-into-an-svg@171"
Insert cell
import {getMetadata} from "@mootari/notebook-data"
Insert cell
topojson = require("topojson-client@3")
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
//{ascending, bisect, quantile }
d3Array = require("d3-array@2")
Insert cell
_ = require('lodash')
Insert cell
d3 = require("d3@5")
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