diff --git a/components/common/Dot.js b/components/common/Dot.js
index 3f424820..d5dcf914 100644
--- a/components/common/Dot.js
+++ b/components/common/Dot.js
@@ -4,12 +4,14 @@ import styles from './Dot.module.css';
export default function Dot({ color, size, className }) {
return (
-
+
);
}
diff --git a/components/common/Dot.module.css b/components/common/Dot.module.css
index 9081dc5c..258d6e87 100644
--- a/components/common/Dot.module.css
+++ b/components/common/Dot.module.css
@@ -1,9 +1,14 @@
+.wrapper {
+ background: var(--gray50);
+ margin-right: 10px;
+ border-radius: 100%;
+}
+
.dot {
background: var(--green400);
width: 10px;
height: 10px;
border-radius: 100%;
- margin-right: 10px;
}
.dot.small {
diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js
index 2f99fa28..1bd9ce65 100644
--- a/components/metrics/BarChart.js
+++ b/components/metrics/BarChart.js
@@ -1,14 +1,14 @@
import React, { useState, useRef, useEffect } from 'react';
-import ReactTooltip from 'react-tooltip';
import classNames from 'classnames';
import ChartJS from 'chart.js';
-import Dot from 'components/common/Dot';
+import Legend from 'components/metrics/Legend';
import { formatLongNumber } from 'lib/format';
import { dateFormat } from 'lib/lang';
import useLocale from 'hooks/useLocale';
import useTheme from 'hooks/useTheme';
import { DEFAUL_CHART_HEIGHT, DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
import styles from './BarChart.module.css';
+import ChartTooltip from './ChartTooltip';
export default function BarChart({
chartId,
@@ -25,7 +25,6 @@ export default function BarChart({
}) {
const canvas = useRef();
const chart = useRef();
- const [legend, setLegend] = useState();
const [tooltip, setTooltip] = useState(null);
const [locale] = useLocale();
const [theme] = useTheme();
@@ -143,7 +142,6 @@ export default function BarChart({
callback: renderYLabel,
beginAtZero: true,
fontColor: colors.text,
- precision: 0,
},
gridLines: {
color: colors.line,
@@ -164,8 +162,6 @@ export default function BarChart({
},
options,
});
-
- chart.current.generateLegend();
}
function updateChart() {
@@ -184,8 +180,6 @@ export default function BarChart({
onUpdate(chart.current);
chart.current.update();
-
- chart.current.generateLegend();
}
useEffect(() => {
@@ -196,7 +190,6 @@ export default function BarChart({
setTooltip(null);
updateChart();
}
- setLegend({ ...chart.current.legend });
}
}, [datasets, unit, animationDuration, locale, theme]);
@@ -210,35 +203,8 @@ export default function BarChart({
>
-
-
- {tooltip ? : null}
-
+
+
>
);
}
-
-const Legend = ({ items = [], locale }) => (
-
- {items.map(({ text, fillStyle }) => (
-
-
- {text}
-
- ))}
-
-);
-
-const Tooltip = ({ title, value, label, labelColor }) => (
-
-);
diff --git a/components/metrics/BarChart.module.css b/components/metrics/BarChart.module.css
index 2c394e83..aea86a4c 100644
--- a/components/metrics/BarChart.module.css
+++ b/components/metrics/BarChart.module.css
@@ -1,60 +1,3 @@
.chart {
position: relative;
}
-
-.tooltip {
- color: var(--msgColor);
- pointer-events: none;
- z-index: 1;
-}
-
-.content {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- text-align: center;
-}
-
-.title {
- font-size: var(--font-size-xsmall);
- font-weight: 600;
-}
-
-.metric {
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: var(--font-size-small);
- font-weight: 600;
-}
-
-.dot {
- position: relative;
- overflow: hidden;
- border-radius: 100%;
- margin-right: 8px;
- background: var(--gray50);
-}
-
-.color {
- width: 10px;
- height: 10px;
-}
-
-.legend {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- margin-top: 10px;
-}
-
-.label {
- display: flex;
- align-items: center;
- font-size: var(--font-size-xsmall);
-}
-
-.label + .label {
- margin-left: 20px;
-}
diff --git a/components/metrics/ChartTooltip.js b/components/metrics/ChartTooltip.js
new file mode 100644
index 00000000..fb290b66
--- /dev/null
+++ b/components/metrics/ChartTooltip.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import Dot from 'components/common/Dot';
+import styles from './ChartTooltip.module.css';
+import ReactTooltip from 'react-tooltip';
+
+export default function ChartTooltip({ chartId, tooltip }) {
+ if (!tooltip) {
+ return null;
+ }
+
+ const { title, value, label, labelColor } = tooltip;
+
+ return (
+
+
+
+
{title}
+
+
+ {value} {label}
+
+
+
+
+ );
+}
diff --git a/components/metrics/ChartTooltip.module.css b/components/metrics/ChartTooltip.module.css
new file mode 100644
index 00000000..cd26d3af
--- /dev/null
+++ b/components/metrics/ChartTooltip.module.css
@@ -0,0 +1,43 @@
+.chart {
+ position: relative;
+}
+
+.tooltip {
+ color: var(--msgColor);
+ pointer-events: none;
+ z-index: 1;
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+}
+
+.title {
+ font-size: var(--font-size-xsmall);
+ font-weight: 600;
+}
+
+.metric {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: var(--font-size-small);
+ font-weight: 600;
+}
+
+.dot {
+ position: relative;
+ overflow: hidden;
+ border-radius: 100%;
+ margin-right: 8px;
+ background: var(--gray50);
+}
+
+.color {
+ width: 10px;
+ height: 10px;
+}
diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js
index 3d55a40d..dfaa424a 100644
--- a/components/metrics/EventsChart.js
+++ b/components/metrics/EventsChart.js
@@ -16,7 +16,7 @@ export default function EventsChart({ websiteId, className, token }) {
const { query } = usePageQuery();
const shareToken = useShareToken();
- const { data } = useFetch(
+ const { data, loading } = useFetch(
`/api/website/${websiteId}/events`,
{
params: {
@@ -31,8 +31,10 @@ export default function EventsChart({ websiteId, className, token }) {
},
[modified],
);
+
const datasets = useMemo(() => {
if (!data) return [];
+ if (loading) return data;
const map = data.reduce((obj, { x, t, y }) => {
if (!obj[x]) {
@@ -59,7 +61,7 @@ export default function EventsChart({ websiteId, className, token }) {
borderWidth: 1,
};
});
- }, [data]);
+ }, [data, loading]);
function handleUpdate(chart) {
chart.data.datasets = datasets;
@@ -79,6 +81,7 @@ export default function EventsChart({ websiteId, className, token }) {
unit={unit}
records={getDateLength(startDate, endDate, unit)}
onUpdate={handleUpdate}
+ loading={loading}
stacked
/>
);
diff --git a/components/metrics/Legend.js b/components/metrics/Legend.js
new file mode 100644
index 00000000..a40ff411
--- /dev/null
+++ b/components/metrics/Legend.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import classNames from 'classnames';
+import Dot from 'components/common/Dot';
+import useLocale from 'hooks/useLocale';
+import styles from './Legend.module.css';
+import useForceUpdate from '../../hooks/useForceUpdate';
+
+export default function Legend({ chart }) {
+ const [locale] = useLocale();
+ const forceUpdate = useForceUpdate();
+
+ function handleClick(index) {
+ const meta = chart.getDatasetMeta(index);
+
+ meta.hidden = meta.hidden === null ? !chart.data.datasets[index].hidden : null;
+
+ chart.update();
+
+ forceUpdate();
+ }
+
+ if (!chart?.legend?.legendItems.find(({ text }) => text)) {
+ return null;
+ }
+
+ return (
+
+ {chart.legend.legendItems.map(({ text, fillStyle, datasetIndex, hidden }) => (
+
handleClick(datasetIndex)}
+ >
+
+ {text}
+
+ ))}
+
+ );
+}
diff --git a/components/metrics/Legend.module.css b/components/metrics/Legend.module.css
new file mode 100644
index 00000000..faa197e3
--- /dev/null
+++ b/components/metrics/Legend.module.css
@@ -0,0 +1,21 @@
+.legend {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-top: 10px;
+}
+
+.label {
+ display: flex;
+ align-items: center;
+ font-size: var(--font-size-xsmall);
+ cursor: pointer;
+}
+
+.label + .label {
+ margin-left: 20px;
+}
+
+.hidden {
+ color: var(--gray400);
+}
diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js
index 33b6eaad..886ee5f0 100644
--- a/components/metrics/MetricsBar.js
+++ b/components/metrics/MetricsBar.js
@@ -31,7 +31,7 @@ export default function MetricsBar({ websiteId, className }) {
},
headers: { [TOKEN_HEADER]: shareToken?.token },
},
- [modified],
+ [url, modified],
);
const formatFunc = format ? formatLongNumber : formatNumber;
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js
index 07ba5161..fb8bdf8f 100644
--- a/components/metrics/WebsiteChart.js
+++ b/components/metrics/WebsiteChart.js
@@ -47,7 +47,7 @@ export default function WebsiteChart({
onDataLoad,
headers: { [TOKEN_HEADER]: shareToken?.token },
},
- [modified],
+ [url, modified],
);
const chartData = useMemo(() => {
diff --git a/package.json b/package.json
index 1f26ba1e..7532b1da 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "0.96.0",
+ "version": "1.0.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao ",
"license": "MIT",