OnlyMapJS Latest release
GitHub Docs

← Gallery / Layers

Draw 3D columns

Stand a ColumnLayer bar on every power station, sized by capacity and coloured by fuel. GridCellLayer is the square-cell sibling.

Open full screen ↗ View source ↗
<!DOCTYPE html>
<!--
  ColumnLayer stands one solid column on each point and takes its height from
  the data. Reach for it when a place has a QUANTITY attached and the reader's
  job is to compare quantities — it is a bar chart that keeps its geography.

  The map: every power station in Europe rated 300 MW or more, from the World
  Resources Institute's global database. Height is installed capacity, colour
  is the fuel. France reads as a wall of nuclear, Norway as hydro, the Rhine
  and the Po as gas. Drag with the right mouse button, or hold Shift and drag,
  to rotate — the third dimension is the whole point.
-->
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>OnlyMapJS — Draw 3D columns</title>
  <link rel="stylesheet" href="https://unpkg.com/@nika-js/onlymap/dist/onlymapjs.css" />
  <script type="module" src="https://unpkg.com/@nika-js/onlymap/dist/onlymap.standalone.js"></script>
  <style>
    :root { color-scheme: dark; }
    html, body { margin: 0; height: 100%; font-family: system-ui, sans-serif; }
    om-map {
      display: block; height: 100vh;
      --om-widget-bg: #12151c;
      --om-widget-fg: #f4f6fa;
      --om-widget-muted: #8d95a6;
      --om-widget-border: #272d38;
      --om-widget-hover-bg: #1e232c;
    }
  </style>
</head>
<body>

  <om-map center="[9.5, 48.6]" zoom="4.3" pitch="55" bearing="-14" basemap="dark-matter">

    <!-- get-elevation reads the value and elevation-scale turns it into metres
         of height, so the scale always needs tuning to the data — 45 here
         means a 1,000 MW station stands 45 km tall, which is what reads at
         continental zoom. Without `extruded` you get flat discs on the ground.

         radius is in METRES like every deck.gl radius, so a column keeps its
         footprint as you zoom rather than growing on screen.

         disk-resolution is how many sides a column has: 4 gives squares, 6
         hexagons, and the default 20 reads as round. GridCellLayer is the
         square-cell sibling of this layer — the same props, with cell-size in
         place of radius, for data that is already binned to a grid.

         filter-field and filter-range cut rows on the GPU. The data runs down
         to 10 MW, and a 300 MW floor is what keeps a continent readable —
         widen the range to bring the smaller stations back.

         A chain of equality tests colours by fuel, and the legend reads that
         chain back to build its key, so the palette is written once, here.

         `pickable` is what lets the hover behavior below find a column at all.
         Leave it off and the tooltip never fires.

         Columns take the scene lighting like any other extruded geometry, so
         `<om-map lighting="daylight">` gives them a sun and cast shading. -->

    <om-layer id="stations" type="ColumnLayer"
              data="../../data/global-power-plants.csv"
              label="Power stations, 300 MW and up" color="#fb923c"
              filter-field="capacity_mw" filter-range="[300, 25000]"
              get-position="[$lon, $lat]"
              get-elevation="$capacity_mw"
              elevation-scale="45"
              radius="7000"
              disk-resolution="16"
              extruded
              get-fill-color="$fuel == 'Nuclear' ? '#22d3ee'
                            : $fuel == 'Coal' ? '#f43f5e'
                            : $fuel == 'Gas' ? '#fbbf24'
                            : $fuel == 'Hydro' ? '#4ade80'
                            : '#94a3b8'"
              opacity="0.88"
              pickable></om-layer>

    <om-widget type="legend" title="Fuel" position="top-right"></om-widget>
    <om-widget type="zoom-controls" position="bottom-right"></om-widget>

    <om-widget position="top-left">
      <style>
        .panel { background: rgba(18,21,28,.88); color: #e6eaf2; padding: 10px 14px;
                 border-radius: 8px; font-size: 13px; max-width: 250px; line-height: 1.45; }
        .panel b { color: #fbbf24; }
      </style>
      <div class="panel">
        <b>Europe's big power stations.</b> Height is installed capacity, from
        300 MW to the 5.5 GW of Gravelines. Hover a column for the station.
      </div>
    </om-widget>

    <om-widget type="attribution"
               text="Power stations © World Resources Institute, Global Power Plant Database v1.3, CC BY 4.0"
               position="bottom-start"></om-widget>

    <om-behavior on="hover" layer="stations" action="show-tooltip" template="#station-tip"></om-behavior>

    <om-fallback>
      <p style="font: 15px system-ui, sans-serif; padding: 24px; max-width: 42ch">
        <strong>This map needs JavaScript.</strong><br />
        Open this file in a web browser such as Chrome, Safari, or Firefox.
      </p>
    </om-fallback>

  </om-map>

  <template id="station-tip">
    <div style="padding:7px 10px; border-radius:7px; background:#12151c; color:#f4f6fa;
                border:1px solid #272d38; box-shadow:0 3px 12px rgba(0,0,0,.55);
                font:12px/1.45 system-ui, sans-serif;">
      <b>{{name}}</b><br />
      {{capacity_mw}} MW · {{fuel}} · {{country}}
    </div>
  </template>

</body>
</html>

The snippet above loads the latest release. The demo above it is pinned to v0.5.3, so it keeps working when a new version ships.