Published
Edited
Dec 8, 2020
Insert cell
Insert cell
ticks = Array.from({ length: 900 }, (_, i) => i)
Insert cell
tick
Insert cell
Insert cell
filterMod = 100
Insert cell
Insert cell
thresholds = 100
Insert cell
// contourColor = d3.scaleSequential(d3.interpolateWarm)
// contourColor = d3.scaleSequential(d3.interpolateCool)
// contourColor = d3.scaleSequential(d3.interpolateRainbow)
contourColor = d3.scaleSequential(d3.interpolateTurbo)
Insert cell
Insert cell
map = {
const canvas = d3
.create("canvas")
.attr("width", mapWidth)
.attr("height", mapHeight)
.node();
canvas.width = mapWidth;
canvas.height = mapHeight;
let ctx = canvas.getContext("2d");
scaleCanvas(canvas, ctx);
ctx.fillStyle = "rgba(5,5,5,1)";
ctx.fillRect(0, 0, mapWidth, mapHeight);

const path = d3.geoPath(projection, ctx);

// ctx.save();
// ctx.beginPath(),
// path(land),
// (ctx.strokeStyle = "rgba(255, 255, 255, 0.2 )"),
// ctx.stroke();
// ctx.restore();

// DRAW TICK
draw(ctx, path);
// svg.append("g").attr("transform", "translate(${height},20)");
// .append(() => legend({ ramp, title: data.title, width: 260 }));

return canvas;
}
Insert cell
mutable debug = {
}
Insert cell
JSON.stringify(debug)
Insert cell
md`

0.2, 0.4, 0.6, 0.8`
Insert cell
customThreshold = [0.1, 2, 4, 12, 32, 38, 58, 60, 80, 90, 180, 206]
Insert cell
Insert cell
Insert cell
Insert cell
function draw(ctx, path) {
let tx = -0;
let ty = -0;
let scale = .01;

// ctx.beginPath(), path(land), ctx.clip();

let contourPath = d3.geoPath(contourProjection).context(ctx);
circleContours.forEach((contour, i) => {
const threshold = contour.value;
ctx.beginPath();
// ctx.fillStyle = "rgba(255,255,255,0.25)";
// ctx.strokeStyle = "rgba(255,255,255,0.25)";

let fill = contourColor(contourNormalize(i));
ctx.fillStyle = fill;

contourPath(contour);
// ctx.stroke();
ctx.fill();
});

// circles.forEach(d => {
// // if (d.longitude > lngFilter * 2 - 160) return false;
// if (!d.longitude || !d.latitude || !d) return false;
// const loc = projection([d.longitude, d.latitude]);
// if (!loc) return null;
// const [x, y] = loc;
// // const x = width / 2;
// // const y = height / 2;

// // let r = 2.5 * (sliceFilter * 0.01);
// // if (r < 1) r = 1;
// // let r = d.frames(tick) * 2;
// let r = 0.25;
// let fill = d.colorFrames(tick);

// // ctx.fillStyle = "rgba(225, 225, 225, 1)";
// ctx.fillStyle = weightColor(fill);
// //ctx.strokeStyle = "rgba(225, 225, 225, 0.15)";
// ctx.lineWidth = 1;
// // ctx.fillRect(x, y, 2, 2);
// ctx.beginPath();
// // ctx.arc(x, y, 2, 0, 2 * Math.PI);
// // ctx.stroke();
// ctx.arc(x, y, r, 0, 2 * Math.PI);
// ctx.fill();
// });
}
Insert cell
contour = d3
.contourDensity()
.x(d => d.x)
.y(d => d.y)
.weight(d => 10 - d.metricNormal)
// .thresholds(thresholds)
.thresholds(function(c) {
let tz = thresholds; // the number of thresholds to evenly space by (usually configured)
var stop = d3.max(c);
tz = d3.tickStep(0, stop, tz);
tz = d3.range(0, Math.floor(stop / tz) * tz, tz);
tz.shift();

// Observable's "console.log", you can watch the value change above in real-time
mutable debug = tz;

// return tz;
return customThreshold;
// return tz;
})
// .thresholds([0, 100, 500, 1000, 5000, 10000])
// .cellSize(25)
.size([mapWidth, mapHeight])
Insert cell
circleContours = contour(circles)
// .filter((d, i) => {
// if (i % filterMod === 0) return true;
// else return false;
// })
Insert cell
contourNormalize = d3
.scaleLinear()
.domain([0, circleContours.length])
.range([0, 0.45])
Insert cell
circles = slicedMonthlyData
.sort((a, b) => {
//return +a.longitude - +b.latitude;
return +b['30DayActiveHH'] - +a['30DayActiveHH'];
// return +a['30DayActiveHH'] - +b['30DayActiveHH'];
})
.map((c, i) => {
// Add for each one, some condition where we're gonna say like
// where in the life cycle this circle is
// where in the animation does this circle kick in

const metricRaw = +c['30DayActiveHH'];
const metricNormal = metricNormalize(metricRaw);
// const metricNormal = metricRaw;

const loc = projection([c.longitude, c.latitude]);
const [x, y] = loc;

let start = Math.round(
(i * (ticks.length - 50)) / slicedMonthlyData.length
);
return {
...c,
x,
y,
metricRaw,
metricNormal,
frames: keyframe([
[0, 0],
[start, 0.25],
[start + 10, metricNormal * 0.5],
[start + 50, metricNormal * 1],
[240, metricNormal * 1]
]),
colorFrames: keyframe([
[0, metricNormal * 0.25],
[start, metricNormal * 0.5],
[start + 10, metricNormal * 0.8],
[start + 50, metricNormal * 1],
[240, metricNormal * 1]
])
};
})
Insert cell
projection = d3
// .geoAlbersUsa()
// .geoMiller()
.geoEquirectangular()
// .geoInterruptedMollweideHemispheres()
// .geoVanDerGrinten2()
//.translate([width / 2, height / 2])
// .scale([width * 0.2])
.fitWidth(mapWidth * 0.98, land)
.translate([mapWidth / 2, mapHeight / 2])
Insert cell
Insert cell
// slicedMonthlyData = cachedGroupedMonthlyData[i][1].slice(0, tick * 100)
slicedMonthlyData = cachedGroupedMonthlyData[i][1].slice(0, tick * tickAmp)
Insert cell
contourProjection = d3.geoIdentity()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// html`<div>
// ${chance
// .shuffle(circles)
// .slice(0, 25)
// .map(d => {
// return drawKeyframes(d.frames.frames);
// })}
// </div>
// `
Insert cell
circles[1]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mapHeight = 2048 / 4
Insert cell
mapWidth = 4096 / 4
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// d3.csv(
// 'https://d1fir59vsllmre.cloudfront.net/daily_20M_active_hh_device_counts%2Fanchor_date%3D2020-11-23%2Fts%3D2020-11-23%2010%253A00%253A00%2Fpart-00000-tid-2060449147669428046-19d3d7a6-f1c5-4427-b145-a951e262f029-627022-41.c000.csv'
// )
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