Skip to content

Creating Custom SVG Region Maps

The Explore module in TeskaLabs LogMan.io is the part of the product where users build their own charts on top of raw log data.

One of the chart types Explore offers is the Region Map. A region map takes the result of a group by query and colours regions of an image (typically a map) to reflect the value associated with each group. The image is a regular SVG file that you upload to the LogMan.io Library and reference from your chart definition.

Region maps do not have to be geographical. Anything that can be drawn as a set of named regions can become a region map: a network zone diagram, a MITRE ATT&CK matrix, a data-centre floor plan, a Kubernetes namespace overview, a building, a rack layout. If you can split your domain into a fixed set of named regions and you have a log field that yields those names, you can turn it into a region map in Explore.

This post explains how the mechanism works, how to author an SVG so TeskaLabs LogMan.io can drive it, and how to make the map adapt automatically to the light and dark themes of the product. We will build a small, hand-authored network-zones map step by step.

Under the hood

TeskaLabs LogMan.io Explore component renders charts with Apache ECharts, powered by a LogMan.io backend microservice called lmio-chart.

How a Region Map works

The data flow is straightforward once you have it in your head:

  1. A user defines an Explore chart of "Region Map" type, a Group by field (for example network.zone), and a Map (the SVG asset in the Library).
  2. The query returns rows that look like (bucket_key, value); for example ("dmz", 1283), ("lan", 47210), …
  3. The chart component loads the SVG from the TeskaLabs LogMan.io Library, and registers it as a map. LogMan.io treats each element in the SVG whose name attribute matches a bucket key as a region of the map.
  4. TeskaLabs LogMan.io paints each matched region by mapping its value through the chart's colour scale. Regions that the data does not mention are painted with a neutral "no data" colour.

The whole match is done by string equality between the bucket key and the SVG element's name attribute. There is nothing magical happening, you control the names on both sides. The contract is: the strings the source field yields must equal the name attributes of the regions you want to colour.

Use Table to prepare a data

In order to define a proper "group by" field, use "Table" to visualize the result of the query. Switch to "Region Map" when you are satisfied with the raw data.

