Ctrl + k

or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-leaflet@5.0.x

docs

index.md
tile.json

tessl/npm-react-leaflet

tessl install tessl/npm-react-leaflet@5.0.3

React components for Leaflet maps

vector-shapes.mddocs/reference/

Vector Shapes

Components for drawing geometric shapes on the map including circles, lines, polygons, and rectangles. All shape components support styling through path options and can contain popups and tooltips.

Related Documentation

  • Markers and Popups - Point markers as alternative visualization
  • Layer Groups - Grouping multiple shapes
  • GeoJSON - Working with GeoJSON geometries
  • Map Container - Performance options for many shapes

Capabilities

Circle Component

Circular shape with radius specified in meters.

/**
 * Component for displaying circles on the map (radius in meters)
 * @param props - Circle properties
 */
const Circle: FunctionComponent<CircleProps>;

interface CircleProps extends CircleOptions, PathProps {
  /** Center point of the circle (required) */
  center: LatLngExpression;
  /** Radius in meters */
  radius?: number;
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, Circle } from "react-leaflet";

function Map() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <Circle
        center={[51.505, -0.09]}
        radius={500}
        pathOptions={{ color: "red", fillColor: "blue", fillOpacity: 0.5 }}
      />
    </MapContainer>
  );
}

CircleMarker Component

Circular marker with radius specified in pixels (constant size regardless of zoom).

/**
 * Component for displaying circle markers (radius in pixels)
 * @param props - Circle marker properties
 */
const CircleMarker: FunctionComponent<CircleMarkerProps>;

interface CircleMarkerProps extends CircleMarkerOptions, PathProps {
  /** Center point of the circle marker (required) */
  center: LatLngExpression;
  /** Radius in pixels */
  radius?: number;
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

<CircleMarker
  center={[51.505, -0.09]}
  radius={20}
  pathOptions={{ color: "green", fillColor: "yellow" }}
>
  <Popup>A circle marker</Popup>
</CircleMarker>

Polyline Component

Multi-segment line connecting multiple points.

/**
 * Component for displaying polylines (multi-segment lines)
 * @param props - Polyline properties
 */
const Polyline: FunctionComponent<PolylineProps>;

interface PolylineProps extends PolylineOptions, PathProps {
  /** Array of positions defining the line segments (required) */
  positions: LatLngExpression[] | LatLngExpression[][];
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

const positions = [
  [51.505, -0.09],
  [51.51, -0.1],
  [51.51, -0.12],
  [51.52, -0.11],
];

<Polyline
  positions={positions}
  pathOptions={{ color: "blue", weight: 3 }}
>
  <Popup>A blue polyline</Popup>
</Polyline>

Multi-Line Polyline:

const multiLine = [
  [[51.5, -0.1], [51.51, -0.11], [51.52, -0.12]],
  [[51.5, -0.05], [51.51, -0.06], [51.52, -0.07]],
];

<Polyline positions={multiLine} pathOptions={{ color: "red" }} />

Polygon Component

Filled polygon shape with optional holes.

/**
 * Component for displaying polygons (closed shapes with fill)
 * @param props - Polygon properties
 */
const Polygon: FunctionComponent<PolygonProps>;

interface PolygonProps extends PolylineOptions, PathProps {
  /** Array of positions defining the polygon vertices (required) */
  positions: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][];
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Simple Polygon:

const polygonPositions = [
  [51.515, -0.09],
  [51.52, -0.1],
  [51.52, -0.12],
];

<Polygon
  positions={polygonPositions}
  pathOptions={{ color: "purple", fillColor: "pink" }}
>
  <Popup>A triangular polygon</Popup>
</Polygon>

Polygon with Holes:

const outerRing = [
  [51.515, -0.09],
  [51.52, -0.1],
  [51.52, -0.12],
  [51.515, -0.11],
];
const hole = [
  [51.517, -0.1],
  [51.518, -0.105],
  [51.517, -0.11],
];

<Polygon positions={[outerRing, hole]} />

Rectangle Component

Rectangular shape defined by geographic bounds.

/**
 * Component for displaying rectangles
 * @param props - Rectangle properties
 */
const Rectangle: FunctionComponent<RectangleProps>;

interface RectangleProps extends PathOptions, PathProps {
  /** Geographic bounds of the rectangle (required) */
  bounds: LatLngBoundsExpression;
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

const bounds = [
  [51.49, -0.08],
  [51.5, -0.06],
];

<Rectangle
  bounds={bounds}
  pathOptions={{ color: "orange", weight: 2 }}
>
  <Popup>A rectangular area</Popup>
</Rectangle>

Types

PathProps

interface PathProps extends InteractiveLayerProps {
  /** Styling options for the path */
  pathOptions?: PathOptions;
}

interface InteractiveLayerProps extends LayerProps, InteractiveLayerOptions {
  interactive?: boolean;
  bubblingMouseEvents?: boolean;
}

PathOptions

// From Leaflet
interface PathOptions {
  /** Whether to draw stroke along the path */
  stroke?: boolean;
  /** Stroke color */
  color?: string;
  /** Stroke width in pixels */
  weight?: number;
  /** Stroke opacity (0.0 - 1.0) */
  opacity?: number;
  /** Line cap shape */
  lineCap?: LineCapShape;
  /** Line join shape */
  lineJoin?: LineJoinShape;
  /** Dash pattern for stroke (e.g., "10, 5" for 10px dash, 5px gap) */
  dashArray?: string;
  /** Distance into the dash pattern to start dash */
  dashOffset?: string;
  /** Whether to fill the path with color */
  fill?: boolean;
  /** Fill color (defaults to color if not specified) */
  fillColor?: string;
  /** Fill opacity (0.0 - 1.0) */
  fillOpacity?: number;
  /** Fill rule for complex polygons */
  fillRule?: FillRule;
  /** Custom class name for the path element */
  className?: string;
}

type LineCapShape = 'butt' | 'round' | 'square';
type LineJoinShape = 'miter' | 'round' | 'bevel';
type FillRule = 'nonzero' | 'evenodd';

CircleOptions

// From Leaflet (extends PathOptions)
interface CircleOptions extends PathOptions {
  /** Radius of the circle in meters */
  radius?: number;
}

CircleMarkerOptions

// From Leaflet (extends PathOptions)
interface CircleMarkerOptions extends PathOptions {
  /** Radius of the circle marker in pixels */
  radius?: number;
}

PolylineOptions

// From Leaflet (extends PathOptions)
interface PolylineOptions extends PathOptions {
  /** Simplification tolerance in pixels */
  smoothFactor?: number;
  /** Whether to disable polyline clipping */
  noClip?: boolean;
}

Styling

Basic Styling

<Circle
  center={[51.505, -0.09]}
  radius={500}
  pathOptions={{
    color: "red",
    fillColor: "blue",
    fillOpacity: 0.5,
    weight: 2,
  }}
/>

Dashed Lines

<Polyline
  positions={positions}
  pathOptions={{
    color: "blue",
    weight: 3,
    dashArray: "10, 5",
  }}
/>

No Fill

<Polygon
  positions={positions}
  pathOptions={{
    color: "green",
    fill: false,
    weight: 3,
  }}
/>

Custom Line Caps and Joins

<Polyline
  positions={positions}
  pathOptions={{
    color: "purple",
    weight: 5,
    lineCap: "round",
    lineJoin: "round",
  }}
/>

Important Notes

