Skip to content

Commit db4247c

Browse files
feat(explorer): cost per proof chart (#1635)
Co-authored-by: Urix <[email protected]>
1 parent 3ea051d commit db4247c

File tree

14 files changed

+534
-30
lines changed

14 files changed

+534
-30
lines changed

explorer/assets/css/app.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import "tailwindcss/base";
22
@import "tailwindcss/components";
33
@import "tailwindcss/utilities";
4+
@import "./tooltip.css";
45

56
@layer base {
67
:root {

explorer/assets/css/tooltip.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.chart-tooltip-container {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
position: relative;
6+
}
7+
8+
.chart-tooltip-dot {
9+
height: 10px;
10+
width: 10px;
11+
top: -5px;
12+
border-radius: 100%;
13+
background-color: hsl(var(--foreground));
14+
opacity: 0.2;
15+
position: absolute;
16+
transition: all 0.2s;
17+
}
18+
19+
.chart-tooltip-dot:hover {
20+
opacity: 0.5;
21+
transform: scale(1.2);
22+
cursor: pointer;
23+
}
24+
25+
.chart-tooltip-items-container {
26+
min-height: 50px;
27+
min-width: 250px;
28+
padding: 20px;
29+
margin-top: 8px;
30+
background-color: hsl(var(--card));
31+
border-radius: 8px;
32+
border-width: 1px;
33+
border-color: hsl(var(--foreground) / 20%);
34+
}
35+
36+
.chart-tooltip-title {
37+
text-align: center;
38+
color: hsl(var(--foreground));
39+
margin-bottom: 10px;
40+
font-weight: 700;
41+
}
42+
43+
.chart-tooltip-items {
44+
display: flex;
45+
flex-direction: column;
46+
gap: 5px;
47+
}
48+
49+
.chart-tooltip-item {
50+
display: flex;
51+
justify-content: space-between;
52+
width: 100%;
53+
}
54+
55+
.chart-tooltip-item-title {
56+
color: hsl(var(--muted-foreground));
57+
}
58+
59+
.chart-tooltip-item-value {
60+
color: hsl(var(--foreground));
61+
}

explorer/assets/js/app.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,30 @@ import darkModeHook from "../vendor/dark_mode";
77
import searchFocusHook from "../vendor/search_focus";
88
import tooltipHook from "../vendor/tooltip";
99
import copyToClipboardHook from "../vendor/clipboard";
10+
import chartHook from "../vendor/charts";
1011

11-
let Hooks = {};
12-
Hooks.DarkThemeToggle = darkModeHook;
13-
Hooks.SearchFocus = searchFocusHook;
14-
Hooks.TooltipHook = tooltipHook;
15-
Hooks.CopyToClipboard = copyToClipboardHook;
12+
let hooks = {};
13+
hooks.DarkThemeToggle = darkModeHook;
14+
hooks.SearchFocus = searchFocusHook;
15+
hooks.TooltipHook = tooltipHook;
16+
hooks.CopyToClipboard = copyToClipboardHook;
17+
hooks.ChartHook = chartHook;
1618

1719
let csrfToken = document
1820
.querySelector("meta[name='csrf-token']")
1921
.getAttribute("content");
2022

2123
let liveSocket = new LiveSocket("/live", Socket, {
2224
params: { _csrf_token: csrfToken },
23-
hooks: Hooks
25+
hooks: hooks,
2426
});
2527

2628
topbar.config({
2729
barColors: { 0: "#18FF7F" },
2830
shadowColor: "rgba(0, 0, 0, .3)"
2931
});
30-
window.addEventListener("phx:page-loading-start", (_info) =>
31-
topbar.show(50)
32-
);
33-
window.addEventListener("phx:page-loading-stop", (_info) =>
34-
topbar.hide()
35-
);
32+
window.addEventListener("phx:page-loading-start", (_info) => topbar.show(50));
33+
window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());
3634

3735
liveSocket.connect();
3836

explorer/assets/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"dependencies": {
3-
"@floating-ui/dom": "^1.6.8"
3+
"@floating-ui/dom": "^1.6.8",
4+
"chart.js": "^4.4.7"
45
},
56
"devDependencies": {
67
"tailwindcss-animate": "^1.0.7"

explorer/assets/pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { alignedTooltip } from "./tooltip";
2+
3+
export const costPerProofCustomOptions = (options, data) => {
4+
// show only min and max values
5+
options.scales.y.ticks.callback = (_value, index, values) => {
6+
const dataY = data.datasets[0].data.map((point) => parseFloat(point.y));
7+
if (index === 0) return `${Math.min(...dataY)} USD`;
8+
if (index === values.length - 1) {
9+
return `${Math.max(...dataY)} USD`;
10+
}
11+
return "";
12+
};
13+
14+
// show age min, mean and max age in x axis
15+
options.scales.x.ticks.callback = (_value, index, values) => {
16+
const age = data.datasets[0].age;
17+
if (index === 0) return age[0];
18+
if (index === Math.floor((age.length - 1) / 2))
19+
return age[Math.floor((age.length - 1) / 2)];
20+
if (index === values.length - 1) return age[age.length - 1];
21+
return "";
22+
};
23+
24+
options.plugins.tooltip.external = (context) =>
25+
alignedTooltip(context, {
26+
title: "Cost per proof",
27+
items: [
28+
{ title: "Cost", id: "cost" },
29+
{ title: "Age", id: "age" },
30+
{ title: "Merkle root", id: "merkle_root" },
31+
{ title: "Block number", id: "block_number" },
32+
{ title: "Amount of proofs", id: "amount_of_proofs" },
33+
],
34+
onTooltipClick: (tooltipModel) => {
35+
const dataset = tooltipModel.dataPoints[0].dataset;
36+
const idx = tooltipModel.dataPoints[0].dataIndex;
37+
const merkleRootHash = dataset.merkle_root[idx];
38+
window.location.href = `/batches/${merkleRootHash}`;
39+
},
40+
onTooltipUpdate: (tooltipModel) => {
41+
const dataset = tooltipModel.dataPoints[0].dataset;
42+
const idx = tooltipModel.dataPoints[0].dataIndex;
43+
44+
const cost = `${dataset.data[idx].y} USD`;
45+
const age = dataset.age[idx];
46+
const merkleRootHash = dataset.merkle_root[idx];
47+
const merkle_root = `${merkleRootHash.slice(
48+
0,
49+
6
50+
)}...${merkleRootHash.slice(merkleRootHash.length - 4)}`;
51+
const block_number = dataset.data[idx].x;
52+
const amount_of_proofs = dataset.amount_of_proofs[idx];
53+
54+
return {
55+
cost,
56+
age,
57+
merkle_root,
58+
block_number,
59+
amount_of_proofs,
60+
};
61+
},
62+
});
63+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Chart from "chart.js/auto";
2+
import { costPerProofCustomOptions } from "./cost_per_proof";
3+
4+
const applyCommonChartOptions = (options, data) => {
5+
// tooltip disabled by default, each chart should implement its own with alignedTooltip
6+
options.plugins.tooltip = {
7+
enabled: false,
8+
};
9+
};
10+
11+
const applyOptionsByChartId = (id, options, data) => {
12+
const idOptionsMap = {
13+
cost_per_proof_chart: () => costPerProofCustomOptions(options, data),
14+
};
15+
16+
idOptionsMap[id] && idOptionsMap[id]();
17+
};
18+
19+
export default {
20+
mounted() {
21+
this.initChart();
22+
window.addEventListener("theme-changed", this.reinitChart.bind(this));
23+
},
24+
25+
updated() {
26+
this.reinitChart();
27+
},
28+
29+
destroyed() {
30+
if (this.chart) {
31+
this.chart.destroy();
32+
}
33+
34+
window.removeEventListener("theme-changed", this.reinitChart.bind(this));
35+
},
36+
37+
initChart() {
38+
const ctx = this.el;
39+
const type = this.el.dataset.chartType;
40+
const data = JSON.parse(this.el.dataset.chartData);
41+
const options = JSON.parse(this.el.dataset.chartOptions);
42+
const chartId = this.el.id;
43+
44+
applyCommonChartOptions(options, data);
45+
applyOptionsByChartId(chartId, options, data);
46+
47+
this.chart = new Chart(ctx, {
48+
type,
49+
data,
50+
options,
51+
});
52+
},
53+
54+
reinitChart() {
55+
if (this.chart) {
56+
this.chart.destroy();
57+
}
58+
this.initChart();
59+
},
60+
};

0 commit comments

Comments
 (0)