DEV Community

kashish
kashish

Posted on

How to Draw Shapes with Different Colors in MapLibre GL JS

If you've ever used mapbox-gl-draw or any MapLibre drawing library, you've probably
hit this frustrating limitation: all your drawn shapes share the same color.

Want polygon A in red and polygon B in green? Not straightforward. I ran into this
problem while building a GIS tool and couldn't find a clean solution β€” so I built one.

The problem

The default drawing libraries for MapLibre GL JS apply one global style to every
feature. If you try to set colors per feature using user_color properties, you
quickly discover it doesn't work reliably, and there are GitHub issues going back
to 2017 from developers hitting the same wall.

What I built

maplibre-gl-multiple-color-draw
β€” a lightweight drawing library where every shape keeps its own individual color
and thickness.

πŸ‘‰ Live Demo

Features:

  • 6 drawing modes: Line, Dashed Line, Freehand, Freehand Dashed, Polygon, Select/Move
  • Each feature has its own color and thickness β€” set it before you draw
  • Change color and thickness at runtime with setColor() and setThickness()
  • Export everything as standard GeoJSON
  • Built-in React hook (useMapDraw)
  • Full TypeScript support
  • Zero dependencies (only peer dep: maplibre-gl)

Installation

npm install maplibre-gl-multiple-color-draw
npm install maplibre-gl
Enter fullscreen mode Exit fullscreen mode

Basic React example

import { useEffect, useRef, useState } from "react";
import maplibregl, { Map as MaplibreMap } from "maplibre-gl";
import { useMapDraw } from "maplibre-gl-multiple-color-draw";
import "maplibre-gl/dist/maplibre-gl.css";

function MapDrawComponent() {
  const mapContainer = useRef<HTMLDivElement>(null);
  const mapRef = useRef<MaplibreMap | null>(null);
  const [map, setMap] = useState<MaplibreMap | null>(null);
  const [color, setColorState] = useState("#3388ff");
  const [thickness, setThicknessState] = useState(2);

  useEffect(() => {
    if (!mapContainer.current || mapRef.current) return;
    const newMap = new maplibregl.Map({
      container: mapContainer.current,
      style: "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
      center: [0, 0],
      zoom: 2,
    });
    mapRef.current = newMap;
    setMap(newMap);
    return () => { newMap.remove(); mapRef.current = null; };
  }, []);

  const { enable, setMode, setColor, setThickness, getGeoJSON, clear } =
    useMapDraw(map, { color, thickness });

  useEffect(() => { if (map) enable(); }, [map, enable]);

  return (
    <div>
      <div style={{ display: "flex", gap: "8px", padding: "10px" }}>
        <button onClick={() => setMode("line")}>Line</button>
        <button onClick={() => setMode("freehand")}>Freehand</button>
        <button onClick={() => setMode("polygon")}>Polygon</button>
        <button onClick={() => setMode("select")}>Select</button>

        <input
          type="color"
          value={color}
          onChange={(e) => { setColorState(e.target.value); setColor(e.target.value); }}
        />

        <input
          type="range" min="1" max="10" value={thickness}
          onChange={(e) => { setThicknessState(Number(e.target.value)); setThickness(Number(e.target.value)); }}
        />

        <button onClick={() => clear()}>Clear</button>
      </div>
      <div ref={mapContainer} style={{ width: "100%", height: "500px" }} />
    </div>
  );
}

export default MapDrawComponent;
Enter fullscreen mode Exit fullscreen mode

Pick a color β†’ draw a shape β†’ pick a different color β†’ draw another shape. Each one
keeps its own style independently.

Export as GeoJSON

When you're done drawing, get everything as standard GeoJSON:

const geoJSON = getGeoJSON();
// ready to send to your API or download
Enter fullscreen mode Exit fullscreen mode

Links

It's new and I'd love feedback β€” drop a comment if you try it or run into any issues!

Top comments (0)