A few practical consequences:

  • The match is case sensitive. Pick one convention (lowercase is the norm in LogMan.io's bundled maps) and align both the SVG and the log pipeline to it. If your field stores values as DMZ but the SVG region is named dmz, nothing will light up.
  • A bucket key is always a string after normalisation. If your Group by field is multi-valued (["t1047", "t1059"]), the same row's value is applied to each referenced region, one event with two techniques colours both technique boxes.
  • Bucket keys not present in the SVG are simply ignored. SVG regions never matched by a key are rendered with the default "no data" colour.

Anatomy of an SVG map

The SVG file needs only three things to work as a region map:

  1. A root <svg> element with a sensible viewBox. LogMan.io uses the viewBox for layout and zoom.
  2. A single group <g id="layer-regions"> that contains the named regions. The group is a convention, not a hard requirement, but it keeps the file tidy and matches the bundled maps.
  3. Inside that group, one element per region with a name="<bucket-key>" attribute. Usually this is a <rect>, but any SVG shape works.

The minimum viable example:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600">
  <g id="layer-regions">
    <rect name="t1047" x="10"  y="20" width="100" height="40"/>
    <rect name="t1059" x="120" y="20" width="100" height="40"/>
  </g>
</svg>

Each <rect> is a region. TeskaLabs LogMan.io links a data point with name t1047 and value 42 to the rectangle whose name attribute is t1047 and paints it according to the colour scale.

Static fill and stroke attributes on a named region are just placeholders, TeskaLabs LogMan.io overwrites them at render time with the colour computed from the data. Use any colour you like there; it only matters when previewing the SVG outside TeskaLabs LogMan.io.

Worked example: a network-zones map

Let us build a small map that colours four network zones (DMZ, internal LAN, guest WiFi, and management) by the number of events seen in each zone. Assume the log pipeline emits a field called network.zone whose values are dmz, lan, guest, and mgmt.

Step 1: Decide the region keys

The keys must match the values your data produces. We pick lowercase, short identifiers:

Zone label Region key
DMZ dmz
Internal LAN lan
Guest WiFi guest
Management mgmt

Step 2: Draw the SVG

Lay out four rectangles on a 400 × 280 canvas, two rows of two. Each rectangle is the data-driven region and carries the matching name attribute. Each rectangle is accompanied by two text labels (a human-readable title and the raw key) that are chrome, not data, so they do not need a name.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 280">
  <title>Network Zones</title>
  <g id="layer-regions">

    <!-- DMZ -->
    <rect name="dmz"
          x="20" y="20" width="160" height="100"
          fill="#eeeeee" stroke="#888888" stroke-width="1"/>
    <text x="100" y="65" text-anchor="middle"
          font-family="sans-serif" font-size="16" font-weight="bold"
          fill="currentColor">DMZ</text>
    <text x="100" y="90" text-anchor="middle"
          font-family="sans-serif" font-size="11"
          fill="currentColor">dmz</text>

    <!-- Internal LAN -->
    <rect name="lan"
          x="220" y="20" width="160" height="100"
          fill="#eeeeee" stroke="#888888" stroke-width="1"/>
    <text x="300" y="65" text-anchor="middle"
          font-family="sans-serif" font-size="16" font-weight="bold"
          fill="currentColor">Internal LAN</text>
    <text x="300" y="90" text-anchor="middle"
          font-family="sans-serif" font-size="11"
          fill="currentColor">lan</text>

    <!-- Guest WiFi -->
    <rect name="guest"
          x="20" y="160" width="160" height="100"
          fill="#eeeeee" stroke="#888888" stroke-width="1"/>
    <text x="100" y="205" text-anchor="middle"
          font-family="sans-serif" font-size="16" font-weight="bold"
          fill="currentColor">Guest WiFi</text>
    <text x="100" y="230" text-anchor="middle"
          font-family="sans-serif" font-size="11"
          fill="currentColor">guest</text>

    <!-- Management -->
    <rect name="mgmt"
          x="220" y="160" width="160" height="100"
          fill="#eeeeee" stroke="#888888" stroke-width="1"/>
    <text x="300" y="205" text-anchor="middle"
          font-family="sans-serif" font-size="16" font-weight="bold"
          fill="currentColor">Management</text>
    <text x="300" y="230" text-anchor="middle"
          font-family="sans-serif" font-size="11"
          fill="currentColor">mgmt</text>

  </g>
</svg>

The SVG looks like this:

Network Zones DMZ dmz Internal LAN lan Guest WiFi guest Management mgmt

At this point the four rectangles are named (dmz, lan, guest, mgmt) and the grey fills are placeholders. When Explore renders this map against a query grouped by network.zone, TeskaLabs LogMan.io replaces those greys with colours from the chart's colour scale, leaving any zone the query did not return in the neutral "no data" colour.

Theme-aware colours: light and dark mode

TeskaLabs LogMan.io ships with two themes (light and dark) and users can switch between them at any time. The data-driven fills of named regions are not a problem: TeskaLabs LogMan.io computes them from the data via the colour scale, and those colours can be configured per dashboard. The problem is the chrome: borders that frame groups of regions, separator lines, section headers, descriptive labels. These have to look right in both themes, and they cannot be hard-coded to black or white.

The Region Map loader handles this by recognising two special attributes on any SVG element:

  • data-fill="<CSS-variable-name>": at load time, the loader reads the value of the named CSS variable from the current document and writes it into the element's fill attribute.
  • data-stroke="<CSS-variable-name>": same, for stroke.

The CSS variables come from TeskaLabs LogMan.io's Bootstrap-based theme. Each theme defines its own values for the same variable names, which is why a single SVG can serve both themes. The most useful variables for map authoring are:

Variable Meaning
--bs-body-color Default text and foreground colour
--bs-body-bg Default page background
--bs-border-color Subtle border colour
--bs-primary Brand primary (royal blue in the light theme)
--bs-secondary Brand secondary

A useful rule of thumb: anything that is not a data-driven named region (labels, frames, separators, header rectangles) should specify its colour via data-fill and/or data-stroke, never via a static colour. That way the SVG is rendered correctly in both themes without you having to maintain two versions of the file.

Theme-aware version of the network-zones SVG

We extend each text label with data-fill="--bs-body-color" so that the label colour follows the theme. We can additionally make the placeholder stroke of each named <rect> adapt as well, although it is not strictly necessary because TeskaLabs LogMan.io redraws the borders with its own border colour.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 280">
  <title>Network Zones</title>
  <g id="layer-regions">

    <!-- DMZ -->
    <rect name="dmz"
          x="20" y="20" width="160" height="100"
          fill="#eeeeee"
          data-stroke="--bs-border-color" stroke-width="1"/>
    <text x="100" y="65" text-anchor="middle"
          font-family="sans-serif" font-size="16" font-weight="bold"
          fill="currentColor" data-fill="--bs-body-color">DMZ</text>
    <text x="100" y="90" text-anchor="middle"
          font-family="sans-serif" font-size="11"
          fill="currentColor" data-fill="--bs-body-color">dmz</text>

    <!-- … the other three zones follow the same pattern … -->

  </g>
</svg>

The data-fill / data-stroke attributes are processed once when the SVG is loaded for the current theme. When the user switches themes, TeskaLabs LogMan.io re-loads and re-registers the map with the values from the now-active theme. You do not have to do anything in the SVG to make that re-render work.

Working with SVG editors

You can hand-write small SVG maps as we did above, but for anything beyond a few regions you will want an editor.

Most editors handle the basics, with one quirk:

Some SVG editors (including the otherwise very capable web-based Boxy SVG ) refuse to set arbitrary attributes such as name directly on a shape. They will, however, let you set custom data attributes such as data-name.

The Region Map loader works around this by treating data-name as a synonym for name. On load it copies the value of data-name to name for every element that has it.

So if your editor cannot set name, set data-name instead, the result is identical. The same is true for data-fill and data-stroke: they are friendly aliases that any reasonable editor will accept.

Uploading the map to LogMan.io

Once the SVG is ready:

  1. Open the Library and navigate to Dashboards / Widgets / Maps /.
  2. Upload the SVG file. Use a descriptive, stable filename. The filename is the identifier you will reference from the chart, and renaming it later breaks any chart that points at it.
  3. In Explore, create a new chart with type Region Map.
  4. In the side panel, set:
    • Map: the filename you just uploaded (for example NetworkZones.svg).
    • Group by: the field whose values match the region names (network.zone in our example).
  5. (Optional) Tune the In Range Color and Zoom Scale Limits to taste.

The chart will render immediately. Regions for which the current query returned data are coloured by the chart's colour scale; regions without data stay in the neutral "no data" colour and remain interactive. Hover shows the region key, which is useful when verifying that your name attributes line up with the data.

Tips and gotchas

A short collection of things that tend to bite first-time authors:

  • Case sensitivity. DMZdmz. Pick lowercase or uppercase, and apply it consistently in the parser/enricher that produces the field and in the SVG. The bundled maps use lowercase.
  • Stable filenames. The filename in Library / Dashboards / Widgets / Maps / is the map identifier. Renaming or moving the file breaks every chart that references it.
  • One name per region, but it can be on multiple elements. If you want both the <rect> and the text label inside it to react to hover, give them the same name. LogMan.io treats them as one logical region.
  • Avoid filters, gradients, and clip-paths on data-driven regions. LogMan.io overwrites fill and stroke on the matched element. If the visual you want depends on a <filter> or a <linearGradient> referenced from fill="url(#…)", it will not survive the overwrite.
  • Keep chrome out of the data path. Anything that should not be coloured by the query must not have a name attribute. Otherwise an unrelated bucket key with the same value will accidentally light up your section header.
  • Multi-valued fields colour multiple regions. If threat.technique.id for one event is ["t1047", "t1059"], both regions receive the same value from that row. This is usually what you want, but be aware of it when designing dashboards on top of multi-valued fields.
  • Generate the SVG when the map gets big. Hand-authoring is fine for a handful of regions, but once you reach dozens or more, a small script that emits the <rect> elements (with name for data and data-fill / data-stroke for chrome) from a structured source is far easier to maintain than a manually drawn file.
  • Region not lighting up? Check, in order: (1) the name attribute exists and is spelled exactly like the bucket key; (2) the SVG is inside the <g id="layer-regions"> group; (3) the Group by field actually produces the value you expect, verify in the Discover screen first.

Wrapping up

A Region Map is, at heart, a very small contract: each interactive region in an SVG carries a name attribute, and that string is what TeskaLabs LogMan.io matches against the keys produced by a group by query. Add data-fill and data-stroke on any chrome you draw, and the same SVG works for both light and dark themes without modification.

With those two ideas in hand (name for data, data-fill / data-stroke for theme) you can turn almost any structured domain into a usable, theme-aware region map in TeskaLabs LogMan.io Explore. Start small with a hand-drawn diagram, and switch to a generator script when the number of regions outgrows the patience of an SVG editor.