  1. Circle vs CircleMarker:

    • Circle: Radius in meters, size changes with zoom
    • CircleMarker: Radius in pixels, constant size at all zoom levels
  2. Position Updates: All shapes update when their position/bounds props change:

    <Circle center={currentCenter} radius={currentRadius} />
  3. Multi-Part Shapes: Polylines and Polygons can have multiple parts:

    // Multi-line polyline
    <Polyline positions={[line1, line2, line3]} />
    
    // Polygon with holes
    <Polygon positions={[outerRing, hole1, hole2]} />
  4. Event Handling: All shapes support event handlers:

    <Polygon
      positions={positions}
      eventHandlers={{
        click: (e) => console.log("Polygon clicked", e),
        mouseover: (e) => console.log("Mouse over"),
        mouseout: (e) => console.log("Mouse out"),
      }}
    />
  5. Interactive Shapes: Control interactivity with the interactive prop:

    <Circle center={pos} radius={500} interactive={false} />
  6. Children Support: All shapes can contain Popups and Tooltips:

    <Polyline positions={positions}>
      <Popup>Line information</Popup>
      <Tooltip>Hover text</Tooltip>
    </Polyline>
  7. Path Options vs Direct Props: Use pathOptions for styling:

    // Correct
    <Circle center={pos} radius={500} pathOptions={{ color: "red" }} />
    
    // Also works (inherited from PathOptions)
    <Circle center={pos} radius={500} color="red" />
  8. Bounds Expression: Rectangles accept flexible bound formats:

    // Array format
    <Rectangle bounds={[[51.49, -0.08], [51.5, -0.06]]} />
    
