Skip to content
Merged

Geo #36

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
ab7fc44
Add d3-geo and topojson-client deps, and us-atlas as devDep
techniq Feb 4, 2023
becbbf6
Add @types for d3-geo and topojson-client
techniq Feb 5, 2023
febdf66
Add GeoContext and GeoPath (WIP)
techniq Feb 5, 2023
8c8f7c4
[Chart] Add re-exports for Canvas and WebGL (like Html and Svg)
techniq Feb 6, 2023
be31730
[GeoPath] Support Canvas render context
techniq Feb 6, 2023
6bcb747
[TooltipContext] Add `raiseTarget` to re-insert event.target as last …
techniq Feb 6, 2023
b209010
[GeoPath] Add Choropleth example
techniq Feb 6, 2023
392f5e1
Move GeoPath example to markdown with Preview component
techniq Feb 7, 2023
4954268
[GeoPath] Fix browser projections, support rotation, add examples
techniq Feb 7, 2023
e6ebbfc
Add geoEqualEarth example
techniq Feb 7, 2023
a62fcbb
Check if default slot defined before rendering wrapper. Move padding …
techniq Feb 7, 2023
28e99e8
Add outline/water to projection example
techniq Feb 7, 2023
38ab686
Add Graticule component
techniq Feb 7, 2023
5b50025
Remove console.log
techniq Feb 8, 2023
cc35ccf
Remove pointer events from Graticule so tooltips detects on features
techniq Feb 8, 2023
2a5ec98
[GeoPath] No reason to iterate features when using canvas context
techniq Feb 8, 2023
a058c26
Add canvas example to projection example
techniq Feb 8, 2023
b2309eb
Add animated globe example
techniq Feb 8, 2023
14336d8
Add a few more destiations to the animated globe example
techniq Feb 8, 2023
6f4d461
[GeoContext] Add clipAngle and clipExtent
techniq Feb 8, 2023
5d9475e
Add ClipPathUse component to conveniently reference another path by i…
techniq Feb 8, 2023
78603ec
Add State map example
techniq Feb 8, 2023
d0572e8
[StateMap] Remove useless projections, filter counties by FIPS instea…
techniq Feb 8, 2023
f67e0e1
[AnimatedGlobe] Move graticule under countries/land
techniq Feb 8, 2023
ec892c0
Lighten up Graticule lines 10%
techniq Feb 8, 2023
37bdc35
[AnimatedGlobe] Use 1:110 scale to reduce draw time and improve anima…
techniq Feb 8, 2023
d21be11
Support toggling between detail (1:50) and simple (1:110) scales
techniq Feb 8, 2023
f7650a8
Rename GeoPathProjection to GeoProjection and simplify menu name
techniq Feb 8, 2023
9af7f79
[StateMap] Use state FIPS id for selection instead of name. Remove n…
techniq Feb 8, 2023
1edf706
[GeoContext] Add scale support
techniq Feb 8, 2023
8e9de9c
[GeoContext] Set optional props as undefined by default
techniq Feb 9, 2023
fcf4715
Move Choropleth as separate example. Add Canvas example
techniq Feb 9, 2023
6f5d76e
[Zoom] Add `scroll` prop to control how mousewheel/scroll is handled.…
techniq Feb 9, 2023
083ab80
Add ZoomControls for docs (remove redundancy)
techniq Feb 9, 2023
65bb182
[GeoPath] Add click event, currently passing geoPath function and ori…
techniq Feb 9, 2023
112f2dc
[Chart] Expose the geo projection as a slot prop
techniq Feb 9, 2023
fccba98
Add Zoomable Map example
techniq Feb 9, 2023
792ba16
Simplify Choropleth example
techniq Feb 10, 2023
b6e6198
Cleanup GeoPath example
techniq Feb 12, 2023
c70cb9d
[GeoContext] Support passing `translate`
techniq Feb 12, 2023
c17d8a7
Add GeoPoint component
techniq Feb 13, 2023
7a2194e
WIP mixing pre-projected maps (using geoIdentity) with non-projected …
techniq Feb 13, 2023
633393a
Fix ZoomableMap example
techniq Feb 13, 2023
b07a12e
[ZoomableMap] Scale stroke width based on zoom/scale
techniq Feb 13, 2023
0919e06
Move docs/examples/data to _data
techniq Feb 14, 2023
672f3bc
[GeoPath] Support overriding slot (SVG) or render (Canvas) to access …
techniq Feb 14, 2023
377bb6a
Cleanup some imports
techniq Feb 14, 2023
50c7fb0
[GeoContext] Add support for center projection
techniq Feb 15, 2023
ebe0ade
Add GeoTile
techniq Feb 15, 2023
80d741c
[GeoContext] Reactively rebuild all of projection if any properties c…
techniq Feb 16, 2023
3ba392b
[GeoTile] Support passing `tileSize` and do not reuse image elements …
techniq Feb 16, 2023
b73c09e
[GeoTile example] Support selecting states to zoom/fit
techniq Feb 16, 2023
7bbd8ed
[GeoTile example] Show lat/long on tooltip
techniq Feb 16, 2023
7beb593
[GeoContext] Rename `geojson` to `fitGeojson` to better describe and …
techniq Feb 16, 2023
b66913c
[Zoom] Do not propagate click to children if dragged beyond clickDist…
techniq Feb 16, 2023
f794224
[Zoom] Simplify and improve wheel scrolling
techniq Feb 16, 2023
aa0013a
[Zoom] Improve types
techniq Feb 16, 2023
5a0b80e
[GeoPath] Show lat/long for tooltip example
techniq Feb 16, 2023
df21980
Add more tile services and organize using optgroup
techniq Feb 17, 2023
43b3617
[ZoomableMap] Support changing scrollMode
techniq Feb 17, 2023
240cb01
Extract TilesetField for easier reuse
techniq Feb 18, 2023
77263ec
Remove lat/long from GeoPath tooltip example until accurate
techniq Feb 18, 2023
7139209
Add Zoomable Tile Map (WIP)
techniq Feb 18, 2023
75e8d30
[Zoom] Support pinch to zoom from wheel event + control key
techniq Feb 18, 2023
b7a4140
Add Bubble Map example (SVG only currently)
techniq Feb 18, 2023
79d138b
Add Spike Map example (SVG only currently)
techniq Feb 18, 2023
16aeab4
Add Bubble Map Canvas example
techniq Feb 18, 2023
34a69c4
Add Spike Map Canvas example
techniq Feb 18, 2023
c962a2c
Add ColorRamp
techniq Feb 21, 2023
63a371c
[ColorRamp] Add chromatic scheme and d3-interpolate examples
techniq Feb 21, 2023
ef02807
Add new Legand impl. with support for most color scales (not just ord…
techniq Feb 22, 2023
29072e9
Add Legend to Bubble Map example
techniq Feb 22, 2023
2d025dd
[GeoContext] Verify projection has fitSize before calling (custom pro…
techniq Feb 23, 2023
38e3bb1
Update NavMenu order and adjust Albers USA display name
techniq Feb 23, 2023
3adeb6d
Add 2016 county unemployement data
techniq Feb 23, 2023
47afc15
Improve Choropleth examples
techniq Feb 23, 2023
61f4efc
[AnimatedGlobe] Add SVG version. Support clicking on countries, list…
techniq Feb 23, 2023
d865278
Add RangeField to simplify usage and apply improvements consistently
techniq Feb 25, 2023
f1d1e10
[RangeField] Add invisible min/max values overlayed on value to fix r…
techniq Feb 25, 2023
52a7534
[Zoom] Improve `zoomTo()` by passing bounding rect
techniq Feb 25, 2023
0fdc7d7
[ZoomableMap] Show county details on selected state. Support resetti…
techniq Feb 25, 2023
ed9180c
[ZoomableMap] Add tooltip and hover state to counties
techniq Feb 25, 2023
1babe52
[ZoomContols] Add placement and orientation props to simplify usage. …
techniq Feb 27, 2023
9af701c
Fix typo
techniq Feb 27, 2023
27c1416
[GeoContext] Only store projection in store
techniq Feb 27, 2023
a9c1630
[GeoDebug] Add helpful component to inspect geo/projection
techniq Mar 2, 2023
317e2e9
[GeoTile] Add debug prop
techniq Mar 2, 2023
0921c59
[GeoDebug] Add showCenter toggle to render circle at projection center
techniq Mar 2, 2023
37ecfd7
[Zoom] Add `mode` prop with `svg` (original) and `projection` options.
techniq Mar 2, 2023
aa03829
[ZoomableTileMap] Wire up debug field
techniq Mar 3, 2023
e69eb2c
Add TileImage with basic caching
techniq Mar 3, 2023
e8ace91
[TileImage] Add tile coords as debug text
techniq Mar 3, 2023
a4bdeed
[ZoomableTileMap] Remove outline
techniq Mar 3, 2023
a7f3806
[Zoom] Maintain center when zooming with increase/decrease buttons, a…
techniq Mar 3, 2023
0606ab9
[Zoom] Relace "projection" mode with "manual". Add DraggableGlobe ex…
techniq Mar 4, 2023
461cdb3
Update draggable globe with lofted arcs
techniq Mar 4, 2023
7332ab4
Add Blur component
techniq Mar 4, 2023
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
288 changes: 227 additions & 61 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@
"@types/d3-array": "^3.0.4",
"@types/d3-delaunay": "^6.0.1",
"@types/d3-dsv": "^3.0.1",
"@types/d3-geo": "^3.0.3",
"@types/d3-hierarchy": "^3.1.2",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-quadtree": "^3.0.2",
"@types/d3-random": "^3.0.1",
"@types/d3-sankey": "^0.12.0",
"@types/d3-scale": "^4.0.3",
"@types/d3-shape": "^3.1.1",
"@types/lodash-es": "^4.17.6",
"@types/topojson-client": "^3.1.1",
"autoprefixer": "^10.4.13",
"mdsvex": "^0.10.6",
"prettier": "^2.8.3",
Expand All @@ -42,6 +46,7 @@
"tslib": "^2.5.0",
"typescript": "^4.9.4",
"unist-util-visit": "^4.1.2",
"us-atlas": "^3.0.0",
"vite": "^4.0.4",
"vite-plugin-sveld": "^1.1.0"
},
Expand All @@ -51,17 +56,22 @@
"d3-array": "^3.2.2",
"d3-delaunay": "^6.0.2",
"d3-dsv": "^3.0.1",
"d3-geo": "^3.1.0",
"d3-hierarchy": "^3.1.2",
"d3-interpolate": "^3.0.1",
"d3-interpolate-path": "^2.3.0",
"d3-quadtree": "^3.0.1",
"d3-random": "^3.0.1",
"d3-sankey": "^0.12.3",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.0.0",
"d3-shape": "^3.2.0",
"d3-tile": "^1.0.0",
"date-fns": "^2.29.3",
"layercake": "^7.2.2",
"lodash-es": "^4.17.21",
"svelte": "^3.55.1",
"svelte-ux": "^0.26.0"
"svelte-ux": "^0.26.0",
"topojson-client": "^3.1.0"
}
}
18 changes: 18 additions & 0 deletions src/lib/components/Blur.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import { uniqueId } from 'svelte-ux/utils/string';

