Public
Edited
Feb 13, 2023
6 stars
Insert cell
Insert cell
Insert cell
/**
* pixiPath generator to draw GeoJSON with Pixi.js Graphics method.
* Handle Features or Geometry Collection, as well as single Feature or geometry object.
* Polygon|MultiPolygon with hole(s) are supported.
* Points are rendered as circles.
* @param {GeoJSON} object - valid GeoJSON (FeaturesCollection, GeometryCollection, single Feature, single geometry object)
* @param {Graphics} context - PIXI Graphics
* @param {Number|Function} radius - size of the point as a constant or a function pass to each point. Default: 5.
* @param {Object} properties - enable access to properties of a feature inside radius function (used internally to pass properties of a feature to toPoint function)
*/
function pixiPath(object, context, radius = 5, properties) {
switch (object.type) {
case "GeometryCollection":
object.geometries.forEach((geometry) =>
pixiPath(geometry, context, radius)
);
break;
case "FeatureCollection":
object.features.forEach((feature) => pixiPath(feature, context, radius));
break;
case "Feature":
pixiPath(object.geometry, context, radius, object.properties);
break;

case "Point":
toPoint(object.coordinates, radius, properties);
break;
case "MultiPoint":
object.coordinates.forEach((point) => toPoint(point, radius, properties));
break;

case "LineString":
toLineString(object.coordinates);
break;
case "MultiLineString":
object.coordinates.forEach(toLineString);
break;

case "Polygon":
toPolygon(object.coordinates);
break;
case "MultiPolygon":
object.coordinates.forEach(toPolygon);
break;
}

function toPoint(coords, radius, properties) {
const [x, y] = coords;
const r = typeof radius === "function" ? radius(properties) : radius;
context.drawCircle(x, y, r);
}

function toLineString(coords) {
coords.forEach((point, i) =>
i === 0 ? context.moveTo(...point) : context.lineTo(...point)
);
}

function toPolygon(coords) {
coords.length === 1
? // Polygon without hole(s)
context.drawPolygon(coords.flat(2))
: // Polygon with hole(s)
coords.forEach((ring, i) =>
i === 0
? context.drawPolygon(ring.flat(2))
: context.beginHole().drawPolygon(ring.flat(2)).endHole()
);
}
}
Insert cell
Insert cell
/**
* Wrapper around pixiPath function to simulate a similar behaviour as d3.geoPath.
* Given an optional projection return a pixiPath generator.
* @param {Function} projection - a d3 projection function. Default: geoIdentity()
*/
function geoPath(projection) {
/**
* pixiPath generator to draw GeoJSON with Pixi.js Graphics method.
* Handle Features or Geometry Collection, as well as single Feature or geometry object.
* Polygon|MultiPolygon with hole(s) are supported.
* Points are rendered as circles.
* GeoJSON is pre-projected according to projection give in geoPath.
*
* @param {GeoJSON} geojson - valid GeoJSON (FeaturesCollection, GeometryCollection, single Feature, single geometry object)
* @param {Graphics} context - PIXI Graphics
* @param {Number|Function} radius - size of the point as a constant or a function pass to each point. Default: 5.
*/
return function (geojson, context, radius) {
projection =
projection ??
d3
.geoIdentity()
.reflectY(true)
.fitSize([width, width * 0.6], geojson);
const geojson_projected = d3.geoProject(geojson, projection);
return pixiPath(geojson_projected, context, radius);
};
}
Insert cell
Insert cell
projection = d3
.geoBertin1953()
.fitSize([width, width * 0.6], { type: "Sphere" })
Insert cell
path = geoPath(projection)
Insert cell
{
const height = width * 0.6;

// Instantiate a Pixi.js app
const app = new PIXI.Application({
width,
height,
antialias: true,
resolution: devicePixelRatio || 1,
autoDensity: true,
backgroundAlpha: 0
});

// Pixi graphics object that will store geometries
const context = new PIXI.Graphics();

// Draw land
context.lineStyle(1, 0x000000, 1);
context.beginFill(0xfefee2);
path(land, context);
context.endFill();

// Draw Sphere outline
context.lineStyle(2, 0x000000, 1);
path({ type: "Sphere" }, context);
// context.endFill();

// Draw main world rivers
context.lineStyle(1, 0x56829c, 0.6);
path(rivers, context);

// Draw cities with size proportionnal to population
context.lineStyle(1, 0x8c6d46, 1);
context.beginFill(0xf2bc79);
path(cities, context, (d) => Math.sqrt(d.pop_max) / 1000); // we have access to properties!
context.endFill();

// add Pixi Graphics object to the stage
app.stage.addChild(context);

return app.view; // Canvas element
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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