Unlisted
Edited
Jan 6, 2022
1 fork
Importers
4 stars
Insert cell
Insert cell
Insert cell
CHOROPLETH = Plot.carto({
projection: "equalEarth",
rotate: [-10, 0],
color: { scheme: "rdylgn" },
marks: [
Plot.feature({type: "Sphere"}, { fill: "lightblue" }),
Plot.feature(d3.geoGraticule10(), { stroke: "#fff", strokeWidth: .25 }),
Plot.features(countries.features,
{
fill: d => d.properties.income_grp,
strokeWidth: .25,
stroke: "#000",
title: d => d.properties?.name_fr
}),
Plot.feature({type: "Sphere"}, { strokeWidth: 2 }),
],
width: width,
height: width / 2,
marginLeft: 1,
marginRight: 1
})
Insert cell
Insert cell
data = [{type:"Point", coordinates: [0,0]}, {type:"Point", coordinates: [0,1]}]
Insert cell
LATLON = Plot.carto({
projection: "mercator",
marks: [
// 🚀 the very first mark defines the region of interest (TODO: design this)
Plot.feature({type: "Sphere"}, { fill: "none" }),
Plot.feature(land, { fill: "black" }),
// transform 1: dataset to a single MultiPoint
Plot.features([
{lat: 15, lon: 20}, {lat: -10, lon: 24.4}, {lon: -100, lat: 2}
], {
transform: (data, facets) => ({
facets,
data: ([{type: "MultiPoint", coordinates: data.map(({lon, lat}) => [lon, lat])}]),
fill: "red"
})}),

// transform 2: dataset to as many Points
Plot.features([
{lat: -15, lon: -20, name: "Cape Town"}, {lat: 10, lon: -24.4, name:"Moscow"}
], {
transform: (data ,facets) => ({
facets,
data: data.map(({lon, lat, name}) => ({type:"Point", name, coordinates: [lon, lat]}))
}),
fill: "green",
title: "name",
}),

// transform 3: dataset to polygon
Plot.features([
{lat: -5, lon: -10}, {lat: 8, lon: -12.4}, {lat: -7, lon: 80},
], {
transform: (data, facets) => ({
facets,
data: [{
type: "Polygon",
// the GeoJSON polygon needs to be closed, so we push point 0 at the end of the ring
coordinates: [ data.slice().concat(data[0]).map(({lon, lat}) => [lon, lat]) ],
properties: { hello: "Hello I'm a polygon" }
}],
fill: "rgba(100, 255, 100, 0.3)", stroke: "lime", title: d => d.properties.hello
})}),

Plot.feature({type: "Sphere"}, { strokeWidth: 2 }),
],
width: 600,
height: 600,
marginLeft: 1,
marginRight: 1,
marginTop: 1,
marginBottom: 1
})
Insert cell
Insert cell
ORTHO = {
const svg = Plot.carto({
projection: "orthographic",
marks: [
Plot.feature({type: "Sphere"}, { fill: "none" }),
Plot.feature(land, { fill: "black" }),
Plot.feature({type: "Sphere"}, { strokeWidth: 2 }),
],
width: 600,
height: 600,
marginLeft: 1,
marginRight: 1,
marginTop: 1,
marginBottom: 1
});
do {
svg.projection.rotate([Date.now() / 100, -35]);
svg.update();
yield svg;
} while (animate);
}
Insert cell
Plot.carto({
projection: "bertin1953",
r: { range: [0, 15] }, // 🌶 rescaling; the default r is quite large for this dataset
marks: [
Plot.feature({type: "Sphere"}, { fill: "none" }),
Plot.feature(land, { fill: "black" }),
Plot.points(largeCities, {
lonLat: d => [d.lng, d.lat],
fill: "orange",
fillOpacity: 0.7,
r: "population"
}),
Plot.feature({type: "Sphere"}, { strokeWidth: 2 }),
],
width,
height: width * 0.7,
marginLeft: 1,
marginRight: 1,
marginTop: 1,
marginBottom: 1
})
Insert cell
us = d3.json("https://unpkg.com/us-atlas@3/counties-10m.json")
Insert cell
topojson = require("topojson-client@3")
Insert cell
Plot.carto({
projection: d3.geoAlbersUsa(),
r: { domain: [30000, 1e5], type: "log" },
color: { type: "log", scheme: "rdbu" },
marks: [
Plot.feature(topojson.feature(us, us.objects.nation)),
Plot.feature(topojson.feature(us, us.objects.counties), { strokeWidth: 0.15 }),
Plot.feature(topojson.feature(us, us.objects.states), { strokeWidth: 0.5 }),
Plot.points(largeCities,
{
filter: d => d.country === "United States",
lonLat: d => [d.lng, d.lat],
fill: "population",
fillOpacity: 0.7,
title: "city",
r: "population"
}
),
],
width,
height: width * 0.7,
marginLeft: 0,
marginRight: 0,
marginTop: 0,
marginBottom: 0
})
Insert cell
{
const us = await FileAttachment("states-albers-10m.json").json();
const states = topojson.mesh(us, us.objects.states, (a,b) => a !== b);
const nation = topojson.feature(us, us.objects.nation);

const projection = d3.geoAlbersUsa().scale(1300).translate([487.5, 305]);
const parse = d3.utcParse("%m/%d/%Y");
const walmart = await d3.tsv(await FileAttachment("walmart.tsv").url(), d => ({
pos: projection(d),
year: parse(d.date).getFullYear(),
decade: Math.floor(parse(d.date).getFullYear() / 10) * 10
})).then(data => data.map(d => ({ ...d.pos, ...d })));
const decades = d3.groupSort(walmart, v => v[0].decade, d => d.decade);

return Plot.carto({
facet: {
data: decades,
y: decades
},
fy: {
tickFormat: d => `${d}’s`
},
projection: "identity",
marks: [
Plot.feature(nation, { fill: "#f4f4f4", stroke: "black" }),
Plot.feature(states, { strokeWidth: 0.5 }),
Plot.features(decades, {
geometry: decade => ({
type: "MultiPoint",
coordinates: walmart.filter(d => d.decade < decade).map(d => d.pos)
}),
fill: "black",
stroke: "#ccc",
strokeWidth: 0.5,
r: 2.5
}),
Plot.features(decades, {
geometry: decade => ({
type: "MultiPoint",
coordinates: walmart.filter(d => d.decade == decade).map(d => d.pos)
}),
fill: "red",
stroke: "#fff",
strokeWidth: 0.5,
r: 2.5
})
],
width: width,
height: width * 3,
marginLeft: 70,
marginRight: 1,
marginTop: 1,
marginBottom: 1
});
}
Insert cell
mutable debug = {}
Insert cell
land = d3.json("https://unpkg.com/visionscarto-world-atlas@0.0.6/world/110m_land.geojson")
Insert cell
import {largeCities} from "@fil/world-cities-urquhart"
Insert cell
countries = d3.json("https://unpkg.com/visionscarto-world-atlas@0.0.6/world/110m_countries.geojson")
Insert cell
makeCarto = (Plot) => {
Plot.carto = ({ projection = "equalEarth", rotate, ...options }) => {
const svg = d3.select(Plot.plot(options));

// the projection is then set according to the first carto mark
if (typeof projection === "string") {
projection = d3[
"geo" + projection[0].toUpperCase() + projection.slice(1)
]();

svg
.select("g.carto")
.each(function ({
width,
height,
marginTop,
marginRight,
marginBottom,
marginLeft
}) {
const features = d3.select(this).selectChildren("g").datum().G;
const geo = features.find((d) => d.type === "Sphere")
? { type: "Sphere" }
: {
type: "FeatureCollection",
features: features.flatMap((d) => {
switch (d.type) {
case "FeatureCollection":
return d.features;
case "Geometry":
return d;
}
})
};

projection.fitExtent(
[
[marginLeft, marginTop],
[width - marginRight, height - marginBottom]
],
geo
);
});
}

if (rotate && projection.rotate) projection.rotate(rotate);
const path = d3.geoPath(projection);

function update() {
svg.selectAll("g.carto > g").each(function ({ r, R, G }) {
d3.select(this)
.selectAll("path")
.attr("d", (i) => {
path.pointRadius(R ? R[i] : r);
return path(G[i]);
});
});
}

update();

return Object.assign(svg.node(), { projection, update });
};

class Features extends Plot.Mark {
constructor(
data,
{
z,
r,
title,
fill,
fillOpacity,
stroke,
strokeOpacity,
strokeWidth,
geometry = (d) => d,
...options
} = {}
) {
const [vr, cr] = maybeNumber(r, 3);
const [vfill, cfill] = maybeColor(fill, "none");
const [vstroke, cstroke] = maybeColor(
stroke,
cfill === "none" ? "currentColor" : "none"
);
const [vstrokewidth, cstrokewidth] = maybeNumber(strokeWidth, 1);
const [vfillopacity, cfillopacity] = maybeNumber(fillOpacity, 1);
const [vstrokeopacity, cstrokeopacity] = maybeNumber(strokeOpacity, 1);
super(
data,
[
{ name: "geometry", value: geometry, optional: false },
{ name: "z", value: z, optional: true },
{ name: "r", value: vr, scale: "r", optional: true },
{ name: "title", value: title, optional: true },
{ name: "fill", value: vfill, scale: "color", optional: true },
{ name: "stroke", value: vstroke, scale: "color", optional: true },
{
name: "strokeWidth",
value: vstrokewidth,
// scale: "strokewidth", // 🌶 channel works, but not the scale
optional: true
},
{
name: "fillOpacity",
value: vfillopacity,
// scale: "fillopacity",
optional: true
},
{
name: "strokeOpacity",
value: vstrokeopacity,
// scale: "fillopacity",
optional: true
}
],
options
);
this.r = cr;
Style(this, {
fill: cfill,
fillOpacity: cfillopacity,
strokeOpacity: cstrokeopacity,
stroke: cstroke,
strokeWidth:
cstroke === "none"
? undefined
: cstrokewidth === undefined
? 1.5
: cstrokewidth,
...options
});
}
render(
I,
scales,
{
r: R,
geometry: G,
z: Z,
title: L,
fill: F,
fillOpacity: FO,
stroke: S,
strokeOpacity: SO,
strokeWidth: SW
},
dimensions
) {
let { data, r } = this;
let index = filter(I, G);
if (R) index = index.filter((i) => positive(R[i]));

return create("svg:g")
.datum(dimensions)
.classed("carto", true)
.call(applyIndirectStyles, this)
.call((g) =>
g
.append("g")
.datum({ r, R, G }) // attach the feature data to the root
.selectAll()
.data(index)
.join("path")
.call(applyDirectStyles, this)
.attr("fill", F && ((i) => F[i]))
.attr("fill-opacity", FO && ((i) => clamp(FO[i], 0, 1)))
.attr("stroke", S && ((i) => S[i]))
.attr("stroke-opacity", SO && ((i) => clamp(SO[i], 0, 1)))
.attr("stroke-width", SW && ((i) => Math.max(0, SW[i])))
.call(title(L))
)
.node();
}
}

Plot.feature = (data, options) => new Features([data], options);
Plot.features = (data, options) => new Features(data, options);
Plot.points = (data, { lonLat = (d) => d, ...options }) =>
new Features(data, {
...options,
geometry: (d) => ({ type: "Point", coordinates: lonLat(d) })
});

return Plot;
}
Insert cell
import {applyDirectStyles, applyIndirectStyles, applyTransform, ascending, clamp, create, filter, maybeNumber, maybeColor, positive, title, Style} from "@data-workflows/plot-symbols"
Insert cell
Plot = require("@observablehq/plot@0.2.9").then(makeCarto)
Insert cell
d3 = require("d3@7", "d3-geo-projection@4")
Insert cell
import {Toggle} from "@observablehq/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