Christian Wahl

SVG Metro Map

10 Sep 2021 • Christian Wahl

One of my resent answers on Stackoverflow was about styling a HTML <process> element like a metro map (at least, that was my interpretation…). OK, is is not possible to style a HTML element like that, so I went for a version in SVG.

The finally result looks like this:

This is basically the SVG and HTML code. I use <symbol> to create the different elements for the metro map and then <use> for placing these elements on the map. It is a quite flexible way to go about it.

<svg viewBox="0 0 250 50">
  <defs>
    <symbol id="line">
      <line x1="0" y1="3" x2="40" y2="3" />
    </symbol>
    <symbol id="dot" transform="translate(-6 -3)">
      <circle cx="6" cy="6" r="4" fill="white" />
    </symbol>
    <symbol id="down">
      <path  transform="translate(0 3)" d="M 0,0 C 25,0 15,20 40,20" />
    </symbol>
    <symbol id="up">
      <path  transform="translate(0 3)" d="M 0,20 C 25,20 15,0 40,0" />
    </symbol>
  </defs>
  <style id="styles"></style>
  <g class="lines">
    <use href="#line" transform="translate(10 10)"/>
    <use href="#line" transform="translate(50 10)"/>
    <use href="#down" transform="translate(90 10)"/>
    <use href="#up"   transform="translate(130 10)"/>
    <use href="#line" transform="translate(170 10)"/>
  </g>
  <g class="dots">
    <use href="#dot" transform="translate(10 10)"/>
    <use href="#dot" transform="translate(50 10)"/>
    <use href="#dot" transform="translate(90 10)"/>
    <use href="#dot" transform="translate(130 30)"/>
    <use href="#dot" transform="translate(170 10)"/>
    <use href="#dot" transform="translate(210 10)"/>
  </g>
</svg>

<form name="form01">
  <input type="range" name="range" min="0" max="5" value="0"/>
</form>

I added a bit of JavaScript for making it dynamic. When the range <input> is changed I will update a smaller part of the CSS.

document.forms.form01.range.addEventListener('change', e => {
  let numlines = parseInt(e.target.value);
  let numdots = (numlines < 1) ? 0 : numlines+1;
  document.querySelector('#styles').innerHTML = `
    .lines use:nth-child(-n+${numlines}) {
      stroke: DarkSlateBlue;
    }
    .dots use:nth-child(-n+${numdots}) {
      stroke: DarkSlateBlue;
    }`;
});

The CSS:

#line line {
  stroke-width: 6px;
}

#dot circle {
  stroke-width: 3px;
  fill: white;
}

#down path, #up path {
  stroke-width: 6px;
  fill: none;
}

svg use {
  stroke: SteelBlue;
}