jharris711
jharris711

Reputation: 612

React-Leaflet Set fillOpacity of single ellipse on mouseover/mouseout

DEMO

I have a map created with React-Leaflet that displays Ellipses created with Leaflet.ellipse and React-Leaflet Core API.

I'm passing a mouse event handler to the ellipse's eventHandlers prop. When I hover the mouse over the ellipse, the fillOpacity is changed from 0.0 to 0.5, then changed from 0.5 to 0.0 on mouse out.

The problem is that when the event is triggered, the fillOpacity for every ellipse changes rather than just the ellipse that is being hovered over. How can I make it so that only the specific ellipse being hovered over has its fillOpacity changed?

Map Component


const Map = () => {
  const [map, setMap] = useState(null);
  const [fillOpacity, setFillOpacity] = useState(0)

  const onMouseEvent = (event, type) => {
    switch (type) {
      case 'over':
        setFillOpacity(0.5)
        break
      case 'out':
        setFillOpacity(0.0)
        break
      default:
        break
    }
  }

  return (
    <>
      <MapContainer
        center={[38, -82]}
        zoom={4}
        zoomControl={false}
        style={{ height: "100vh", width: "100%", padding: 0 }}
        whenCreated={map => setMap(map)}
      >
        <LayersControl position="topright">
          <LayersControl.BaseLayer checked name="Map">
            <TileLayer
              attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
              url={maps.base}
            />
          </LayersControl.BaseLayer>
          <LayersControl.Overlay checked name="Ellipses">
            <FeatureGroup>
              <>{CITIES.map((city, index) => {
                return (
                  <Ellipse
                    key={index}
                    center={city.center} 
                    radii={city.radii} 
                    tilt={city.tilt} 
                    pathOptions={{
                      fillOpacity,
                      opacity: 1,
                      weight: 2,
                    }}
                    eventHandlers={{
                      mouseover: (event, type) => onMouseEvent(event, 'over'),
                      mouseout: (event, type) => onMouseEvent(event, 'out'),
                    }}
                  >
                    <Popup>
                      <Typography variant='subtitle1'>{city.city}</Typography>
                    </Popup>
                  </Ellipse>
                )
              })}</>
              </FeatureGroup>
            </LayersControl.Overlay>
        </LayersControl>
      </MapContainer>
    </>
  );
};

Ellipse Component


import { createPathComponent } from '@react-leaflet/core'
import L from 'leaflet'
import 'leaflet-ellipse'

const Ellipse = createPathComponent(createEllipse, updateEllipse)

function createEllipse(props, context) {
  const { center, radii, tilt, options } = props
  const instance = new L.Ellipse(center, radii, tilt, options)
  return {
    instance,
    context: { ...context, overlayContainer: instance },
  }
}

function updateEllipse(instance, props, prevProps) {
  if (
    props.center !== prevProps.center ||
    props.radii !== prevProps.radii ||
    props.tilt !== prevProps.tilt ||
    props.options !== prevProps.options
  ) {
    instance.setStyle(props.options)
    instance.setLatLng(props.center)
    instance.setRadius(props.radii)
    instance.setTilt(props.tilt)
  }
}

export default Ellipse

Upvotes: 0

Views: 487

Answers (1)

Seth Lutske
Seth Lutske

Reputation: 10676

The problem is that you are using the same state variable for all the elipses. Here:

{CITIES.map((city, index) => {
  return (
    <Ellipse
      key={index}
      pathOptions={{
        fillOpacity. // <---- problem here
      }}
    >
      ...
    </Ellipse>
  )
})}

So all ellipses are getting their opacity from the fillOpacity state variable. When you mouse over any of them, the state variable changes, and all of them respond. You'd be better off not handling opacity as a state variable. Just set it to a constant 0, and within the event handler, dig into the target of the mouseover, and call setStyle:

  const onMouseEvent = (event, type) => {
    switch (type) {
      case 'over':
        event.target.setStyle({ fillOpacity: 0.5 })
        break
      case 'out':
        event.target.setStyle({ fillOpacity: 0.0 })
        break
      default:
        break
    }
  }

So now each ellipse manages its own opacity directly through leaflet methods, rather than all inheriting their opacity through a too-broadly-applied react state variable.

Working demo

Upvotes: 2

Related Questions