main
ts 198 lines 6.59 KB
Raw
1 import type { TooltipComponentOption } from "echarts/components"
2 import type { CallbackDataParams, TopLevelFormatterParams } from "echarts/types/dist/shared"
3
4 export const CHART_TOOLTIP_GLASS_BLUR_PX = 3
5
6 export type ChartTooltipBorder = "primary" | "black"
7
8 export interface ChartTooltipTheme {
9 fgDefaultColor: string
10 fontFamily: string
11 primaryColor?: string
12 }
13
14 export function chartTooltipThemeFromStyle(style: Record<string, string>): ChartTooltipTheme {
15 return {
16 fgDefaultColor: style["fg-default-color"],
17 fontFamily: style["font-family"],
18 primaryColor: style["primary-color"]
19 }
20 }
21
22 export function getChartTooltipGlassExtraCss(): string {
23 const alpha = 0.8
24 const rules = [
25 `backdrop-filter: blur(${CHART_TOOLTIP_GLASS_BLUR_PX}px)`,
26 `-webkit-backdrop-filter: blur(${CHART_TOOLTIP_GLASS_BLUR_PX}px)`,
27 `background-color: rgba(var(--bg-default-color-rgb) / ${alpha}) !important`,
28 "border-radius: var(--border-radius)",
29 "box-shadow: 0 5px 10px -5px rgba(0,0,0,0.2), 0 5px 20px 0 rgba(0,0,0,0.2)",
30 "padding: 0",
31 "overflow: hidden"
32 ]
33 return rules.join("; ")
34 }
35
36 export function buildChartTooltipGlassBase(
37 theme: ChartTooltipTheme,
38 options?: { trigger?: TooltipComponentOption["trigger"] }
39 ): Pick<
40 TooltipComponentOption,
41 "trigger" | "backgroundColor" | "borderColor" | "borderWidth" | "textStyle" | "extraCssText"
42 > {
43 return {
44 trigger: options?.trigger ?? "item",
45 backgroundColor: "transparent",
46 borderColor: "var(--border-color)",
47 borderWidth: 1,
48 textStyle: {
49 color: theme.fgDefaultColor,
50 fontSize: 12,
51 fontFamily: theme.fontFamily
52 },
53 extraCssText: getChartTooltipGlassExtraCss()
54 }
55 }
56
57 export function resolveChartItemColor(color: CallbackDataParams["color"]): string {
58 return typeof color === "string" ? color : "#000000"
59 }
60
61 export function resolveChartTooltipMarker(options: {
62 marker: CallbackDataParams["marker"]
63 color: CallbackDataParams["color"]
64 }): string {
65 if (typeof options.marker === "string") return options.marker
66 const fill = resolveChartItemColor(options.color)
67 return `<span style="display:inline-block;vertical-align:middle;width:10px;height:10px;border-radius:50%;background:${fill};"></span>`
68 }
69
70 /** Tooltip a due sezioni con header (pallino + titolo) e corpo. */
71 export function formatChartTooltipWithMarker(options: {
72 marker: CallbackDataParams["marker"]
73 color: CallbackDataParams["color"]
74 title: string
75 lines: string[]
76 }): string {
77 const dot = resolveChartTooltipMarker(options)
78 return `
79 <div style="display:flex;align-items:center;gap:8px;background-color:var(--bg-default-color);padding: 4px 8px;">
80 ${dot} ${options.title}
81 </div>
82 <div style="padding: 4px 8px;">
83 ${options.lines.join("<br/>")}
84 </div>`
85 }
86
87 export interface ChartTooltipPieFormatterOptions {
88 resolveColor?: (param: CallbackDataParams) => CallbackDataParams["color"]
89 }
90
91 export function formatChartTooltipPieItem(
92 params: CallbackDataParams | CallbackDataParams[] | undefined,
93 options?: ChartTooltipPieFormatterOptions
94 ): string {
95 if (!params || Array.isArray(params)) return ""
96 const val = typeof params.value === "number" ? params.value : 0
97 const pct = typeof params.percent === "number" ? params.percent : 0
98 return formatChartTooltipWithMarker({
99 marker: params.marker,
100 color: options?.resolveColor?.(params) ?? params.color,
101 title: params.name ?? "",
102 lines: [`<strong>${val}</strong> (${pct.toFixed(1)}%)`]
103 })
104 }
105
106 export interface ChartTooltipAxisFormatterOptions {
107 resolveColor?: (param: CallbackDataParams) => CallbackDataParams["color"]
108 }
109
110 export function formatChartTooltipAxisFirst(
111 params: TopLevelFormatterParams,
112 options?: ChartTooltipAxisFormatterOptions
113 ): string {
114 if (!Array.isArray(params) || params.length === 0) return ""
115 const p = params[0]
116 const raw = Array.isArray(p.value) ? p.value[0] : p.value
117 const val = typeof raw === "number" ? raw : 0
118 return formatChartTooltipWithMarker({
119 marker: p.marker,
120 color: options?.resolveColor?.(p) ?? p.color,
121 title: p.name ?? "",
122 lines: [`<strong>${val}</strong>`]
123 })
124 }
125
126 export interface ChartTooltipTimeAxisFormatterOptions extends ChartTooltipAxisFormatterOptions {
127 formatTime?: (timestamp: number) => string
128 }
129
130 export interface ChartTooltipAxisMultiSeriesRow {
131 marker: CallbackDataParams["marker"]
132 color: CallbackDataParams["color"]
133 label: string
134 valueHtml: string
135 }
136
137 export interface ChartTooltipAxisMultiSeriesOptions {
138 /** Riga titolo senza marker (es. timestamp). */
139 title: string
140 /** Colore testo titolo; default `var(--fg-secondary-color)`. */
141 titleMutedColor?: string
142 rows: ChartTooltipAxisMultiSeriesRow[]
143 }
144
145 /** Asse multi-serie: titolo in alto, sotto righe `marker + label + valore`. */
146 export function formatChartTooltipAxisMultiSeries(options: ChartTooltipAxisMultiSeriesOptions): string {
147 const titleColor = options.titleMutedColor ?? "var(--fg-secondary-color)"
148 const rowsHtml = options.rows
149 .map(row => {
150 const marker = resolveChartTooltipMarker({ marker: row.marker, color: row.color })
151 return `<div style="display:flex;align-items:center;gap:4px;line-height:1.5">${marker}<span>${row.label}: <b>${row.valueHtml}</b></span></div>`
152 })
153 .join("")
154 return `
155 <div style="font-size:11px;color:${titleColor};padding:4px 8px;background-color:var(--bg-default-color)">${options.title}</div>
156 <div style="padding:4px 8px">${rowsHtml}</div>`
157 }
158
159 export function formatChartTooltipAxisMultiSeriesFromParams(
160 params: TopLevelFormatterParams,
161 options: {
162 formatTitle: (first: CallbackDataParams) => string
163 formatRow: (param: CallbackDataParams) => { label: string; valueHtml: string }
164 titleMutedColor?: string
165 }
166 ): string {
167 if (!Array.isArray(params) || params.length === 0) return ""
168 const first = params[0]
169 return formatChartTooltipAxisMultiSeries({
170 title: options.formatTitle(first),
171 titleMutedColor: options.titleMutedColor,
172 rows: params.map(p => ({
173 marker: p.marker,
174 color: p.color,
175 label: p.seriesName ?? "",
176 valueHtml: options.formatRow(p).valueHtml
177 }))
178 })
179 }
180
181 /** Sparkline / asse `time`: titolo = timestamp formattato, corpo = valore. */
182 export function formatChartTooltipTimeAxisFirst(
183 params: TopLevelFormatterParams,
184 options?: ChartTooltipTimeAxisFormatterOptions
185 ): string {
186 if (!Array.isArray(params) || params.length === 0) return ""
187 const p = params[0]
188 const time = Array.isArray(p.value) && typeof p.value[0] === "number" ? p.value[0] : null
189 const raw = Array.isArray(p.value) ? p.value[1] : p.value
190 const val = typeof raw === "number" ? raw : Number(raw) || 0
191 const timeLabel = time != null ? (options?.formatTime?.(time) ?? String(time)) : ""
192 return formatChartTooltipWithMarker({
193 marker: p.marker,
194 color: options?.resolveColor?.(p) ?? p.color,
195 title: timeLabel,
196 lines: [`<strong>${val}</strong>`]
197 })
198 }