diff --git a/resources/assets/js/chart-theme.js b/resources/assets/js/chart-theme.js index 62b55ef0..ffbbe251 100644 --- a/resources/assets/js/chart-theme.js +++ b/resources/assets/js/chart-theme.js @@ -213,21 +213,28 @@ export function getAxisThemeConfig(mode) { const config = { light: { x: { - color: "rgba(238,243,245,1)", // theme-secondary-200 + color: "rgba(219, 222, 229, 1)", // theme-secondary-300 }, y: { - color: "rgba(238,243,245,1)", // theme-secondary-200 + color: "rgba(219, 222, 229, 1)", // theme-secondary-300 }, }, dark: { x: { - color: "rgba(60,66,73,1)", // theme-secondary-800 + color: "rgba(61, 68, 77, 1)", // theme-dark-700 }, y: { - color: "rgba(60,66,73,1)", // theme-secondary-800 + color: "rgba(61, 68, 77, 1)", // theme-dark-700 }, }, }; return config[mode]; } + +export function getCrosshairColor(mode) { + return { + light: "rgba(99, 114, 130, 1)", // theme-secondary-700, + dark: "rgba(164, 177, 188, 1)", // theme-dark-200, + }[mode]; +} diff --git a/resources/assets/js/chart.js b/resources/assets/js/chart.js index 8bb7665e..5895883a 100644 --- a/resources/assets/js/chart.js +++ b/resources/assets/js/chart.js @@ -3,9 +3,10 @@ import { makeGradient, getFontConfig, getAxisThemeConfig, + getCrosshairColor, } from "./chart-theme"; -import { Chart, registerables } from "chart.js"; +import { Chart, registerables, LineController } from "chart.js"; Chart.register(...registerables); @@ -18,6 +19,9 @@ Chart.register(...registerables); * @param {Array} theme * @param {Number} time * @param {String} currency + * @param {Number} yPadding + * @param {Number} xPadding + * @param {Boolean} showCrosshair * @return {Object} */ const CustomChart = ( @@ -28,8 +32,64 @@ const CustomChart = ( tooltips, theme, time, - currency + currency, + yPadding = 15, + xPadding = 10, + showCrosshair = false ) => { + const themeMode = () => { + if (theme.mode === "auto") { + return ["light", "dark"].includes(localStorage.theme) + ? localStorage.theme + : "light"; + } + + return theme.mode; + }; + + class LineWithCrosshair extends LineController { + draw() { + super.draw(arguments); + + // Based on https://stackoverflow.com/a/70245628/3637093 + if ( + this.chart.tooltip._active && + this.chart.tooltip._active.length + ) { + const activePoint = this.chart.tooltip._active[0].element; + const ctx = this.chart.ctx; + const x = activePoint.x; + const y = activePoint.y; + const topY = this.chart.legend.bottom; + const bottomY = this.chart.chartArea.bottom; + const left = this.chart.chartArea.left; + const right = this.chart.chartArea.right; + + ctx.save(); + ctx.lineWidth = 1; + ctx.setLineDash([3, 3]); + ctx.strokeStyle = getCrosshairColor(themeMode()); + + ctx.beginPath(); + ctx.moveTo(x, topY); + ctx.lineTo(x, bottomY); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(left, y); + ctx.lineTo(right, y); + ctx.stroke(); + + ctx.restore(); + } + } + } + + LineWithCrosshair.id = "lineWithCrosshair"; + LineWithCrosshair.defaults = LineController.defaults; + + Chart.register(LineWithCrosshair); + return { time: time, chart: null, @@ -71,16 +131,6 @@ const CustomChart = ( this.chart.update(); }, - themeMode() { - if (theme.mode === "auto") { - return ["light", "dark"].includes(localStorage.theme) - ? localStorage.theme - : "light"; - } - - return theme.mode; - }, - loadData() { const datasets = []; @@ -95,7 +145,7 @@ const CustomChart = ( values.forEach((value, key) => { let themeName = value.type === "bar" ? "grey" : theme.name; - let graphic = getInfoFromThemeName(themeName, this.themeMode()); + let graphic = getInfoFromThemeName(themeName, themeMode()); let backgroundColor = graphic.backgroundColor; if (backgroundColor.hasOwnProperty("gradient")) { backgroundColor = makeGradient( @@ -104,12 +154,17 @@ const CustomChart = ( ); } + let chartType = value.type || "line"; + if (showCrosshair && chartType === "line") { + chartType = "lineWithCrosshair"; + } + datasets.push({ fill: true, stack: "combined", label: value.name || "", data: value.data || value, - type: value.type || "line", + type: chartType, backgroundColor: value.type === "bar" ? graphic.borderColor @@ -149,17 +204,19 @@ const CustomChart = ( display: grid && key === 0, type: "linear", ticks: { - ...getFontConfig("axis", this.themeMode()), - padding: 15, + ...getFontConfig("axis", themeMode()), + padding: yPadding, display: grid && key === 0, suggestedMax: range.max, callback: (value, index, data) => this.getCurrencyValue(value), }, grid: { + drawTicks: false, display: grid && key === 0, drawBorder: false, - color: getAxisThemeConfig(this.themeMode()).y.color, + borderDash: [3, 3], + color: getAxisThemeConfig(themeMode()).y.color, }, }); }); @@ -175,9 +232,14 @@ const CustomChart = ( } this.$watch("time", () => this.updateChart()); - window.addEventListener("resize", () => - window.livewire.emit("updateChart") - ); + + window.addEventListener("resize", () => { + try { + this.chart.resize(); + } catch (e) { + // Hide resize errors - they don't seem to cause any issues + } + }); const data = { labels: labels, @@ -206,13 +268,10 @@ const CustomChart = ( label: (context) => this.getCurrencyValue(context.raw), labelTextColor: (context) => - getFontConfig("tooltip", this.themeMode()) - .fontColor, + getFontConfig("tooltip", themeMode()).fontColor, }, - backgroundColor: getFontConfig( - "tooltip", - this.themeMode() - ).backgroundColor, + backgroundColor: getFontConfig("tooltip", themeMode()) + .backgroundColor, }, }, hover: { @@ -232,13 +291,13 @@ const CustomChart = ( ticks: { display: grid, includeBounds: true, - padding: 10, - ...getFontConfig("axis", this.themeMode()), + padding: xPadding, + ...getFontConfig("axis", themeMode()), }, grid: { - display: grid, + display: false, drawBorder: false, - color: getAxisThemeConfig(this.themeMode()).x.color, + color: getAxisThemeConfig(themeMode()).x.color, }, }, }, diff --git a/resources/views/chart.blade.php b/resources/views/chart.blade.php index e0c68e09..66f03b09 100644 --- a/resources/views/chart.blade.php +++ b/resources/views/chart.blade.php @@ -9,6 +9,9 @@ 'grid' => false, 'tooltips' => false, 'theme' => collect(['name' => 'grey', 'mode' => 'light']), + 'yPadding' => 15, + 'xPadding' => 10, + 'showCrosshair' => false, ])
toArray()) }}, '{{ time() }}', '{{ $currency }}', + {{ $yPadding }}, + {{ $xPadding }}, + {{ $showCrosshair ? 'true' : 'false' }} )" wire:key="{{ $id.time() }}" {{ $attributes->only('class') }} > -
+