Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(map): optimize geoPathsFor method #1792

Merged
merged 3 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { GeoContext } from "d3-geo"

// Can be used as a d3 projection context to convert a geojson feature to a SVG path
// In contrast to what d3-geo does by default, it will round all coordinates to one decimal place
// Adapted from https://github.com/d3/d3-geo/blob/8d3f3a98c034b087e2c808f752d2381d51c30015/src/path/string.js

// FUTURE: Once we update to d3-geo@^3.10, d3-geo can do this by default: https://github.com/d3/d3-geo/pull/272

// Rounds to one decimal place
const round = (x: number): number => Math.round(x * 10) / 10

export class GeoPathRoundingContext implements GeoContext {
_string: string = ""

beginPath(): void {
this._string = ""
}

moveTo(x: number, y: number): void {
this._string += `M${round(x)},${round(y)}`
}

lineTo(x: number, y: number): void {
this._string += `L${round(x)},${round(y)}`
}

arc(_x: number, _y: number, _radius: number): void {
throw new Error("Method not implemented.")
}

closePath(): void {
this._string += "Z"
}

result(): string {
return this._string
}
}
27 changes: 10 additions & 17 deletions packages/@ourworldindata/grapher/src/mapCharts/MapChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
DEFAULT_BOUNDS,
flatten,
getRelativeMouse,
identity,
sortBy,
guid,
minBy,
Expand All @@ -24,6 +23,7 @@ import {
HorizontalNumericColorLegend,
} from "../horizontalColorLegend/HorizontalColorLegends"
import { MapProjectionName, MapProjectionGeos } from "./MapProjections"
import { GeoPathRoundingContext } from "./GeoPathRoundingContext"
import { select } from "d3-selection"
import { easeCubic } from "d3-ease"
import { MapTooltip } from "./MapTooltip"
Expand Down Expand Up @@ -96,25 +96,18 @@ const geoPathCache = new Map<MapProjectionName, string[]>()
const geoPathsFor = (projectionName: MapProjectionName): string[] => {
if (geoPathCache.has(projectionName))
return geoPathCache.get(projectionName)!
const projectionGeo = MapProjectionGeos[projectionName]
const strs = GeoFeatures.map((feature) => {
const s = projectionGeo(feature) as string
const paths = s.split(/Z/).filter(identity)

const newPaths = paths.map((path) => {
const points = path.split(/[MLZ]/).filter((f: any) => f)
const rounded = points.map((point) =>
point
.split(/,/)
.map((v) => parseFloat(v).toFixed(1))
.join(",")
)
return "M" + rounded.join("L")
})

return newPaths.join("Z") + "Z"
// Use this context to round the path coordinates to a set number of decimal places
const ctx = new GeoPathRoundingContext()
const projectionGeo = MapProjectionGeos[projectionName].context(ctx)
const strs = GeoFeatures.map((feature) => {
ctx.beginPath() // restart the path
projectionGeo(feature)
return ctx.result()
})

projectionGeo.context(null) // reset the context for future calls

geoPathCache.set(projectionName, strs)
return geoPathCache.get(projectionName)!
}
Expand Down