export let id: string = uniqueId('blur-');
export let stdDeviation = 5;
</script>

<defs>
<filter {id}>
<feGaussianBlur in="SourceGraphic" {stdDeviation} />
</filter>
</defs>

{#if $$slots.default}
<g filter="url(#{id})">
<slot {id} />
</g>
{/if}
46 changes: 38 additions & 8 deletions src/lib/components/Chart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@

// TODO: Workaround for sveld error: `Cannot read properties of null (reading 'type')` in `ComponentParser`
// See: https://github.com/carbon-design-system/sveld/issues/104
import { LayerCake, Svg as _Svg, Html as _Html } from 'layercake';
import {
LayerCake,
Svg as _Svg,
Html as _Html,
Canvas as _Canvas,
WebGL as _WebGL
} from 'layercake';

export const Svg = _Svg;
export const Html = _Html;
export const Canvas = _Canvas;
export const WebGL = _WebGL;
</script>

<script lang="ts">
Expand All @@ -16,6 +24,7 @@
import { get } from 'lodash-es';
import { isScaleBand } from '$lib/utils/scales';
import TooltipContext from './TooltipContext.svelte';
import GeoContext from './GeoContext.svelte';

type Accessor = string | ((d: any) => number);

Expand Down Expand Up @@ -70,6 +79,8 @@
$: yReverse = yScale ? !isScaleBand(yScale) : true;

export let tooltip: ComponentProps<TooltipContext> | undefined = undefined;

export let geo: ComponentProps<GeoContext> | undefined = undefined;
</script>

<LayerCake
Expand All @@ -88,11 +99,30 @@
let:width
let:element
>
{#if tooltip}
<TooltipContext {...tooltip} let:tooltip>
<slot {aspectRatio} {containerHeight} {containerWidth} {height} {width} {element} {tooltip} />
</TooltipContext>
{:else}
<slot {aspectRatio} {containerHeight} {containerWidth} {height} {width} {element} />
{/if}
<GeoContext {...geo} let:projection>
{#if tooltip}
<TooltipContext {...tooltip} let:tooltip>
<slot
{aspectRatio}
{containerHeight}
{containerWidth}
{height}
{width}
{element}
{projection}
{tooltip}
/>
</TooltipContext>
{:else}
<slot
{aspectRatio}
{containerHeight}
{containerWidth}
{height}
{width}
{element}
{projection}
/>
{/if}
</GeoContext>
</LayerCake>
21 changes: 21 additions & 0 deletions src/lib/components/ClipPathUse.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import { uniqueId } from 'svelte-ux/utils/string';

import ClipPath from './ClipPath.svelte';

/** Id of path or shape */
export let refId: string;

/** Unique id for clipPath */
export let clipPathId: string = uniqueId('clipPath-');
</script>

<ClipPath id={clipPathId}>
<use href="#{refId}" />
</ClipPath>

{#if $$slots.default}
<g style="clip-path: url(#{clipPathId})" on:click on:mousemove on:mouseleave on:keydown>
<slot {refId} {clipPathId} />
</g>
{/if}
21 changes: 21 additions & 0 deletions src/lib/components/ColorRamp.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
export let interpolator: (t: number) => string;
export let steps = 10;
export let height: string | number = '20px';
export let width: string | number = '100%';

let href = '';
$: {
const canvas = document.createElement('canvas');
canvas.width = steps;
canvas.height = 1;
const context = canvas.getContext('2d')!;
for (let i = 0; i < steps; ++i) {
context.fillStyle = interpolator(i / (steps - 1));
context.fillRect(i, 0, 1, 1);
}
href = canvas.toDataURL();
}
</script>

<image {href} preserveAspectRatio="none" style:height style:width {...$$restProps} />
94 changes: 94 additions & 0 deletions src/lib/components/GeoContext.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<script lang="ts" context="module">
import { getContext, setContext } from 'svelte';
import { writable, type Writable } from 'svelte/store';
import {
geoMercator,
type GeoIdentityTransform,
type GeoPermissibleObjects,
type GeoProjection
} from 'd3-geo';

export const geoContextKey = {};

export type GeoContext = Writable<GeoProjection | GeoIdentityTransform>;

export function geoContext() {
return getContext<GeoContext>(geoContextKey);
}

function setGeoContext(geo: GeoContext) {
setContext(geoContextKey, geo);
}
</script>

<script lang="ts">
const { width, height } = getContext('LayerCake');

/** @type {Function} projection - A d3 projection function. Pass this in as an uncalled function, e.g. `projection={geoAlbersUsa}`. */
export let projection: () => GeoProjection | GeoIdentityTransform = geoMercator;

export let fitGeojson: GeoPermissibleObjects;

/** By default, the map fills to fit the $width and $height. If instead you want a fixed-aspect ratio, like for a server-side rendered map, set that here. */
export let fixedAspectRatio: number | undefined = undefined;

export let clipAngle: number | undefined = undefined;
export let clipExtent: [[number, number], [number, number]] | undefined = undefined;
export let rotate:
| {
/** Lambda (Center Meridian) */
yaw: number;
/** Phi */
pitch: number;
/** Gamma */
roll: number;
}
| undefined = undefined;
export let scale: number | undefined = undefined;
export let translate: [number, number] | undefined = undefined;
export let center: [number, number] | undefined = undefined;

const geo = writable(projection());
setGeoContext(geo);

$: fitSizeRange = (fixedAspectRatio ? [100, 100 / fixedAspectRatio] : [$width, $height]) as [
number,
number
];

$: {
const _projection = projection();

if (fitGeojson && 'fitSize' in _projection) {
_projection.fitSize(fitSizeRange, fitGeojson);
}

if (scale && 'scale' in _projection) {
_projection.scale(scale);
}

if (rotate && 'rotate' in _projection) {
_projection.rotate([rotate.yaw, rotate.pitch, rotate.roll]);
}

if (translate && 'translate' in _projection) {
_projection.translate(translate);
}

if (center && 'center' in _projection) {
_projection.center(center);
}

if (clipAngle && 'clipAngle' in _projection) {
_projection.clipAngle(clipAngle);
}

if (clipExtent && 'clipExtent' in _projection) {
_projection.clipExtent(clipExtent);
}

geo.set(_projection);
}
</script>

<slot projection={$geo} />
71 changes: 71 additions & 0 deletions src/lib/components/GeoPath.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte';
import { geoPath as d3geoPath, type GeoPath, type GeoPermissibleObjects } from 'd3-geo';

import { geoContext } from './GeoContext.svelte';
import type { TooltipContextValue } from './TooltipContext.svelte';
import { scaleCanvas } from 'layercake';

export let geojson: GeoPermissibleObjects;

export let fill: string | undefined = undefined;
export let fillScale: Object | undefined = undefined;
export let stroke: string | undefined = undefined;
export let strokeWidth: number | string | undefined = undefined;

/** Render to canvas */
export let render: ((ctx: CanvasRenderingContext2D, { geoPath: GeoPath }) => any) | undefined =
undefined;

/**
* Tooltip context to setup mouse events to show tooltip for related data
*/
export let tooltip: TooltipContextValue | undefined = undefined;

const dispatch = createEventDispatcher<{ click: { geoPath: GeoPath; event: MouseEvent } }>();

const { rGet, width, height } = getContext('LayerCake');
const canvas = getContext('canvas');
const geo = geoContext();

$: geoPath = d3geoPath($geo);

$: renderContext = canvas ? 'canvas' : 'svg';

$: ctx = canvas?.ctx;
$: if (renderContext === 'canvas' && $ctx) {
// console.count('render');
scaleCanvas($ctx, $width, $height);
$ctx.clearRect(0, 0, $width, $height);

if (render) {
render($ctx, { geoPath });
} else {
$ctx.beginPath();
// Set the context here since setting it in `$: geoPath` is a circular reference
geoPath.context($ctx);
geoPath(geojson);

$ctx.fillStyle = fill || (fillScale && $rGet(fillScale)) || 'transparent';
$ctx.fill();

$ctx.lineWidth = strokeWidth;
$ctx.strokeStyle = stroke;
$ctx.stroke();
}
}
</script>

{#if renderContext === 'svg'}
<slot {geoPath}>
<path
d={geoPath(geojson)}
fill={fill || (fillScale && $rGet(fillScale)) || 'transparent'}
stroke={stroke || 'black'}
on:mousemove={(e) => tooltip?.show(e, geojson)}
on:mouseleave={(e) => tooltip?.hide()}
on:click={(event) => dispatch('click', { geoPath, event })}
{...$$restProps}
/>
</slot>
{/if}
40 changes: 40 additions & 0 deletions src/lib/components/GeoPoint.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import { getContext } from 'svelte';
import { scaleCanvas } from 'layercake';

import { geoContext } from './GeoContext.svelte';

/** Latitude */
export let lat: number;
/** Longitude */
export let long: number;

/** Render to canvas */
export let render: (
ctx: CanvasRenderingContext2D,
coords: { x: number; y: number }
) => any = () => {};

const { width, height } = getContext('LayerCake');
const canvas = getContext('canvas');
const geo = geoContext();

$: [x, y] = $geo([long, lat]) ?? [0, 0];

$: renderContext = canvas ? 'canvas' : 'svg';

$: ctx = canvas?.ctx;
$: if (renderContext === 'canvas' && $ctx) {
// console.count('render');
scaleCanvas($ctx, $width, $height);
$ctx.clearRect(0, 0, $width, $height);

render($ctx, { x, y });
}
</script>

{#if renderContext === 'svg'}
<g transform="translate({x},{y})">
<slot />
</g>
{/if}
Loading