From c1d3e9ec6757d4f67265486968e4ef1fc0702df9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 14 Mar 2023 22:37:50 -0700 Subject: [PATCH 1/3] Upgraded Chart.js to v4. Updated charts. --- components/layout/Header.module.css | 2 +- components/metrics/BarChart.js | 165 ++++++------------ components/metrics/BarChart.module.css | 4 +- components/metrics/Legend.module.css | 2 +- components/metrics/PageviewsChart.js | 49 +++--- hooks/useDateRange.js | 1 - lib/date.js | 8 +- package.json | 5 +- pages/_app.js | 1 + .../analytics/pageview/getPageviewStats.ts | 4 +- yarn.lock | 46 +++-- 11 files changed, 113 insertions(+), 174 deletions(-) diff --git a/components/layout/Header.module.css b/components/layout/Header.module.css index c896e967..e796b6f0 100644 --- a/components/layout/Header.module.css +++ b/components/layout/Header.module.css @@ -12,7 +12,7 @@ gap: 10px; font-size: var(--font-size-lg); font-weight: 700; - color: var(--font-color100); + color: var(--font-color100) !important; } .buttons { diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js index cab5021c..e3e42b7e 100644 --- a/components/metrics/BarChart.js +++ b/components/metrics/BarChart.js @@ -1,7 +1,7 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useMemo } from 'react'; import { StatusLight } from 'react-basics'; import classNames from 'classnames'; -import ChartJS from 'chart.js'; +import Chart from 'chart.js/auto'; import HoverTooltip from 'components/common/HoverTooltip'; import Legend from 'components/metrics/Legend'; import { formatLongNumber } from 'lib/format'; @@ -15,7 +15,6 @@ import styles from './BarChart.module.css'; export default function BarChart({ datasets, unit, - records, animationDuration = DEFAULT_ANIMATION_DURATION, className, stacked = false, @@ -30,74 +29,36 @@ export default function BarChart({ const [theme] = useTheme(); const forceUpdate = useForceUpdate(); - const colors = { - text: THEME_COLORS[theme].gray700, - line: THEME_COLORS[theme].gray200, - zeroLine: THEME_COLORS[theme].gray500, - }; - - function renderXLabel(label, index, values) { - if (loading) return ''; - const d = new Date(values[index].value); - const sw = canvas.current.width / window.devicePixelRatio; - - switch (unit) { - case 'minute': - return index % 2 === 0 ? dateFormat(d, 'H:mm', locale) : ''; - case 'hour': - return dateFormat(d, 'p', locale); - case 'day': - if (records > 25) { - if (sw <= 275) { - return index % 10 === 0 ? dateFormat(d, 'M/d', locale) : ''; - } - if (sw <= 550) { - return index % 5 === 0 ? dateFormat(d, 'M/d', locale) : ''; - } - if (sw <= 700) { - return index % 2 === 0 ? dateFormat(d, 'M/d', locale) : ''; - } - return dateFormat(d, 'MMM d', locale); - } - if (sw <= 375) { - return index % 2 === 0 ? dateFormat(d, 'MMM d', locale) : ''; - } - if (sw <= 425) { - return dateFormat(d, 'MMM d', locale); - } - return dateFormat(d, 'EEE M/d', locale); - case 'month': - if (sw <= 330) { - return index % 2 === 0 ? dateFormat(d, 'MMM', locale) : ''; - } - return dateFormat(d, 'MMM', locale); - default: - return label; - } - } + const colors = useMemo( + () => ({ + text: THEME_COLORS[theme].gray700, + line: THEME_COLORS[theme].gray200, + zeroLine: THEME_COLORS[theme].gray500, + }), + [theme], + ); function renderYLabel(label) { return +label > 1000 ? formatLongNumber(label) : label; } function renderTooltip(model) { - const { opacity, title, body, labelColors } = model; + const { opacity, labelColors, dataPoints } = model.tooltip; - if (!opacity || !title) { + if (!dataPoints?.length || !opacity) { setTooltip(null); return; } - const [label, value] = body[0].lines[0].split(':'); const format = unit === 'hour' ? 'EEE p — PPP' : 'PPPP'; setTooltip( <> -
{dateFormat(new Date(+title[0]), format, locale)}
+
{dateFormat(new Date(dataPoints[0].raw.x), format, locale)}
- + - {formatLongNumber(value)} {label} + {formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
@@ -107,68 +68,53 @@ export default function BarChart({ function createChart() { const options = { + responsive: true, + maintainAspectRatio: false, animation: { duration: animationDuration, + resize: { + duration: 0, + }, + active: { + duration: 0, + }, }, - tooltips: { - enabled: false, - custom: renderTooltip, - }, - hover: { - animationDuration: 0, - }, - responsive: true, - responsiveAnimationDuration: 0, - maintainAspectRatio: false, - legend: { - display: false, - }, - onResize: ({ width, height }) => { - //console.log({ width, height }); + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + external: renderTooltip, + }, }, scales: { - xAxes: [ - { - type: 'time', - distribution: 'series', - time: { - unit, - tooltipFormat: 'x', - }, - ticks: { - callback: renderXLabel, - minRotation: 0, - maxRotation: 0, - fontColor: colors.text, - autoSkipPadding: 1, - }, - gridLines: { - display: false, - }, - offset: true, - stacked: true, + x: { + type: 'time', + stacked: true, + grid: { + display: false, }, - ], - yAxes: [ - { - ticks: { - callback: renderYLabel, - beginAtZero: true, - fontColor: colors.text, - }, - gridLines: { - color: colors.line, - zeroLineColor: colors.zeroLine, - }, - stacked, + ticks: { + autoSkip: false, + maxRotation: 0, }, - ], + }, + y: { + type: 'linear', + min: 0, + beginAtZero: true, + stacked, + ticks: { + callback: renderYLabel, + }, + }, }, }; onCreate(options); - chart.current = new ChartJS(canvas.current, { + chart.current = new Chart(canvas.current, { type: 'bar', data: { datasets, @@ -180,16 +126,7 @@ export default function BarChart({ function updateChart() { const { options } = chart.current; - options.legend.labels.fontColor = colors.text; - options.scales.xAxes[0].time.unit = unit; - options.scales.xAxes[0].ticks.callback = renderXLabel; - options.scales.xAxes[0].ticks.fontColor = colors.text; - options.scales.yAxes[0].ticks.fontColor = colors.text; - options.scales.yAxes[0].ticks.precision = 0; - options.scales.yAxes[0].gridLines.color = colors.line; - options.scales.yAxes[0].gridLines.zeroLineColor = colors.zeroLine; options.animation.duration = animationDuration; - options.tooltips.custom = renderTooltip; onUpdate(chart.current); @@ -207,7 +144,7 @@ export default function BarChart({ updateChart(); } } - }, [datasets, unit, animationDuration, locale]); + }, [datasets, unit, animationDuration, locale, loading]); return ( <> diff --git a/components/metrics/BarChart.module.css b/components/metrics/BarChart.module.css index 593bff91..1768d15b 100644 --- a/components/metrics/BarChart.module.css +++ b/components/metrics/BarChart.module.css @@ -1,10 +1,12 @@ .chart { position: relative; height: 400px; + + overflow: hidden; } @media only screen and (max-width: 992px) { .chart { - height: 200px; + /*height: 200px;*/ } } diff --git a/components/metrics/Legend.module.css b/components/metrics/Legend.module.css index e78bf609..20deffcd 100644 --- a/components/metrics/Legend.module.css +++ b/components/metrics/Legend.module.css @@ -8,7 +8,7 @@ .label { display: flex; align-items: center; - font-size: var(--font-size-xs); + font-size: var(--font-size-sm); cursor: pointer; } diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js index 8161c910..38550969 100644 --- a/components/metrics/PageviewsChart.js +++ b/components/metrics/PageviewsChart.js @@ -25,12 +25,16 @@ export default function PageviewsChart({ const primaryColor = colord(THEME_COLORS[theme].primary); return { views: { - background: primaryColor.alpha(0.4).toRgbString(), - border: primaryColor.alpha(0.5).toRgbString(), + hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(), + backgroundColor: primaryColor.alpha(0.4).toRgbString(), + borderColor: primaryColor.alpha(0.7).toRgbString(), + hoverBorderColor: primaryColor.toRgbString(), }, visitors: { - background: primaryColor.alpha(0.6).toRgbString(), - border: primaryColor.alpha(0.7).toRgbString(), + hoverBackgroundColor: primaryColor.alpha(0.9).toRgbString(), + backgroundColor: primaryColor.alpha(0.6).toRgbString(), + borderColor: primaryColor.alpha(0.9).toRgbString(), + hoverBorderColor: primaryColor.toRgbString(), }, }; }, [theme]); @@ -50,29 +54,30 @@ export default function PageviewsChart({ return null; } + const datasets = [ + { + label: formatMessage(labels.uniqueVisitors), + data: data.sessions, + lineTension: 0, + borderWidth: 1, + ...colors.visitors, + }, + { + label: formatMessage(labels.pageViews), + data: data.pageviews, + lineTension: 0, + borderWidth: 1, + ...colors.views, + }, + ]; + return (
{ - return normalize(getDateFromString(e.t)).getTime() === t.getTime(); + const d = data.find(({ x }) => { + return normalize(getDateFromString(x)).getTime() === t.getTime(); }); - return x?.y || 0; + return d?.y || 0; } for (let i = 0; i < n; i++) { const t = normalize(add(startDate, i)); const y = findData(t); - arr.push({ ...data[i], t, y }); + arr.push({ x: t, y }); } return arr; diff --git a/package.json b/package.json index ed7d6e73..5153f6cc 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,8 @@ "@umami/prisma-client": "^0.2.0", "@umami/redis-client": "^0.2.0", "chalk": "^4.1.1", - "chart.js": "^2.9.4", + "chart.js": "^4.2.1", + "chartjs-adapter-date-fns": "^3.0.0", "classnames": "^2.3.1", "clickhouse": "^2.5.0", "colord": "^2.9.2", @@ -93,7 +94,7 @@ "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "react": "^18.2.0", - "react-basics": "^0.72.0", + "react-basics": "^0.73.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-intl": "^5.24.7", diff --git a/pages/_app.js b/pages/_app.js index ec14d084..ad9e71ac 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -10,6 +10,7 @@ import 'styles/locale.css'; import 'styles/index.css'; import '@fontsource/inter/400.css'; import '@fontsource/inter/700.css'; +import 'chartjs-adapter-date-fns'; import Script from 'next/script'; const client = new QueryClient({ diff --git a/queries/analytics/pageview/getPageviewStats.ts b/queries/analytics/pageview/getPageviewStats.ts index b2d86b33..273151aa 100644 --- a/queries/analytics/pageview/getPageviewStats.ts +++ b/queries/analytics/pageview/getPageviewStats.ts @@ -50,7 +50,7 @@ async function relationalQuery( const { filterQuery, joinSession } = parseFilters(filters, params); return rawQuery( - `select ${getDateQuery('website_event.created_at', unit, timezone)} t, + `select ${getDateQuery('website_event.created_at', unit, timezone)} x, count(${count !== '*' ? `${count}${sessionKey}` : count}) y from website_event ${joinSession} @@ -83,7 +83,7 @@ async function clickhouseQuery( return rawQuery( `select - ${getDateStringQuery('g.t', unit)} as t, + ${getDateStringQuery('g.t', unit)} as x, g.y as y from (select diff --git a/yarn.lock b/yarn.lock index 397c8249..ed85f811 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1745,6 +1745,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + "@netlify/esbuild-android-64@0.14.39": version "0.14.39" resolved "https://registry.yarnpkg.com/@netlify/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#7bd30aba94a92351d2c5e25e178ceb824f3c2f99" @@ -3348,28 +3353,17 @@ chalk@^4.0.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chart.js@^2.9.4: - version "2.9.4" - resolved "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz" - integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== +chart.js@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.2.1.tgz#d2bd5c98e9a0ae35408975b638f40513b067ba1d" + integrity sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw== dependencies: - chartjs-color "^2.1.0" - moment "^2.10.2" + "@kurkle/color" "^0.3.0" -chartjs-color-string@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz" - integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== - dependencies: - color-name "^1.0.0" - -chartjs-color@^2.1.0: - version "2.4.1" - resolved "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz" - integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== - dependencies: - chartjs-color-string "^0.6.0" - color-convert "^1.9.3" +chartjs-adapter-date-fns@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz#c25f63c7f317c1f96f9a7c44bd45eeedb8a478e5" + integrity sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg== chokidar@^3.5.3: version "3.5.3" @@ -3464,7 +3458,7 @@ cluster-key-slot@^1.1.0: resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== -color-convert@^1.9.0, color-convert@^1.9.3: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -6117,7 +6111,7 @@ moment-timezone@^0.5.35: dependencies: moment "^2.29.4" -"moment@>= 2.9.0", moment@^2.10.2, moment@^2.29.4: +"moment@>= 2.9.0", moment@^2.29.4: version "2.29.4" resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== @@ -7082,10 +7076,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.72.0: - version "0.72.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.72.0.tgz#23dbd61d5ac6bb8b8d61f1f3adcebb7edeab8a26" - integrity sha512-dWthEwyh/ilt1BSPYwMdd1oE/OFDp8oZ5udZGrdXs5guRh9Ukar4V4chQPbnuZPYUnAH4jg19H5Fesvz2lSaaw== +react-basics@^0.73.0: + version "0.73.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.73.0.tgz#9555563f3407ac417dc833dfca47588123d55535" + integrity sha512-eEK8yWWrXO7JATBlPKBfFQlD1hNZoNeEtlYNx+QjOCLKu1qjClutP5nXWHmX4gHE97XFwUKzbTU35NkNEy5C0w== dependencies: classnames "^2.3.1" date-fns "^2.29.3" From be8eb61f7fa56453cda073608ba94b32a4793162 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 14 Mar 2023 23:29:53 -0700 Subject: [PATCH 2/3] Fixed issue with responsive charts. --- components/metrics/BarChart.module.css | 1 - components/metrics/Legend.module.css | 2 +- components/metrics/WebsiteChart.module.css | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/metrics/BarChart.module.css b/components/metrics/BarChart.module.css index 1768d15b..c54f2d4a 100644 --- a/components/metrics/BarChart.module.css +++ b/components/metrics/BarChart.module.css @@ -1,7 +1,6 @@ .chart { position: relative; height: 400px; - overflow: hidden; } diff --git a/components/metrics/Legend.module.css b/components/metrics/Legend.module.css index 20deffcd..b079e67f 100644 --- a/components/metrics/Legend.module.css +++ b/components/metrics/Legend.module.css @@ -2,7 +2,7 @@ display: flex; justify-content: center; flex-wrap: wrap; - margin-top: 10px; + padding: 10px 0; } .label { diff --git a/components/metrics/WebsiteChart.module.css b/components/metrics/WebsiteChart.module.css index 05bfcbb8..3a3d4718 100644 --- a/components/metrics/WebsiteChart.module.css +++ b/components/metrics/WebsiteChart.module.css @@ -7,7 +7,7 @@ .chart { position: relative; - padding-bottom: 10px; + overflow: hidden; } .title { From 54051d7204c086367189bf271e0682c87e930a81 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 15 Mar 2023 16:27:05 -0700 Subject: [PATCH 3/3] Updated tracker script name and endpoint. --- rollup.tracker.config.js | 4 ++-- tracker/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rollup.tracker.config.js b/rollup.tracker.config.js index 5bc09610..f4e7223c 100644 --- a/rollup.tracker.config.js +++ b/rollup.tracker.config.js @@ -6,12 +6,12 @@ import { terser } from 'rollup-plugin-terser'; export default { input: 'tracker/index.js', output: { - file: 'public/umami.js', + file: 'public/script.js', format: 'iife', }, plugins: [ replace({ - '/api/collect': process.env.COLLECT_API_ENDPOINT || '/api/collect', + '/api/send': process.env.COLLECT_API_ENDPOINT || '/api/send', delimiters: ['', ''], preventAssignment: true, }), diff --git a/tracker/index.js b/tracker/index.js index baff16ec..8f27ab36 100644 --- a/tracker/index.js +++ b/tracker/index.js @@ -61,7 +61,7 @@ const root = hostUrl ? hostUrl.replace(/\/$/, '') : currentScript.src.split('/').slice(0, -1).join('/'); - const endpoint = `${root}/api/collect`; + const endpoint = `${root}/api/send`; const screen = `${width}x${height}`; const eventClass = /^umami--([a-z]+)--([\w]+[\w-]*)$/; const eventSelect = "[class*='umami--']";