Leaflet / Leaflet

🍃 JavaScript library for mobile-friendly interactive maps 🇺🇦
https://leafletjs.com
BSD 2-Clause "Simplified" License
40.17k stars 5.75k forks source link

Add `resetStyle` to `L.featureGroup` #9309

Open kraktus opened 1 month ago

kraktus commented 1 month ago

Checklist

Motivation

Hello, thanks for the great lib.

The geojson layers has plenty of QoL methods like geojson.resetStyle() that would be great to have when working with other types of layers, in my case L.featureGroup.

When hovering off a featureGroup it is very inconvenient to reset its style, when each element of it has a different style. The advice found on the internet advising to store a default and highlight style, only works when you want to style all the elements similarly.

pseudo-code MCVE

const highLightStyle = {
  weight: 3,
  color: '#666',
  dashArray: '',
  fillOpacity: 0.7,
};

let currentlyHighlighted: L.Layer | undefined = undefined;

const resetHighlight = (e: L.LeafletMouseEvent) => {
  ??? // here layer.resetStyle() would be perfect
  // layer.options does not work for FeatureGroup
};

const highlightFeature = (e: L.LeafletMouseEvent) => {
    let layer = e.target;
    currentlyHighlighted = layer;
    layer.setStyle(highLightStyle);
    layer.bringToFront();
  };

export const mcve = (map: L.Map) => {
  // minimal geojson polygon
  const polygon = {
    ...
  }
  const geojson = L.geoJson(polygon, { style: ... }); // different than the circles
  const circles = [...] // each circle has a different style
  // in my case the click behavior is different for circles and geojson, and is added independently 
  ...
  // mouseover is shared
  const layer = L.featureGroup([polygon, ...circles]);
    layer.on({
      mouseover: highlightFeature,
      mouseout: resetHighlight,
    });

  layer.addTo(map);
}

Suggested solution

Add resetStyle method to L.featureGroup, behaving exactly like the method of the same name on L.geojson

Alternatives considered

Maybe to amend the geojson to add the circles as a MultiPoint in it? But in that case hovering over a point would not display the outline of the polygon.

kraktus commented 1 month ago

In the meantime, if someone else stumbles on this issue, here is a workaround (basically tracking the style of each object yourself).

const highLightStyle = {
  weight: 3,
  color: '#666',
  dashArray: '',
  fillOpacity: 0.7,
};

const resetHighlight = (originalStyle: OriginalStyle) => (e: L.LeafletMouseEvent) => {
  originalStyle.resetStyle()
};

const highlightFeature = (e: L.LeafletMouseEvent) => {
    let layer = e.target;
    currentlyHighlighted = layer;
    layer.setStyle(highLightStyle);
    layer.bringToFront();
  };

export const mcve = (map: L.Map) => {
  // minimal geojson polygon
  const polygon = {
    ...
  }
  const originalStyle = new OriginalStyle();
  const geojson = L.geoJson(polygon, { style: style }); // different than the circles
  originalStyle.addGeoJson(geojson, style(style));
  const circles = [...] // each circle has a different style
  circles.foreach(c => originalStyle.addPath(c));
  // in my case the click behavior is different for circles and geojson, and is added independently 
  ...
  // mouseover is shared
  const layer = L.featureGroup([polygon, ...circles]);
    layer.on({
      mouseover: highlightFeature,
      mouseout: resetHighlight(originalStyle),
    });

  layer.addTo(map);
}

const style = (feature?: Feature): L.PathOptions => { ... }

// Manually track the original style of each object in the layer
class OriginalStyle {
  private elm: [L.Path | L.GeoJSON, L.PathOptions][];
  constructor() {
    this.elm = [];
  }
  addPath(layer: L.Path) {
    // deepCopy is conservative here
    this.elm.push([layer, deepCopy(layer.options)]);
  }

  addGeoJson(geojson: L.GeoJSON, styleOptions: L.PathOptions) {
    // deepCopy is conservative here
    this.elm.push([geojson, deepCopy (styleOptions)]);
  }

  resetStyle() {
    for (const [layer, style] of this.elm) {
      layer.setStyle(style);
    }
  }
}