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

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