Zrobiłem przykład online https://stackblitz.com/edit/react-c1rcyu?file=src/App.js (jakby link jakoś nie działał to daj znać).
W swoim przykładzie zrobiłem coś takiego, że cały obrazek SVG wyeksportowałem do osobnego komponentu i otoczyłem go funkcją forwardRef
i użyłem useRef
, żeby móc go pobrać i zmieniać odpowiednie atrybuty. Dodatkowo musiałem zmienić zapis atrybutu style
, bo React się czepiał, że wartość nie jest obiektem style={{ fill: '#94add6', fillOpacity: 1 }}
. Dodałem im jakieś ID, ale równie dobrze mogłaby to być klasa, albo jakikolwiek inny atrybut, który będzie unikatowy (tylko taka drobna uwaga, że w tym przykładzie online numeracja województw jest trochę upośledzona, bo w którymś zrobiłem literówkę i wyszło mi finalnie 17 zamiast 16 :D :D)
https://pl.reactjs.org/docs/forwarding-refs.html
https://pl.reactjs.org/docs/hooks-reference.html#useref
Kopiuj
const Poland = forwardRef((props, ref) => {
return (
<svg
ref={ref}
...
>
<path
...
style={{ fill: '#94add6', fillOpacity: 1 }}
id="territory-1"
/>
...
/>
</svg>
);
};
export default Poland;
W drugim komponencie jest prosta textarea i nasza mapka

I dla uproszczenia cały mechanizm wrzuciłem do jednego komponentu, ale można go powydzielać do osobnych funkcji / hooków.
Kopiuj
import React, { useState, useRef, useEffect } from 'react';
import Poland from './poland.js';
const App = () => {
const [territories, setTerritories] = useState('');
const polandRef = useRef();
useEffect(() => {
const territoriesIds = territories
.split(',')
.map((el) => el.trim())
.filter((el) => el);
for (const path of polandRef.current.querySelectorAll('path')) {
if (territoriesIds.some((tId) => `territory-${tId}` === path.id)) {
if (!path.dataset.color) {
const hue = Math.random() * 100;
const saturation = Math.random() * 100;
const lightness = Math.random() * 100;
const color = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
path.dataset.color = color;
}
path.style.fill = path.dataset.color;
} else {
path.style.fill = '#94add6';
}
}
}, [territories]);
const transormTerritories = (e) => {
const values = e.target.value.replace(/,+/, ',');
setTerritories(values);
};
return (
<>
<textarea onChange={transormTerritories} value={territories}></textarea>
<Poland ref={polandRef} />
</>
);
};
export default App;
MetodatransormTerritories
waliduje tekst wpisany przez użytkownika (usuwa nadmiarowe przecinki :D :p)
Na samym początku hooka useEffect
robię proste przekształcenie wartości pobranych od użytkownika w tablicę ID i pobieram wszystkie znaczniki path
z svg
. Jeśli konkretny path
posiada wartość z tablicy to losuje mu kolor i przypisuje go do customowego atrybutu dataset, oraz ustawiam odpowiednią wartość fill
. W przypadku gdy nie ma takiego id to ustawiam standardowy kolor, który był na samym początku.
Walidacja pola textarea jest trochę słaba, bo pewnie można znaleźć więcej rzeczy, niż usuwanie przecinków i nadmiarowych spacji, ale starałem się uprościć wszystko do maksimum.
EDIT:
Jeszcze wpadłem na pomysł, że można byłoby zmienić lekko generowanie kolorów. Na samym początku losujemy wszystkie kolory, których będziemy używać i później jedynie przypisujemy wylosowany kolor.
Kopiuj
useEffect(() => {
for (const path of polandRef.current.querySelectorAll('path')) {
const hue = Math.random() * 100;
const saturation = Math.random() * 100;
const lightness = Math.random() * 100;
const color = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
path.dataset.color = color;
}
}, [polandRef.current]);
useEffect(() => {
const territoriesIds = territories
.split(',')
.map((el) => el.trim())
.filter((el) => el);
for (const path of polandRef.current.querySelectorAll('path')) {
path.style.fill = territoriesIds.some((tId) => `territory-${tId}` === path.id)
? path.dataset.color
: '#94add6'
}
}, [territories]);
<path class="super-path" ...>
[id*="super"] { fill: #abc; }
https://www.w3schools.com/css/css_attribute_selectors.asp to trzeba będzie część rzeczy zdublikować. Możliwe, że dałoby się coś uprościć, ale musiałbym wiedzieć w jaki sposób jest to generowane