Проблема
Так уж вышло, что Chart.js не предоставляет возможности влиять на внешний вид легенды графика. Можно изменить некоторые её параметры, но чтобы хоть как-то поменять структуру её элемента необходимо прибегнуть к помощи плагина.
При попытке воспроизвести пример с плагином в среде с реактом я столкнулся с проблемой, при которой элементы легенды рисовались несколько раз. Возможно, существует какая-то глубоко зарытая опция для её решения, но на мой взгляд куда большей проблемой является включение ванильного джаваскрипта из плагина в код реактового компонента.
Решение проблемы
Было принято решение рисовать легенду средствами реакта, используя ту информацию, которую можно получить из объекта класса Chart, лежащего в основе графика. react-chartjs-2 позволяет получить этот объект с помощью свойства ref. В связи с тем, что Chart будет использоваться во время рендеринга, необходимо поместить его в состояние:
const [chart, setChart] = useState();
return <Line
ref={chart => setChart(chart)}
data={
datasets: [
{label: 'Данные 1', ...},
{label: 'Данные 2', ...},
],
...
}
...
/>
После первого рендера в переменной chart окажется нужный нам объект, который можно использовать для рендера элементов легенды (они указаны в свойстве label у datasets):
return <>
<ul className="legend">
{/* Получаем элементы легенды и рисуем их */}
{chart?.legend?.legendItems?.map(({datasetIndex, text, fillStyle, strokeStyle}) => {
if (datasetIndex === undefined) return null;
return <li className="legend__item">
{/* Следующий элемент отображает цвет графика */}
<div className='stsos-legend-item__color-box' style={{background: fillStyle, borderColor: strokeStyle}}/>
{/* text содержит в себе label dataset'a */}
{text}
</li>;
})}
</ul>
<Line
options={{
// Не забываем выключить дефолтную легенду
plugins: {
legend: {
display: false,
}
}
}}
/>
</>;
Дело осталось за малым. Дефолтная легенда позволяет по клику на элемент скрывать/отображать относящийся к нему график (в легенде при этом элемент перечёркивается в случае скрытия). Чтобы воспроизвести такое поведение, необходимо создать дополнительное состояние, в котором будет храниться массив с логическими значениями факта скрытия графика. При клике на элемент, используя chart, будем скрывать соответствующий график, а затем обновлять значение в ранее упомянутом массиве по нужному индексу, чтобы отслеживать состояние элемента легенды. Финальный код примет примерно следующий вид:
const [prevChart, setPrevChart] = useState();
const [chart, setChart] = useState();
// Состояние для ранее упомянутого массива
// Пример: [true, false] (график с "Данные 1" скрыт, график с "Данные 2" отображён)
const [datasetHiddens, setDatasetHiddens] = useState();
// chart инициализируется только один раз, обновления легенды не обновляют сам chart
if (chart !== prevChart) {
setPrevChart(chart);
setDatasetHiddens(chart?.legend?.legendItems?.map((item) => !item.hidden));
}
return <>
<ul className="legend">
{chart?.legend?.legendItems?.map(({datasetIndex, text, fillStyle, strokeStyle}) => {
if (datasetIndex === undefined || !datasetHiddens) return null;
const isHidden = datasetHiddens[datasetIndex];
return <li
className="legend__item"
// Собственно то, зачем мы и заводили состояние с массивом
style={isHidden ? {
textDecoration: 'line-through'
}: undefined}
onClick={() => {
chart.setDatasetVisibility(datasetIndex, !isHidden);
setDatasetHiddens(prev => {
if (!prev) return;
const result = [...prev];
result[datasetIndex] = !isHidden;
return result;
});
chart.update();
}}
>
{/* Следующий элемент отображает цвет графика */}
<div className='stsos-legend-item__color-box' style={{background: fillStyle, borderColor: strokeStyle}}/>
{/* text содержит в себе label dataset'a */}
{text}
</li>;
})}
</ul>
<Line
ref={chart => setChart(chart)}
data={{ datasets: [ {label: 'Данные 1'}, {label: 'Данные 2'}, ] }}
options={{
plugins: {
legend: {
display: false,
}
}
}} />
</>;
В моём случае, после некоторых изменений в шаблоне и стилях, данный подход позволил визуально и функционально воспроизвести дефолтную легенду с добавлением в неё своих данных (красная надпись с процентами).