    // LatLngBounds object
    <Rectangle bounds={new LatLngBounds([51.49, -0.08], [51.5, -0.06])} />

Examples

Heat Map Simulation with Circles

const hotspots = [
  { position: [51.505, -0.09], intensity: 0.8 },
  { position: [51.51, -0.1], intensity: 0.6 },
  { position: [51.52, -0.08], intensity: 0.4 },
];

function HeatMap() {
  return (
    <>
      {hotspots.map((spot, idx) => (
        <Circle
          key={idx}
          center={spot.position}
          radius={300}
          pathOptions={{
            fillColor: "red",
            fillOpacity: spot.intensity,
            color: "red",
            weight: 1,
          }}
        />
      ))}
    </>
  );
}

Interactive Path with State

function InteractivePath() {
  const [color, setColor] = useState("blue");

  return (
    <Polyline
      positions={positions}
      pathOptions={{ color, weight: 5 }}
      eventHandlers={{
        click: () => setColor(color === "blue" ? "red" : "blue"),
      }}
    >
      <Popup>Click the line to change color</Popup>
    </Polyline>
  );
}

Drawing Route

function RouteDisplay({ route }) {
  return (
    <>
      <Polyline
        positions={route.path}
        pathOptions={{ color: "blue", weight: 4 }}
      >
        <Popup>
          <div>
            <h4>{route.name}</h4>
            <p>Distance: {route.distance} km</p>
          </div>
        </Popup>
      </Polyline>
      <CircleMarker
        center={route.path[0]}
        radius={8}
        pathOptions={{ color: "green", fillColor: "green" }}
      >
        <Tooltip permanent>Start</Tooltip>
      </CircleMarker>
      <CircleMarker
        center={route.path[route.path.length - 1]}
        radius={8}
        pathOptions={{ color: "red", fillColor: "red" }}
      >
        <Tooltip permanent>End</Tooltip>
      </CircleMarker>
    </>
  );
}

Zoned Area

function ZonedArea() {
  const zones = [
    { bounds: [[51.49, -0.12], [51.50, -0.10]], color: "green", name: "Safe Zone" },
    { bounds: [[51.50, -0.12], [51.51, -0.10]], color: "yellow", name: "Caution Zone" },
    { bounds: [[51.51, -0.12], [51.52, -0.10]], color: "red", name: "Danger Zone" },
  ];

  return (
    <>
      {zones.map((zone, idx) => (
        <Rectangle
          key={idx}
          bounds={zone.bounds}
          pathOptions={{
            color: zone.color,
            fillColor: zone.color,
            fillOpacity: 0.3,
          }}
        >
          <Popup>{zone.name}</Popup>
        </Rectangle>
      ))}
    </>
  );
}

Performance Optimization

Canvas Renderer for Many Shapes

When rendering many vector shapes (>1000), use Canvas renderer instead of SVG:

<MapContainer preferCanvas={true} center={[51.505, -0.09]} zoom={13}>
  <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
  {/* Thousands of shapes render faster with Canvas */}
  {shapes.map(shape => (
    <Circle key={shape.id} center={shape.center} radius={shape.radius} />
  ))}
</MapContainer>

Memoized Shapes

const MemoizedCircle = React.memo(({ center, radius, color }) => (
  <Circle
    center={center}
    radius={radius}
    pathOptions={{ color, fillColor: color, fillOpacity: 0.5 }}
  />
));

function OptimizedShapes({ shapes }) {
  return (
    <>
      {shapes.map(shape => (
        <MemoizedCircle
          key={shape.id}
          center={shape.center}
          radius={shape.radius}
          color={shape.color}
        />
      ))}
    </>
  );
}

Simplify Complex Polygons

<Polygon
  positions={complexPositions}
  pathOptions={{
    smoothFactor: 2.0, // Higher value = more simplification
  }}
/>

Conditional Rendering Based on Zoom

function ZoomDependentShapes() {
  const [zoom, setZoom] = useState(13);

  return (
    <MapContainer center={[51.505, -0.09]} zoom={zoom}>
      <MapEvents onZoomChange={setZoom} />
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      
      {zoom > 12 && (
        // Only render detailed shapes at high zoom levels
        <Circle center={[51.505, -0.09]} radius={100} />
      )}
    </MapContainer>
  );
}

Advanced Patterns

Animated Shapes

function AnimatedCircle({ center, radius }) {
  const [currentRadius, setCurrentRadius] = useState(0);

  useEffect(() => {
    let frame = 0;
    const animate = () => {
      frame++;
      setCurrentRadius(Math.sin(frame * 0.1) * radius + radius);
      if (frame < 100) {
        requestAnimationFrame(animate);
      }
    };
    animate();
  }, [radius]);

  return (
    <Circle
      center={center}
      radius={currentRadius}
      pathOptions={{ color: "blue", fillOpacity: 0.3 }}
    />
  );
}

Interactive Shape Editing

function EditablePolygon() {
  const [positions, setPositions] = useState([
    [51.515, -0.09],
    [51.52, -0.1],
    [51.52, -0.12],
  ]);

  const handleClick = (e) => {
    setPositions([...positions, [e.latlng.lat, e.latlng.lng]]);
  };

  return (
    <>
      <Polygon
        positions={positions}
        pathOptions={{ color: "purple" }}
        eventHandlers={{
          click: handleClick,
        }}
      />
      {positions.map((pos, idx) => (
        <CircleMarker
          key={idx}
          center={pos}
          radius={5}
          draggable={true}
          eventHandlers={{
            dragend: (e) => {
              const newPos = e.target.getLatLng();
              const newPositions = [...positions];
              newPositions[idx] = [newPos.lat, newPos.lng];
              setPositions(newPositions);
            },
          }}
        />
      ))}
    </>
  );
}

Shape with Gradient Fill (using SVG)

function GradientCircle({ center, radius }) {
  return (
    <>
      <defs>
        <radialGradient id="grad1">
          <stop offset="0%" style={{ stopColor: "rgb(255,255,0)", stopOpacity: 1 }} />
          <stop offset="100%" style={{ stopColor: "rgb(255,0,0)", stopOpacity: 1 }} />
        </radialGradient>
      </defs>
      <Circle
        center={center}
        radius={radius}
        pathOptions={{
          fillColor: "url(#grad1)",
          fillOpacity: 0.7,
        }}
      />
    </>
  );
}

Troubleshooting

Shapes Not Appearing

Solutions:

