block by rveciana a8f24ce0ea98e403e266dfb4f9b6b2e9

Svelte mapping: Transitions

Full Screen

Third example of a map drawn with Svelte and the d3 projections. Transitions are made now when the mouse is over a region and when the projection is changed.

Check this blog post from Geoexamples for more explanations.

To test it, clone the standard svelte template by

npx degit sveltejs/template svelte-app cd svelte-app

And copy the App.svelte and Feature.svelte files into the src directory.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />

    <title>Svelte app</title>

    <link rel="stylesheet" href="global.css" />
    <link rel="stylesheet" href="bundle.css" />

    <script defer src="bundle.js"></script>
  </head>

  <body></body>
</html>

App.svelte

<script>
  import { geoAlbers, geoPath, geoProjection } from "d3-geo";
  import { geoAlbersUk } from "d3-composite-projections";
  import { scaleLinear } from "d3-scale";
  import { extent } from "d3-array";
  import { onMount } from "svelte";
  import { feature } from "topojson";
  import { tweened } from "svelte/motion";
  import { interpolate } from "d3-interpolate";
  import Feature from "./Feature.svelte";

  let data = [];
  let colorScale = () => {};
  const width = "960";
  const height = "500";
  const projectionAlbers = geoAlbers()
    .rotate([4.4, 0.8])
    .center([0, 55.4])
    .parallels([50, 60])
    .scale(3800)
    .translate([width / 2, (1.8 * height) / 2]);

  const projectionAlbersUk = geoAlbersUk()
    .translate([width / 2, (1.85 * height) / 2])
    .scale(5200);

  const projectionTween = (projection0, projection1) => {
    return function(t) {
      function project(λ, φ) {
        (λ *= 180 / Math.PI), (φ *= 180 / Math.PI);
        var p0 = projection0([λ, φ]),
          p1 = projection1([λ, φ]);
        if (!p0 || !p1) return [0, 0];
        return [(1 - t) * p0[0] + t * p1[0], (1 - t) * -p0[1] + t * -p1[1]];
      }

      return geoProjection(project)
        .scale(1)
        .translate([0, 0]);
    };
  };

  const currentProj = tweened(projectionAlbers, {
    duration: 1000,
    interpolate: projectionTween
  });

  $: path = geoPath().projection($currentProj);

  const opacity = tweened(0, {
    duration: 1000
  });

  onMount(async function() {
    const response = await fetch(
      "https://gist.githubusercontent.com/rveciana/27272a581e975835aaa321ddf816d726/raw/c40062a328843322208b8e98c2104dc8f6ad5301/uk-counties.json"
    );
    const json = await response.json();
    const topoData = feature(json, json.objects.UK);
    const land = {
      ...topoData,
      features: topoData.features.filter(
        d => d.properties.NAME_1 === "Scotland"
      )
    };

    const namesExtent = extent(land.features, d => d.properties.NAME_2.length);
    colorScale = scaleLinear()
      .domain(namesExtent)
      .range(["#feedde", "#fd8d3c"]);
    data = land.features;
  });
</script>

<style>
  svg {
    width: 960px;
    height: 500px;
    background-color: "#eeeeee";
  }
  .borders {
    fill: #ddd;
  }
</style>

<button
  on:click={() => {
    currentProj.set($currentProj === projectionAlbers ? projectionAlbersUk : projectionAlbers);
    opacity.set($currentProj === projectionAlbers ? 1 : 0);
  }}>
  Change
</button>
<svg width="960" height="500">
  <path
    class="borders"
    d={projectionAlbersUk.getCompositionBorders()}
    style="opacity: {$opacity}" />
  {#each data as feature}
    <Feature
      featurePath={path(feature)}
      initialColor={colorScale(feature.properties.NAME_2.length)} />
  {/each}

</svg>

Feature.svelte

<script>
  import { tweened } from "svelte/motion";
  import { interpolateLab } from "d3-interpolate";
  import { rgb } from "d3-color";
  export let featurePath;
  export let initialColor;

  const color = tweened(initialColor, {
    duration: 300,
    interpolate: interpolateLab
  });
</script>

<style>
  .provinceShape {
    stroke: #444444;
    stroke-width: 0.5;
  }
</style>

<path
  d={featurePath}
  class="provinceShape"
  fill={$color}
  on:mouseover={() => {
    color.set(rgb(initialColor).brighter(0.3));
  }}
  on:mouseout={() => {
    color.set(initialColor);
  }} />

bundle.css

svg.svelte-1lltho4{width:960px;height:500px;background-color:"#eeeeee"}.borders.svelte-1lltho4{fill:#ddd}
.provinceShape.svelte-116txg3{stroke:#444444;stroke-width:0.5}

/*# sourceMappingURL=bundle.css.map */