@wick-charts/vue 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/index.d.ts +110 -19
- package/dist/index.js +1690 -1627
- package/package.json +28 -3
- package/src/BarSeries.vue +59 -0
- package/src/CandlestickSeries.vue +71 -0
- package/src/ChartContainer.vue +141 -0
- package/src/LineSeries.vue +59 -0
- package/src/PieSeries.vue +50 -0
- package/src/composables.ts +60 -0
- package/src/context.ts +31 -0
- package/src/index.ts +116 -0
- package/src/shims-vue.d.ts +6 -0
- package/src/ui/Crosshair.vue +60 -0
- package/src/ui/InfoBar.vue +196 -0
- package/src/ui/Legend.vue +302 -0
- package/src/ui/NumberFlow.vue +98 -0
- package/src/ui/PieLegend.vue +126 -0
- package/src/ui/PieTooltip.vue +200 -0
- package/src/ui/TimeAxis.vue +92 -0
- package/src/ui/Title.vue +57 -0
- package/src/ui/Tooltip.vue +262 -0
- package/src/ui/YAxis.vue +100 -0
- package/src/ui/YLabel.vue +194 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type ChartInstance, type SliceInfo, type ValueFormatter, formatCompact } from '@wick-charts/core';
|
|
3
|
+
import { computed, onMounted, onUnmounted, ref, useSlots } from 'vue';
|
|
4
|
+
|
|
5
|
+
import { useChartInstance, useTheme } from '../context';
|
|
6
|
+
|
|
7
|
+
type PieLegendMode = 'value' | 'percent' | 'both';
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
/**
|
|
12
|
+
* Owning series id. **Optional** — when omitted, the first visible pie
|
|
13
|
+
* series is picked.
|
|
14
|
+
*/
|
|
15
|
+
seriesId?: string;
|
|
16
|
+
/** 'value' shows only the value, 'percent' only the percent, 'both' shows both. Default: 'both'. */
|
|
17
|
+
mode?: PieLegendMode;
|
|
18
|
+
/** Custom formatter for the absolute slice value. */
|
|
19
|
+
format?: ValueFormatter;
|
|
20
|
+
}>(),
|
|
21
|
+
{
|
|
22
|
+
mode: undefined,
|
|
23
|
+
format: undefined,
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
defineSlots<{
|
|
28
|
+
default?(ctx: { slices: readonly SliceInfo[]; mode: PieLegendMode; format: ValueFormatter }): unknown;
|
|
29
|
+
}>();
|
|
30
|
+
|
|
31
|
+
const resolvedMode = computed<PieLegendMode>(() => props.mode ?? 'both');
|
|
32
|
+
const resolvedFormat = computed<ValueFormatter>(() => props.format ?? formatCompact);
|
|
33
|
+
|
|
34
|
+
const chart = useChartInstance();
|
|
35
|
+
const themeRef = useTheme();
|
|
36
|
+
const slots = useSlots();
|
|
37
|
+
const hasCustomSlot = computed(() => typeof slots.default === 'function');
|
|
38
|
+
|
|
39
|
+
const bump = ref(0);
|
|
40
|
+
const handler = () => {
|
|
41
|
+
bump.value++;
|
|
42
|
+
};
|
|
43
|
+
onMounted(() => {
|
|
44
|
+
chart.on('overlayChange', handler);
|
|
45
|
+
if (chart.getSeriesIds().length > 0) bump.value++;
|
|
46
|
+
});
|
|
47
|
+
onUnmounted(() => {
|
|
48
|
+
chart.off('overlayChange', handler);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function resolvePieSeriesId(c: ChartInstance, explicit: string | undefined): string | null {
|
|
52
|
+
if (explicit !== undefined) return explicit;
|
|
53
|
+
|
|
54
|
+
const pies = c.getSeriesIdsByType('pie', { visibleOnly: true });
|
|
55
|
+
|
|
56
|
+
return pies.length > 0 ? pies[0] : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const theme = computed(() => themeRef.value);
|
|
60
|
+
const resolvedId = computed<string | null>(() => {
|
|
61
|
+
void bump.value;
|
|
62
|
+
|
|
63
|
+
return resolvePieSeriesId(chart, props.seriesId);
|
|
64
|
+
});
|
|
65
|
+
const slices = computed<readonly SliceInfo[]>(() => {
|
|
66
|
+
void bump.value;
|
|
67
|
+
if (resolvedId.value === null) return [];
|
|
68
|
+
|
|
69
|
+
return chart.getSliceInfo(resolvedId.value) ?? [];
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<slot
|
|
75
|
+
v-if="hasCustomSlot && slices.length > 0"
|
|
76
|
+
:slices="slices"
|
|
77
|
+
:mode="resolvedMode"
|
|
78
|
+
:format="resolvedFormat"
|
|
79
|
+
/>
|
|
80
|
+
<div
|
|
81
|
+
v-else-if="slices.length > 0"
|
|
82
|
+
:style="{
|
|
83
|
+
display: 'flex',
|
|
84
|
+
flexDirection: 'column',
|
|
85
|
+
gap: '6px',
|
|
86
|
+
padding: '8px 12px',
|
|
87
|
+
fontFamily: theme.typography.fontFamily,
|
|
88
|
+
fontSize: theme.typography.fontSize + 'px',
|
|
89
|
+
color: theme.tooltip.textColor,
|
|
90
|
+
pointerEvents: 'auto',
|
|
91
|
+
}"
|
|
92
|
+
>
|
|
93
|
+
<div
|
|
94
|
+
v-for="(slice, i) in slices"
|
|
95
|
+
:key="i"
|
|
96
|
+
:style="{ display: 'flex', alignItems: 'center', gap: '10px' }"
|
|
97
|
+
>
|
|
98
|
+
<span :style="{ width: '10px', height: '10px', borderRadius: '50%', background: slice.color, flexShrink: 0 }" />
|
|
99
|
+
<span :style="{ flex: 1, opacity: 0.8 }">{{ slice.label }}</span>
|
|
100
|
+
<span
|
|
101
|
+
v-if="resolvedMode === 'value' || resolvedMode === 'both'"
|
|
102
|
+
:style="{
|
|
103
|
+
fontWeight: 600,
|
|
104
|
+
fontVariantNumeric: 'tabular-nums',
|
|
105
|
+
minWidth: resolvedMode === 'value' ? '40px' : undefined,
|
|
106
|
+
textAlign: 'right',
|
|
107
|
+
}"
|
|
108
|
+
>
|
|
109
|
+
{{ resolvedFormat(slice.value) }}
|
|
110
|
+
</span>
|
|
111
|
+
<span
|
|
112
|
+
v-if="resolvedMode === 'percent' || resolvedMode === 'both'"
|
|
113
|
+
:style="{
|
|
114
|
+
opacity: resolvedMode === 'percent' ? 1 : 0.5,
|
|
115
|
+
fontWeight: resolvedMode === 'percent' ? 600 : 400,
|
|
116
|
+
fontSize: (resolvedMode === 'percent' ? theme.typography.fontSize : theme.axis.fontSize) + 'px',
|
|
117
|
+
fontVariantNumeric: 'tabular-nums',
|
|
118
|
+
minWidth: '40px',
|
|
119
|
+
textAlign: 'right',
|
|
120
|
+
}"
|
|
121
|
+
>
|
|
122
|
+
{{ slice.percent.toFixed(1) }}%
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
type ChartInstance,
|
|
4
|
+
type HoverInfo,
|
|
5
|
+
type ValueFormatter,
|
|
6
|
+
computeTooltipPosition,
|
|
7
|
+
formatCompact,
|
|
8
|
+
} from '@wick-charts/core';
|
|
9
|
+
import { computed, onMounted, onUnmounted, ref, useSlots, watchEffect } from 'vue';
|
|
10
|
+
|
|
11
|
+
import { useCrosshairPosition } from '../composables';
|
|
12
|
+
import { useChartInstance } from '../context';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TOOLTIP_WIDTH = 160;
|
|
15
|
+
const DEFAULT_TOOLTIP_HEIGHT = 70;
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
/**
|
|
19
|
+
* Owning series id. **Optional** — when omitted, the first visible pie
|
|
20
|
+
* series is picked.
|
|
21
|
+
*/
|
|
22
|
+
seriesId?: string;
|
|
23
|
+
/** Custom formatter for the slice value. Default: shared `formatCompact`. */
|
|
24
|
+
format?: ValueFormatter;
|
|
25
|
+
}>();
|
|
26
|
+
|
|
27
|
+
defineSlots<{
|
|
28
|
+
default?(ctx: { info: HoverInfo; format: ValueFormatter }): unknown;
|
|
29
|
+
}>();
|
|
30
|
+
|
|
31
|
+
const effectiveFormat = computed<ValueFormatter>(() => props.format ?? formatCompact);
|
|
32
|
+
|
|
33
|
+
const chart = useChartInstance();
|
|
34
|
+
const crosshair = useCrosshairPosition(chart);
|
|
35
|
+
const slots = useSlots();
|
|
36
|
+
const hasCustomSlot = computed(() => typeof slots.default === 'function');
|
|
37
|
+
|
|
38
|
+
const bump = ref(0);
|
|
39
|
+
const handler = () => {
|
|
40
|
+
bump.value++;
|
|
41
|
+
};
|
|
42
|
+
onMounted(() => {
|
|
43
|
+
chart.on('overlayChange', handler);
|
|
44
|
+
});
|
|
45
|
+
onUnmounted(() => {
|
|
46
|
+
chart.off('overlayChange', handler);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function resolvePieSeriesId(c: ChartInstance, explicit: string | undefined): string | null {
|
|
50
|
+
if (explicit !== undefined) return explicit;
|
|
51
|
+
|
|
52
|
+
const pies = c.getSeriesIdsByType('pie', { visibleOnly: true });
|
|
53
|
+
|
|
54
|
+
return pies.length > 0 ? pies[0] : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const theme = computed(() => {
|
|
58
|
+
// `setTheme` fires `overlayChange`; read `bump` so this computed picks up
|
|
59
|
+
// runtime theme swaps instead of freezing on the first-render snapshot.
|
|
60
|
+
void bump.value;
|
|
61
|
+
|
|
62
|
+
return chart.getTheme();
|
|
63
|
+
});
|
|
64
|
+
const resolvedId = computed<string | null>(() => {
|
|
65
|
+
void bump.value;
|
|
66
|
+
|
|
67
|
+
return resolvePieSeriesId(chart, props.seriesId);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const info = computed<HoverInfo | null>(() => {
|
|
71
|
+
void crosshair.value;
|
|
72
|
+
void bump.value;
|
|
73
|
+
if (resolvedId.value === null) return null;
|
|
74
|
+
|
|
75
|
+
return chart.getHoverInfo(resolvedId.value);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Custom-slot measurement — unknown content dimensions mean we can't
|
|
79
|
+
// pre-compute `tooltipWidth/Height`. Observe the container, then recompute
|
|
80
|
+
// the position once measured so user-rendered tooltips flip/clamp correctly
|
|
81
|
+
// instead of overflowing with the hardcoded 160×70 defaults.
|
|
82
|
+
const containerRef = ref<HTMLDivElement | null>(null);
|
|
83
|
+
const measuredSize = ref<{ width: number; height: number } | null>(null);
|
|
84
|
+
let observer: ResizeObserver | null = null;
|
|
85
|
+
|
|
86
|
+
watchEffect((onCleanup) => {
|
|
87
|
+
if (!hasCustomSlot.value) return;
|
|
88
|
+
const node = containerRef.value;
|
|
89
|
+
if (!node || typeof ResizeObserver === 'undefined') return;
|
|
90
|
+
|
|
91
|
+
observer = new ResizeObserver((entries) => {
|
|
92
|
+
const box = entries[0]?.contentRect;
|
|
93
|
+
if (!box) return;
|
|
94
|
+
const prev = measuredSize.value;
|
|
95
|
+
if (!prev || prev.width !== box.width || prev.height !== box.height) {
|
|
96
|
+
measuredSize.value = { width: box.width, height: box.height };
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
observer.observe(node);
|
|
100
|
+
|
|
101
|
+
onCleanup(() => {
|
|
102
|
+
observer?.disconnect();
|
|
103
|
+
observer = null;
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const tooltipPos = computed(() => {
|
|
108
|
+
if (!crosshair.value || !info.value) return null;
|
|
109
|
+
const mediaSize = chart.getMediaSize();
|
|
110
|
+
|
|
111
|
+
if (hasCustomSlot.value) {
|
|
112
|
+
const size = measuredSize.value;
|
|
113
|
+
if (!size) return { left: 0, top: 0 };
|
|
114
|
+
|
|
115
|
+
return computeTooltipPosition({
|
|
116
|
+
x: crosshair.value.mediaX,
|
|
117
|
+
y: crosshair.value.mediaY,
|
|
118
|
+
chartWidth: mediaSize.width,
|
|
119
|
+
chartHeight: mediaSize.height,
|
|
120
|
+
tooltipWidth: size.width,
|
|
121
|
+
tooltipHeight: size.height,
|
|
122
|
+
offsetX: 16,
|
|
123
|
+
offsetY: 16,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return computeTooltipPosition({
|
|
128
|
+
x: crosshair.value.mediaX,
|
|
129
|
+
y: crosshair.value.mediaY,
|
|
130
|
+
chartWidth: mediaSize.width,
|
|
131
|
+
chartHeight: mediaSize.height,
|
|
132
|
+
tooltipWidth: DEFAULT_TOOLTIP_WIDTH,
|
|
133
|
+
tooltipHeight: DEFAULT_TOOLTIP_HEIGHT,
|
|
134
|
+
offsetX: 16,
|
|
135
|
+
offsetY: 16,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<div
|
|
142
|
+
v-if="info && crosshair && tooltipPos && !hasCustomSlot"
|
|
143
|
+
:style="{
|
|
144
|
+
position: 'absolute',
|
|
145
|
+
left: tooltipPos.left + 'px',
|
|
146
|
+
top: tooltipPos.top + 'px',
|
|
147
|
+
pointerEvents: 'none',
|
|
148
|
+
background: theme.tooltip.background,
|
|
149
|
+
backdropFilter: 'blur(12px)',
|
|
150
|
+
WebkitBackdropFilter: 'blur(12px)',
|
|
151
|
+
border: '1px solid ' + theme.tooltip.borderColor,
|
|
152
|
+
borderRadius: '8px',
|
|
153
|
+
padding: '10px 14px',
|
|
154
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.1), 0 1px 4px rgba(0,0,0,0.06)',
|
|
155
|
+
fontSize: theme.typography.fontSize + 'px',
|
|
156
|
+
fontFamily: theme.typography.fontFamily,
|
|
157
|
+
color: theme.tooltip.textColor,
|
|
158
|
+
zIndex: 10,
|
|
159
|
+
display: 'flex',
|
|
160
|
+
flexDirection: 'column',
|
|
161
|
+
gap: '6px',
|
|
162
|
+
}"
|
|
163
|
+
>
|
|
164
|
+
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px' }">
|
|
165
|
+
<span
|
|
166
|
+
:style="{
|
|
167
|
+
width: '10px',
|
|
168
|
+
height: '10px',
|
|
169
|
+
borderRadius: '50%',
|
|
170
|
+
background: info.color,
|
|
171
|
+
flexShrink: 0,
|
|
172
|
+
}"
|
|
173
|
+
/>
|
|
174
|
+
<span :style="{ fontWeight: 600 }">{{ info.label }}</span>
|
|
175
|
+
</div>
|
|
176
|
+
<div :style="{ display: 'flex', justifyContent: 'space-between', gap: '16px' }">
|
|
177
|
+
<span :style="{ opacity: 0.6 }">{{ effectiveFormat(info.value) }}</span>
|
|
178
|
+
<span :style="{ fontWeight: 600 }">{{ info.percent.toFixed(1) }}%</span>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div
|
|
183
|
+
v-if="info && crosshair && tooltipPos && hasCustomSlot"
|
|
184
|
+
ref="containerRef"
|
|
185
|
+
:data-measured="measuredSize !== null ? 'true' : 'false'"
|
|
186
|
+
:style="{
|
|
187
|
+
position: 'absolute',
|
|
188
|
+
left: tooltipPos.left + 'px',
|
|
189
|
+
top: tooltipPos.top + 'px',
|
|
190
|
+
pointerEvents: 'none',
|
|
191
|
+
zIndex: 10,
|
|
192
|
+
width: 'max-content',
|
|
193
|
+
maxWidth: chart.getMediaSize().width + 'px',
|
|
194
|
+
boxSizing: 'border-box',
|
|
195
|
+
visibility: measuredSize !== null ? 'visible' : 'hidden',
|
|
196
|
+
}"
|
|
197
|
+
>
|
|
198
|
+
<slot :info="info" :format="effectiveFormat" />
|
|
199
|
+
</div>
|
|
200
|
+
</template>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { formatTime, resolveAxisFontSize, resolveAxisTextColor } from '@wick-charts/core';
|
|
3
|
+
import { computed } from 'vue';
|
|
4
|
+
|
|
5
|
+
import { useVisibleRange } from '../composables';
|
|
6
|
+
import { useChartInstance } from '../context';
|
|
7
|
+
|
|
8
|
+
interface TrackedTick {
|
|
9
|
+
opacity: number;
|
|
10
|
+
addedAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const chart = useChartInstance();
|
|
14
|
+
const _visibleRange = useVisibleRange(chart);
|
|
15
|
+
|
|
16
|
+
const tickMap = new Map<number, TrackedTick>();
|
|
17
|
+
|
|
18
|
+
const theme = computed(() => chart.getTheme());
|
|
19
|
+
|
|
20
|
+
const tickData = computed(() => {
|
|
21
|
+
// Access _visibleRange.value to track dependency
|
|
22
|
+
void _visibleRange.value;
|
|
23
|
+
|
|
24
|
+
const dataInterval = chart.getDataInterval();
|
|
25
|
+
const { ticks: currentTicks, tickInterval } = chart.timeScale.niceTickValues(dataInterval);
|
|
26
|
+
const currentSet = new Set(currentTicks);
|
|
27
|
+
const now = performance.now();
|
|
28
|
+
|
|
29
|
+
// Mark current ticks as visible
|
|
30
|
+
for (const t of currentTicks) {
|
|
31
|
+
if (!tickMap.has(t)) {
|
|
32
|
+
tickMap.set(t, { opacity: 1, addedAt: now });
|
|
33
|
+
} else {
|
|
34
|
+
tickMap.get(t)!.opacity = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Mark missing ticks for fade-out
|
|
39
|
+
for (const [t, entry] of tickMap) {
|
|
40
|
+
if (!currentSet.has(t)) {
|
|
41
|
+
entry.opacity = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Clean up old faded-out ticks
|
|
46
|
+
for (const [t, entry] of tickMap) {
|
|
47
|
+
if (entry.opacity === 0 && now - entry.addedAt > 5000) {
|
|
48
|
+
tickMap.delete(t);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
allTicks: Array.from(tickMap.entries()),
|
|
54
|
+
tickInterval,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<div
|
|
61
|
+
:style="{
|
|
62
|
+
position: 'absolute',
|
|
63
|
+
left: '0',
|
|
64
|
+
bottom: '0',
|
|
65
|
+
right: chart.yAxisWidth + 'px',
|
|
66
|
+
height: chart.xAxisHeight + 'px',
|
|
67
|
+
pointerEvents: 'none',
|
|
68
|
+
display: 'flex',
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
}"
|
|
71
|
+
>
|
|
72
|
+
<span
|
|
73
|
+
v-for="[time, entry] in tickData.allTicks"
|
|
74
|
+
:key="time"
|
|
75
|
+
:style="{
|
|
76
|
+
position: 'absolute',
|
|
77
|
+
left: chart.timeScale.timeToX(time) + 'px',
|
|
78
|
+
transform: 'translateX(-50%)',
|
|
79
|
+
color: resolveAxisTextColor(theme, 'x'),
|
|
80
|
+
fontSize: resolveAxisFontSize(theme, 'x') + 'px',
|
|
81
|
+
fontFamily: theme.typography.fontFamily,
|
|
82
|
+
userSelect: 'none',
|
|
83
|
+
whiteSpace: 'nowrap',
|
|
84
|
+
opacity: entry.opacity,
|
|
85
|
+
transition: 'opacity 0.3s ease',
|
|
86
|
+
willChange: 'opacity',
|
|
87
|
+
}"
|
|
88
|
+
>
|
|
89
|
+
{{ formatTime(time, tickData.tickInterval) }}
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
package/src/ui/Title.vue
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { inject } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { ThemeKey, TitleAnchorKey } from '../context';
|
|
5
|
+
|
|
6
|
+
withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
sub?: string;
|
|
9
|
+
}>(),
|
|
10
|
+
{},
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const anchor = inject(TitleAnchorKey);
|
|
14
|
+
const theme = inject(ThemeKey);
|
|
15
|
+
|
|
16
|
+
// Match the "loud failure" contract of other Vue overlays (`useChartInstance`,
|
|
17
|
+
// `useTheme`) — misuse outside `<ChartContainer>` should throw, not render
|
|
18
|
+
// nothing silently.
|
|
19
|
+
if (!anchor) {
|
|
20
|
+
throw new Error('<Title> must be used within <ChartContainer>: missing TitleAnchorKey.');
|
|
21
|
+
}
|
|
22
|
+
if (!theme) {
|
|
23
|
+
throw new Error('<Title> must be used within <ChartContainer>: missing ThemeKey.');
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<Teleport v-if="anchor" :to="anchor">
|
|
29
|
+
<div
|
|
30
|
+
data-chart-title=""
|
|
31
|
+
:style="{
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'baseline',
|
|
34
|
+
gap: '6px',
|
|
35
|
+
padding: '6px 8px 4px',
|
|
36
|
+
flexShrink: 0,
|
|
37
|
+
fontFamily: theme.typography.fontFamily,
|
|
38
|
+
fontSize: theme.typography.fontSize + 'px',
|
|
39
|
+
fontWeight: 600,
|
|
40
|
+
color: theme.tooltip.textColor,
|
|
41
|
+
pointerEvents: 'none',
|
|
42
|
+
}"
|
|
43
|
+
>
|
|
44
|
+
<span><slot /></span>
|
|
45
|
+
<span
|
|
46
|
+
v-if="sub"
|
|
47
|
+
:style="{
|
|
48
|
+
fontWeight: 400,
|
|
49
|
+
color: theme.axis.textColor,
|
|
50
|
+
fontSize: theme.axis.fontSize + 'px',
|
|
51
|
+
}"
|
|
52
|
+
>
|
|
53
|
+
{{ sub }}
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
56
|
+
</Teleport>
|
|
57
|
+
</template>
|