From e84662a746c372bc3847138b31e669d0cba3e9cb Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 26 Jun 2024 02:58:14 +0200 Subject: [PATCH] replaced chartjs legend with custom HTML legend --- public/elotracker/chart-integration.js | 141 +++++++++++++++++++------ public/elotracker/index.php | 1 + public/elotracker/styles.css | 42 ++++++++ 3 files changed, 154 insertions(+), 30 deletions(-) diff --git a/public/elotracker/chart-integration.js b/public/elotracker/chart-integration.js index 418bf4a..834f4a4 100644 --- a/public/elotracker/chart-integration.js +++ b/public/elotracker/chart-integration.js @@ -46,48 +46,115 @@ const zoom_plugin = { mode: "x", }, } -const trigger_legend_hover = function (e, legendItem, legend) { - if (legendItem.hidden) { + +let hovered = false; +const trigger_legend_hover = function (legendItem, chart) { + if (legendItem.hidden || hovered) { return; } - legend.chart.data.datasets.forEach((dataset, i) => { + hovered = true; + chart.data.datasets.forEach((dataset, i) => { dataset.backgroundColor = (legendItem.datasetIndex === i || dataset.backgroundColor.length === 9) ? dataset.backgroundColor : dataset.backgroundColor + '3D'; dataset.borderColor = (legendItem.datasetIndex === i || dataset.borderColor.length === 9) ? dataset.borderColor : dataset.borderColor + '3D'; dataset.borderWidth = legendItem.datasetIndex === i ? 4 : 3; dataset.pointRadius = dataset.pointRadius===0 ? 0 : 2; }); - legend.chart.update(); + chart.update(); } -const trigger_legend_leave = function (e, legendItem, legend) { - legend.chart.data.datasets.forEach((dataset, i) => { +const trigger_legend_leave = function (chart) { + chart.data.datasets.forEach((dataset, i) => { dataset.backgroundColor = dataset.backgroundColor.length === 9 ? dataset.backgroundColor.slice(0, -2) : dataset.backgroundColor; dataset.borderColor = dataset.borderColor.length === 9 ? dataset.borderColor.slice(0, -2) : dataset.borderColor; dataset.borderWidth = 3; }); - legend.chart.update(); + chart.update(); + hovered = false; } -const legend_plugin = { - position: "bottom", - labels: { - padding: 10, - useBorderRadius: true, - borderRadius: 2, - boxWidth: 20, - }, - onHover: trigger_legend_hover, - onLeave: trigger_legend_leave, - onClick: function (e, legendItem, legend) { - if (legend.chart.isDatasetVisible(legendItem.datasetIndex)) { - trigger_legend_leave(e, legendItem, legend); - legend.chart.hide(legendItem.datasetIndex); - legendItem.hidden = true; +const trigger_legend_click = function (item) { + combined_charts.forEach(c_chart => { + if (c_chart.isDatasetVisible(item.datasetIndex)) { + trigger_legend_leave(c_chart) + c_chart.hide(item.datasetIndex); + item.hidden = true; } else { - legend.chart.show(legendItem.datasetIndex); - legendItem.hidden = false; - trigger_legend_hover(e, legendItem, legend); + c_chart.show(item.datasetIndex); + item.hidden = false; + trigger_legend_hover(item, c_chart) } + }) +} +// break hover state when mouseout fails to trigger +window.onmousemove = (e) => { + if (hovered && (e.target.closest("ul") === null || !e.target.closest("ul").classList.contains("legend-list"))) { + console.log("breaking hover") + combined_charts.forEach(chart => { + trigger_legend_leave(chart); + }) + } +} +const get_create_LegendList = (chart, id) => { + let created = false; + const legendContainer = document.getElementById(id); + let listContainer = legendContainer.querySelector("ul"); + if (!listContainer) { + created = true; + listContainer = document.createElement("ul"); + listContainer.classList.add('legend-list'); + legendContainer.appendChild(listContainer); } + return {list: listContainer, created: created}; } +const htmlLegendPlugin = { + id: 'htmlLegend', + afterUpdate(chart, args, options) { + //console.log("update") + const legendList = get_create_LegendList(chart, options.containerID); + const ul = legendList.list; + + // Reuse the built-in legendItems generator + const items = chart.options.plugins.legend.labels.generateLabels(chart); + + items.forEach(item => { + if (legendList.created) { + const li = document.createElement('li'); + li.classList.add("legend-element"); + + li.onmouseover = () => {trigger_legend_hover(item, chart)}; + li.onmouseout = () => {trigger_legend_leave(chart)}; + li.onclick = () => {trigger_legend_click(item)}; + + // Color box + const boxSpan = document.createElement('span'); + boxSpan.style.background = item.fillStyle; + boxSpan.style.borderColor = item.strokeStyle; + boxSpan.style.borderWidth = item.lineWidth + 'px'; + + // Text + const textContainer = document.createElement('p'); + textContainer.style.color = item.fontColor; + textContainer.style.textDecoration = item.hidden ? 'line-through' : ''; + + const text = document.createTextNode(item.text); + textContainer.appendChild(text); + + li.appendChild(boxSpan); + li.appendChild(textContainer); + ul.appendChild(li); + + } else { + const list_element = ul.querySelector(`li.legend-element:nth-of-type(${item.datasetIndex+1})`); + const boxSpan = list_element.querySelector("span"); + const textContainer = list_element.querySelector("p"); + boxSpan.style.background = item.fillStyle; + boxSpan.style.borderColor = item.strokeStyle; + boxSpan.style.borderWidth = item.lineWidth + 'px'; + textContainer.style.color = item.fontColor; + textContainer.style.textDecoration = item.hidden ? 'line-through' : ''; + } + }); + + }, +}; window.onload = create_charts; @@ -205,7 +272,12 @@ async function create_charts() { plugins: { tooltip: tooltip_plugin, zoom: {...zoom_plugin}, - legend: legend_plugin, + legend: { + display: false, + }, + htmlLegend: { + containerID: 'combined-chart-legend', + } }, scales: { x: x_scale, @@ -221,7 +293,8 @@ async function create_charts() { }, responsive: true, maintainAspectRatio: false, - } + }, + plugins: [htmlLegendPlugin], })); combined_charts.push(new Chart(`progress-chart-combined-progress`, { type: "line", @@ -232,7 +305,12 @@ async function create_charts() { plugins: { tooltip: tooltip_plugin, zoom: {...zoom_plugin}, - legend: legend_plugin, + legend: { + display: false, + }, + htmlLegend: { + containerID: 'combined-chart-legend', + } }, scales: { x: x_scale, @@ -242,7 +320,8 @@ async function create_charts() { }, responsive: true, maintainAspectRatio: false, - } + }, + plugins: [htmlLegendPlugin], })); minval -= 7200000; maxval += 7200000; @@ -384,9 +463,11 @@ function toggle_combined_chart() { buttons.forEach(element => element.classList.remove("dropdown-open")); chart.classList.remove("closed"); this.classList.add("dropdown-open"); + document.querySelector("#combined-chart-legend").classList.remove("hidden"); } else { chart.classList.add("closed"); this.classList.remove("dropdown-open"); + document.querySelector("#combined-chart-legend").classList.add("hidden"); } } @@ -471,7 +552,7 @@ async function update_leaderboard_elements() { // replace updated Leaderboard document.querySelector(".leaderboard-list").outerHTML = leaderboard_list; // reapply EventListeners - document.querySelectorAll("button.leaderboard-element").forEach(element => element.addEventListener("click", toggle_leaderboard_chart)); + document.querySelectorAll("button.leaderboard-element").forEach(element => element.addEventListener("mousedown", toggle_leaderboard_chart)); // recreate charts create_charts(); }) diff --git a/public/elotracker/index.php b/public/elotracker/index.php index bd5c26f..21da170 100644 --- a/public/elotracker/index.php +++ b/public/elotracker/index.php @@ -19,6 +19,7 @@
+ diff --git a/public/elotracker/styles.css b/public/elotracker/styles.css index d51607e..eebff6c 100644 --- a/public/elotracker/styles.css +++ b/public/elotracker/styles.css @@ -272,4 +272,46 @@ button.button-updating svg { } .leaderboard>.graph-wrapper.closed { height: 0; +} + +.combined-chart-legend { + display: grid; + grid-template-rows: 1fr; + transition: height 400ms, grid-template-rows 400ms; + overflow: hidden; +} +.combined-chart-legend.hidden { + grid-template-rows: 0fr; +} +ul.legend-list { + display: flex; + flex-direction: column; + margin: 0; + padding: 0; + min-height: 0; +} +li.legend-element { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + padding: 2px 8px; + border-radius: 4px; + transition: background-color 200ms; +} +li.legend-element:hover { + background-color: #1f2931; +} +li.legend-element span { + display: inline-block; + flex-shrink: 0; + height: 20px; + width: 20px; + margin-right: 10px; + border-radius: 4px; + transition: background-color 200ms, border-color 200ms; +} +li.legend-element p { + margin: 0; + padding: 0; } \ No newline at end of file