  1. Verify positions are valid coordinates
  2. Check pathOptions for visibility (opacity, fill)
  3. Ensure shapes are inside MapContainer
  4. Check z-index and pane settings

Performance Issues with Many Shapes

Solutions:

  1. Use preferCanvas={true} in MapContainer
  2. Implement shape clustering or aggregation
  3. Use memoization (React.memo)
  4. Render only visible shapes based on map bounds
  5. Simplify complex polygons with smoothFactor

Shapes Not Updating

Solutions:

  1. Ensure position/bounds props are changing
  2. Use key prop to force re-render
  3. Check that pathOptions are being applied
  4. Verify update functions in custom components

Click Events Not Firing

Solutions:

  1. Set interactive={true} (default for most shapes)
  2. Check that shape has fill (clicks only register on filled areas for polygons)
  3. Verify event handlers are properly attached
  4. Check for overlapping shapes with higher z-index

Testing Vector Shapes

Unit Testing

import { render } from '@testing-library/react';
import { MapContainer, Circle } from 'react-leaflet';

test('renders circle shape', () => {
  const { container } = render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <Circle 
        center={[51.505, -0.09]} 
        radius={500}
        pathOptions={{ color: 'red' }}
      />
    </MapContainer>
  );
  
  expect(container).toBeInTheDocument();
});

Testing Shape Interactions

test('handles shape click events', () => {
  const handleClick = jest.fn();
  
  render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <Polygon
        positions={[[51.515, -0.09], [51.52, -0.1], [51.52, -0.12]]}
        eventHandlers={{ click: handleClick }}
      />
    </MapContainer>
  );
  
  // Simulate interaction
});

Accessibility for Vector Shapes

Adding ARIA Labels

<Circle 
  center={[51.505, -0.09]} 
  radius={500}
  // Add title for accessibility
>
  <Tooltip permanent>Area of interest</Tooltip>
</Circle>

Interactive Shapes with Keyboard Support

<Polygon
  positions={positions}
  interactive={true}
  pathOptions={{ color: 'blue' }}
>
  <Popup>
    <div role="dialog" aria-label="Selected area details">
      <h3>Area Information</h3>
      <p>Additional details</p>
    </div>
  </Popup>
</Polygon>

Type-Safe Shape Creation

Using TypeScript with Vector Shapes

import type { LatLngExpression, PathOptions } from 'leaflet';

interface CircleData {
  center: LatLngExpression;
  radius: number;
  style: PathOptions;
}

const circleData: CircleData = {
  center: [51.505, -0.09],
  radius: 500,
  style: {
    color: 'red',
    fillColor: 'blue',
    fillOpacity: 0.5,
  },
};

<Circle
  center={circleData.center}
  radius={circleData.radius}
  pathOptions={circleData.style}
/>