@perses-dev/components 0.54.0-beta.9 → 0.54.0-rc.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/dist/EChart/index.d.ts +1 -0
- package/dist/EChart/index.d.ts.map +1 -1
- package/dist/EChart/index.js +1 -0
- package/dist/EChart/index.js.map +1 -1
- package/dist/EChart/timezone-formatter.d.ts +5 -0
- package/dist/EChart/timezone-formatter.d.ts.map +1 -0
- package/dist/EChart/timezone-formatter.js +43 -0
- package/dist/EChart/timezone-formatter.js.map +1 -0
- package/dist/LinksEditor/index.d.ts +0 -1
- package/dist/LinksEditor/index.d.ts.map +1 -1
- package/dist/LinksEditor/index.js +0 -1
- package/dist/LinksEditor/index.js.map +1 -1
- package/dist/TimeSeriesTooltip/TimeChartTooltip.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/TimeChartTooltip.js +25 -6
- package/dist/TimeSeriesTooltip/TimeChartTooltip.js.map +1 -1
- package/dist/TimeSeriesTooltip/TooltipContent.d.ts +1 -1
- package/dist/TimeSeriesTooltip/TooltipContent.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/TooltipContent.js.map +1 -1
- package/dist/TimeSeriesTooltip/TooltipHeader.d.ts +1 -1
- package/dist/TimeSeriesTooltip/TooltipHeader.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/TooltipHeader.js.map +1 -1
- package/dist/TimeSeriesTooltip/index.d.ts +1 -0
- package/dist/TimeSeriesTooltip/index.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/index.js +1 -0
- package/dist/TimeSeriesTooltip/index.js.map +1 -1
- package/dist/TimeSeriesTooltip/nearby-series.d.ts +5 -23
- package/dist/TimeSeriesTooltip/nearby-series.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/nearby-series.js +218 -153
- package/dist/TimeSeriesTooltip/nearby-series.js.map +1 -1
- package/dist/TimeSeriesTooltip/tooltip-model.d.ts +1 -1
- package/dist/TimeSeriesTooltip/tooltip-model.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/tooltip-model.js.map +1 -1
- package/dist/TimeSeriesTooltip/types.d.ts +30 -0
- package/dist/TimeSeriesTooltip/types.d.ts.map +1 -0
- package/dist/TimeSeriesTooltip/types.js +15 -0
- package/dist/TimeSeriesTooltip/types.js.map +1 -0
- package/dist/TimeSeriesTooltip/utils.d.ts +26 -1
- package/dist/TimeSeriesTooltip/utils.d.ts.map +1 -1
- package/dist/TimeSeriesTooltip/utils.js +105 -9
- package/dist/TimeSeriesTooltip/utils.js.map +1 -1
- package/dist/cjs/EChart/index.js +1 -0
- package/dist/cjs/EChart/timezone-formatter.js +49 -0
- package/dist/cjs/LinksEditor/index.js +0 -1
- package/dist/cjs/TimeSeriesTooltip/TimeChartTooltip.js +24 -5
- package/dist/cjs/TimeSeriesTooltip/index.js +1 -0
- package/dist/cjs/TimeSeriesTooltip/nearby-series.js +216 -151
- package/dist/cjs/TimeSeriesTooltip/types.js +16 -0
- package/dist/cjs/TimeSeriesTooltip/utils.js +109 -8
- package/dist/cjs/utils/chart-actions.js +12 -4
- package/dist/cjs/utils/request-interpolation.js +20 -2
- package/dist/cjs/utils/variable-interpolation.js +6 -0
- package/dist/utils/chart-actions.d.ts.map +1 -1
- package/dist/utils/chart-actions.js +12 -4
- package/dist/utils/chart-actions.js.map +1 -1
- package/dist/utils/request-interpolation.d.ts.map +1 -1
- package/dist/utils/request-interpolation.js +20 -2
- package/dist/utils/request-interpolation.js.map +1 -1
- package/dist/utils/variable-interpolation.d.ts +3 -1
- package/dist/utils/variable-interpolation.d.ts.map +1 -1
- package/dist/utils/variable-interpolation.js +6 -0
- package/dist/utils/variable-interpolation.js.map +1 -1
- package/package.json +3 -4
- package/dist/LinksEditor/LinksEditor.d.ts +0 -8
- package/dist/LinksEditor/LinksEditor.d.ts.map +0 -1
- package/dist/LinksEditor/LinksEditor.js +0 -149
- package/dist/LinksEditor/LinksEditor.js.map +0 -1
- package/dist/cjs/LinksEditor/LinksEditor.js +0 -162
|
@@ -49,35 +49,220 @@ _export(exports, {
|
|
|
49
49
|
const _model = require("../model");
|
|
50
50
|
const _utils = require("../utils");
|
|
51
51
|
const _tooltipmodel = require("./tooltip-model");
|
|
52
|
+
const _utils1 = require("./utils");
|
|
52
53
|
const INCREASE_NEARBY_SERIES_MULTIPLIER = 5.5; // adjusts how many series show in tooltip (higher == more series shown)
|
|
53
54
|
const DYNAMIC_NEARBY_SERIES_MULTIPLIER = 30; // used for adjustment after series number divisor
|
|
54
55
|
const SHOW_FEWER_SERIES_LIMIT = 5;
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
56
|
+
function gatherCandidates(data, seriesMapping, closestTimestamp, cursorX, cursorY, cursorXPixel, cursorPixelY, yBuffer, yBufferPixels, chart) {
|
|
57
|
+
const candidates = [];
|
|
58
|
+
const totalSeries = data.length;
|
|
59
|
+
const stackTotals = new Map();
|
|
60
|
+
let sortedTimestamps = [];
|
|
61
|
+
const firstValues = data[0]?.values;
|
|
62
|
+
if (firstValues && firstValues.length > 0) {
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const [ts] of firstValues){
|
|
65
|
+
if (!seen.has(ts)) {
|
|
66
|
+
seen.add(ts);
|
|
67
|
+
sortedTimestamps.push(ts);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
sortedTimestamps = sortedTimestamps.sort((a, b)=>a - b);
|
|
71
|
+
}
|
|
72
|
+
// Bar-only indexes: ECharts groups bars independently of lines, so bar-relative index and count must exclude line series.
|
|
73
|
+
const barSeriesIndexes = [];
|
|
74
|
+
for(let i = 0; i < totalSeries; i++){
|
|
75
|
+
if ((seriesMapping[i]?.type ?? 'line') === 'bar') barSeriesIndexes.push(i);
|
|
76
|
+
}
|
|
77
|
+
// Computed once outside the loop — both depend only on the timestamp, not the series index.
|
|
78
|
+
let barBandwidth = null;
|
|
79
|
+
let barCenterPixelX = null;
|
|
80
|
+
if (barSeriesIndexes.length > 0 && cursorXPixel !== null) {
|
|
81
|
+
barBandwidth = (0, _utils1.calculateBarBandwidth)(closestTimestamp, sortedTimestamps, chart);
|
|
82
|
+
barCenterPixelX = (0, _utils1.getPixelXFromGrid)(closestTimestamp, chart);
|
|
83
|
+
}
|
|
84
|
+
for(let seriesIdx = 0; seriesIdx < totalSeries; seriesIdx++){
|
|
85
|
+
const currentSeries = seriesMapping[seriesIdx];
|
|
86
|
+
if (!currentSeries) continue;
|
|
87
|
+
const currentDataset = data[seriesIdx];
|
|
88
|
+
if (!currentDataset) continue;
|
|
89
|
+
const currentDatasetValues = currentDataset.values;
|
|
90
|
+
if (!currentDatasetValues || !Array.isArray(currentDatasetValues)) continue;
|
|
91
|
+
const seriesType = currentSeries.type ?? 'line';
|
|
92
|
+
const currentSeriesName = currentSeries.name ? currentSeries.name.toString() : '';
|
|
93
|
+
const seriesId = currentSeries.id ? currentSeries.id.toString() : '';
|
|
94
|
+
const markerColor = (currentSeries.color ?? '#000').toString();
|
|
95
|
+
let datumIdx = -1;
|
|
96
|
+
let xValue = 0;
|
|
97
|
+
let yValue;
|
|
98
|
+
for(let i = 0; i < currentDatasetValues.length; i++){
|
|
99
|
+
const tuple = currentDatasetValues[i];
|
|
100
|
+
if (!tuple) continue;
|
|
101
|
+
if (tuple[0] === closestTimestamp) {
|
|
102
|
+
datumIdx = i;
|
|
103
|
+
xValue = tuple[0];
|
|
104
|
+
yValue = tuple[1];
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (datumIdx === -1) continue;
|
|
109
|
+
if (yValue === null || yValue === undefined) continue;
|
|
110
|
+
let isCandidate = false;
|
|
111
|
+
let visualY = yValue;
|
|
112
|
+
let distance = Infinity;
|
|
113
|
+
if (seriesType === 'line') {
|
|
114
|
+
visualY = (0, _utils1.calculateVisualYForSeries)(seriesIdx, yValue, seriesMapping, stackTotals);
|
|
115
|
+
if (cursorPixelY !== undefined && yBufferPixels !== null) {
|
|
116
|
+
try {
|
|
117
|
+
const dataPointPixel = chart.convertToPixel({
|
|
118
|
+
seriesIndex: seriesIdx
|
|
119
|
+
}, [
|
|
120
|
+
datumIdx,
|
|
121
|
+
visualY
|
|
122
|
+
]);
|
|
123
|
+
if (dataPointPixel && dataPointPixel[1] !== undefined) {
|
|
124
|
+
const pixelDistance = Math.abs(cursorPixelY - dataPointPixel[1]);
|
|
125
|
+
isCandidate = pixelDistance <= yBufferPixels;
|
|
126
|
+
distance = pixelDistance;
|
|
127
|
+
} else {
|
|
128
|
+
const verticalDistance = Math.abs(visualY - cursorY);
|
|
129
|
+
isCandidate = verticalDistance <= yBuffer;
|
|
130
|
+
distance = verticalDistance;
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
const verticalDistance = Math.abs(visualY - cursorY);
|
|
134
|
+
isCandidate = verticalDistance <= yBuffer;
|
|
135
|
+
distance = verticalDistance;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const verticalDistance = Math.abs(visualY - cursorY);
|
|
139
|
+
isCandidate = verticalDistance <= yBuffer;
|
|
140
|
+
distance = verticalDistance;
|
|
141
|
+
}
|
|
142
|
+
} else if (seriesType === 'bar') {
|
|
143
|
+
if (cursorXPixel === null || barBandwidth === null || barCenterPixelX === null) continue;
|
|
144
|
+
const barRelativeIdx = barSeriesIndexes.indexOf(seriesIdx);
|
|
145
|
+
if (barRelativeIdx === -1) continue;
|
|
146
|
+
const segmentBounds = (0, _utils1.calculateBarSegmentBounds)(barRelativeIdx, barBandwidth, barCenterPixelX, barSeriesIndexes.length);
|
|
147
|
+
const isWithinXBounds = cursorXPixel >= segmentBounds.left && cursorXPixel <= segmentBounds.right;
|
|
148
|
+
if (!isWithinXBounds) continue;
|
|
149
|
+
const stackId = currentSeries.stack;
|
|
150
|
+
let isHoveringYBounds = true;
|
|
151
|
+
if (stackId) {
|
|
152
|
+
const stackIdStr = stackId.toString();
|
|
153
|
+
const visualYBottom = stackTotals.get(stackIdStr) ?? 0;
|
|
154
|
+
visualY = (0, _utils1.calculateVisualYForSeries)(seriesIdx, yValue, seriesMapping, stackTotals);
|
|
155
|
+
const yBounds = (0, _utils1.calculateBarYBounds)(visualYBottom, visualY, chart);
|
|
156
|
+
if (yBounds) {
|
|
157
|
+
const cursorYPixel = chart.convertToPixel('grid', [
|
|
158
|
+
0,
|
|
159
|
+
cursorY
|
|
160
|
+
]);
|
|
161
|
+
if (cursorYPixel && cursorYPixel[1] !== undefined) {
|
|
162
|
+
isHoveringYBounds = cursorYPixel[1] >= yBounds.top && cursorYPixel[1] <= yBounds.bottom;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
visualY = yValue;
|
|
167
|
+
}
|
|
168
|
+
if (!isHoveringYBounds) continue;
|
|
169
|
+
const segmentCenter = (segmentBounds.left + segmentBounds.right) / 2;
|
|
170
|
+
distance = Math.abs(cursorXPixel - segmentCenter);
|
|
171
|
+
isCandidate = true;
|
|
172
|
+
}
|
|
173
|
+
if (isCandidate) {
|
|
174
|
+
candidates.push({
|
|
175
|
+
seriesIdx,
|
|
176
|
+
datumIdx,
|
|
177
|
+
seriesId,
|
|
178
|
+
seriesName: currentSeriesName,
|
|
179
|
+
date: closestTimestamp,
|
|
180
|
+
markerColor,
|
|
181
|
+
x: xValue,
|
|
182
|
+
y: yValue,
|
|
183
|
+
visualY,
|
|
184
|
+
distance
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return candidates;
|
|
189
|
+
}
|
|
190
|
+
function findClosestCandidate(candidates) {
|
|
191
|
+
if (candidates.length === 0) return null;
|
|
192
|
+
let winner = null;
|
|
193
|
+
for (const candidate of candidates){
|
|
194
|
+
if (winner === null || candidate.distance < winner.distance) {
|
|
195
|
+
winner = candidate;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return winner;
|
|
199
|
+
}
|
|
200
|
+
function processCandidates(candidates, winner, format, seriesFormatMap, chart, nonCandidateSeriesIndexes) {
|
|
63
201
|
const nearbySeriesIndexes = [];
|
|
64
202
|
const emphasizedSeriesIndexes = [];
|
|
65
|
-
const nonEmphasizedSeriesIndexes = [
|
|
203
|
+
const nonEmphasizedSeriesIndexes = [
|
|
204
|
+
...nonCandidateSeriesIndexes
|
|
205
|
+
];
|
|
66
206
|
const emphasizedDatapoints = [];
|
|
67
207
|
const duplicateDatapoints = [];
|
|
68
|
-
const totalSeries = data.length;
|
|
69
208
|
const yValueCounts = new Map();
|
|
70
|
-
|
|
209
|
+
const result = [];
|
|
210
|
+
for (const candidate of candidates){
|
|
211
|
+
const seriesFormat = seriesFormatMap?.get(candidate.seriesId) ?? format;
|
|
212
|
+
// Use raw y, not visualY — visualY is for proximity detection only.
|
|
213
|
+
const displayY = candidate.y;
|
|
214
|
+
const formattedY = (0, _model.formatValue)(displayY, seriesFormat);
|
|
215
|
+
const isClosestToCursor = winner !== null && candidate.seriesIdx === winner.seriesIdx;
|
|
216
|
+
if (isClosestToCursor) {
|
|
217
|
+
emphasizedSeriesIndexes.push(candidate.seriesIdx);
|
|
218
|
+
const duplicateValuesCount = yValueCounts.get(displayY) ?? 0;
|
|
219
|
+
yValueCounts.set(displayY, duplicateValuesCount + 1);
|
|
220
|
+
if (duplicateValuesCount > 0) {
|
|
221
|
+
duplicateDatapoints.push({
|
|
222
|
+
seriesIndex: candidate.seriesIdx,
|
|
223
|
+
dataIndex: candidate.datumIdx,
|
|
224
|
+
seriesName: candidate.seriesName,
|
|
225
|
+
yValue: displayY
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
emphasizedDatapoints.push({
|
|
229
|
+
seriesIndex: candidate.seriesIdx,
|
|
230
|
+
dataIndex: candidate.datumIdx,
|
|
231
|
+
seriesName: candidate.seriesName,
|
|
232
|
+
yValue: displayY
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
nonEmphasizedSeriesIndexes.push(candidate.seriesIdx);
|
|
236
|
+
}
|
|
237
|
+
result.push({
|
|
238
|
+
seriesIdx: candidate.seriesIdx,
|
|
239
|
+
datumIdx: candidate.datumIdx,
|
|
240
|
+
seriesName: candidate.seriesName,
|
|
241
|
+
date: candidate.date,
|
|
242
|
+
x: candidate.x,
|
|
243
|
+
y: displayY,
|
|
244
|
+
formattedY,
|
|
245
|
+
markerColor: candidate.markerColor,
|
|
246
|
+
isClosestToCursor
|
|
247
|
+
});
|
|
248
|
+
nearbySeriesIndexes.push(candidate.seriesIdx);
|
|
249
|
+
}
|
|
250
|
+
(0, _utils.batchDispatchNearbySeriesActions)(chart, nearbySeriesIndexes, emphasizedSeriesIndexes, nonEmphasizedSeriesIndexes, emphasizedDatapoints, duplicateDatapoints);
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
function checkforNearbyTimeSeries(data, seriesMapping, pointInGrid, yBuffer, chart, format, seriesFormatMap, // in the case of multi-axis, we need the cursor Y position in pixel space
|
|
254
|
+
cursorPixelY, cursorXPixel) {
|
|
255
|
+
const cursorX = pointInGrid[0] ?? null;
|
|
256
|
+
const cursorY = pointInGrid[1] ?? null;
|
|
257
|
+
if (cursorX === null || cursorY === null) return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
258
|
+
if (chart.dispatchAction === undefined) return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
259
|
+
if (!Array.isArray(data)) return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
260
|
+
// All series share the same x-axis timestamps (enforced by getCommonTimeScale).
|
|
71
261
|
const firstTimeSeriesValues = data[0]?.values;
|
|
72
262
|
const closestTimestamp = (0, _utils.getClosestTimestamp)(firstTimeSeriesValues, cursorX);
|
|
73
|
-
if (closestTimestamp === null)
|
|
74
|
-
return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
75
|
-
}
|
|
76
|
-
// For multi-axis support: convert yBuffer to pixel space for consistent comparison
|
|
77
|
-
// This allows us to compare series on different Y axes fairly
|
|
263
|
+
if (closestTimestamp === null) return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
78
264
|
let yBufferPixels = null;
|
|
79
265
|
if (cursorPixelY !== undefined) {
|
|
80
|
-
// Convert a point at cursorY and cursorY + yBuffer to pixels to get the buffer in pixel space
|
|
81
266
|
const cursorPoint = chart.convertToPixel('grid', [
|
|
82
267
|
0,
|
|
83
268
|
cursorY
|
|
@@ -90,133 +275,16 @@ cursorPixelY) {
|
|
|
90
275
|
yBufferPixels = Math.abs(bufferPoint[1] - cursorPoint[1]);
|
|
91
276
|
}
|
|
92
277
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
const lineSeries = currentSeries;
|
|
102
|
-
const currentSeriesName = lineSeries.name ? lineSeries.name.toString() : '';
|
|
103
|
-
const seriesId = lineSeries.id ? lineSeries.id.toString() : '';
|
|
104
|
-
const markerColor = lineSeries.color ?? '#000';
|
|
105
|
-
// Get the format for this series (from seriesFormatMap or fallback to default format)
|
|
106
|
-
const seriesFormat = seriesFormatMap?.get(seriesId) ?? format;
|
|
107
|
-
if (Array.isArray(data)) {
|
|
108
|
-
for(let datumIdx = 0; datumIdx < currentDatasetValues.length; datumIdx++){
|
|
109
|
-
const nearbyTimeSeries = currentDatasetValues[datumIdx];
|
|
110
|
-
if (nearbyTimeSeries === undefined || !Array.isArray(nearbyTimeSeries)) break;
|
|
111
|
-
const xValue = nearbyTimeSeries[0];
|
|
112
|
-
const yValue = nearbyTimeSeries[1];
|
|
113
|
-
// TODO: ensure null values not displayed in tooltip
|
|
114
|
-
if (yValue !== undefined && yValue !== null) {
|
|
115
|
-
if (closestTimestamp === xValue) {
|
|
116
|
-
// Check if this series is nearby the cursor
|
|
117
|
-
let isNearby = false;
|
|
118
|
-
// For multi-axis: compare in pixel space
|
|
119
|
-
if (cursorPixelY !== undefined && yBufferPixels !== null) {
|
|
120
|
-
const dataPointPixel = chart.convertToPixel({
|
|
121
|
-
seriesIndex: seriesIdx
|
|
122
|
-
}, [
|
|
123
|
-
datumIdx,
|
|
124
|
-
yValue
|
|
125
|
-
]);
|
|
126
|
-
if (dataPointPixel && dataPointPixel[1] !== undefined) {
|
|
127
|
-
const pixelDistance = Math.abs(cursorPixelY - dataPointPixel[1]);
|
|
128
|
-
isNearby = pixelDistance <= yBufferPixels;
|
|
129
|
-
} else {
|
|
130
|
-
// Fallback to data-space comparison for primary axis
|
|
131
|
-
isNearby = cursorY <= yValue + yBuffer && cursorY >= yValue - yBuffer;
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
// Fallback to original data-space comparison
|
|
135
|
-
isNearby = cursorY <= yValue + yBuffer && cursorY >= yValue - yBuffer;
|
|
136
|
-
}
|
|
137
|
-
if (isNearby) {
|
|
138
|
-
// show fewer bold series in tooltip when many total series
|
|
139
|
-
const minPercentRange = totalSeries > SHOW_FEWER_SERIES_LIMIT ? 2 : 5;
|
|
140
|
-
const percentRangeToCheck = Math.max(minPercentRange, 100 / totalSeries);
|
|
141
|
-
// For isClosestToCursor, also use pixel space for multi-axis
|
|
142
|
-
let isClosestToCursor = false;
|
|
143
|
-
if (cursorPixelY !== undefined) {
|
|
144
|
-
const dataPointPixel = chart.convertToPixel({
|
|
145
|
-
seriesIndex: seriesIdx
|
|
146
|
-
}, [
|
|
147
|
-
datumIdx,
|
|
148
|
-
yValue
|
|
149
|
-
]);
|
|
150
|
-
if (dataPointPixel && dataPointPixel[1] !== undefined) {
|
|
151
|
-
const pixelDistance = Math.abs(cursorPixelY - dataPointPixel[1]);
|
|
152
|
-
// Use percentage of buffer for "closest" determination
|
|
153
|
-
const tightBufferPixels = (yBufferPixels ?? 50) * (percentRangeToCheck / 100);
|
|
154
|
-
isClosestToCursor = pixelDistance <= Math.max(tightBufferPixels, 5);
|
|
155
|
-
} else {
|
|
156
|
-
isClosestToCursor = isWithinPercentageRange({
|
|
157
|
-
valueToCheck: cursorY,
|
|
158
|
-
baseValue: yValue,
|
|
159
|
-
percentage: percentRangeToCheck
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
isClosestToCursor = isWithinPercentageRange({
|
|
164
|
-
valueToCheck: cursorY,
|
|
165
|
-
baseValue: yValue,
|
|
166
|
-
percentage: percentRangeToCheck
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
if (isClosestToCursor) {
|
|
170
|
-
// shows as bold in tooltip, customize 'emphasis' options in getTimeSeries util
|
|
171
|
-
emphasizedSeriesIndexes.push(seriesIdx);
|
|
172
|
-
// Used to determine which datapoint to apply select styles to.
|
|
173
|
-
// Accounts for cases where lines may be rendered directly on top of eachother.
|
|
174
|
-
const duplicateValuesCount = yValueCounts.get(yValue) ?? 0;
|
|
175
|
-
yValueCounts.set(yValue, duplicateValuesCount + 1);
|
|
176
|
-
if (duplicateValuesCount > 0) {
|
|
177
|
-
duplicateDatapoints.push({
|
|
178
|
-
seriesIndex: seriesIdx,
|
|
179
|
-
dataIndex: datumIdx,
|
|
180
|
-
seriesName: currentSeriesName,
|
|
181
|
-
yValue: yValue
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
// keep track of all bold datapoints in tooltip so that 'select' state only applied to topmost
|
|
185
|
-
emphasizedDatapoints.push({
|
|
186
|
-
seriesIndex: seriesIdx,
|
|
187
|
-
dataIndex: datumIdx,
|
|
188
|
-
seriesName: currentSeriesName,
|
|
189
|
-
yValue: yValue
|
|
190
|
-
});
|
|
191
|
-
} else {
|
|
192
|
-
nonEmphasizedSeriesIndexes.push(seriesIdx);
|
|
193
|
-
// ensure series far away from cursor are not highlighted
|
|
194
|
-
chart.dispatchAction({
|
|
195
|
-
type: 'downplay',
|
|
196
|
-
seriesIndex: seriesIdx
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
const formattedY = (0, _model.formatValue)(yValue, seriesFormat);
|
|
200
|
-
currentNearbySeriesData.push({
|
|
201
|
-
seriesIdx: seriesIdx,
|
|
202
|
-
datumIdx: datumIdx,
|
|
203
|
-
seriesName: currentSeriesName,
|
|
204
|
-
date: closestTimestamp,
|
|
205
|
-
x: xValue,
|
|
206
|
-
y: yValue,
|
|
207
|
-
formattedY: formattedY,
|
|
208
|
-
markerColor: markerColor.toString(),
|
|
209
|
-
isClosestToCursor
|
|
210
|
-
});
|
|
211
|
-
nearbySeriesIndexes.push(seriesIdx);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
278
|
+
const resolvedCursorXPixel = cursorXPixel ?? (0, _utils1.getPixelXFromGrid)(closestTimestamp, chart);
|
|
279
|
+
const candidates = gatherCandidates(data, seriesMapping, closestTimestamp, cursorX, cursorY, resolvedCursorXPixel, cursorPixelY, yBuffer, yBufferPixels, chart);
|
|
280
|
+
const winner = findClosestCandidate(candidates);
|
|
281
|
+
const candidateIndexes = new Set();
|
|
282
|
+
for (const candidate of candidates)candidateIndexes.add(candidate.seriesIdx);
|
|
283
|
+
const nonCandidateSeriesIndexes = [];
|
|
284
|
+
for(let idx = 0; idx < data.length; idx++){
|
|
285
|
+
if (!candidateIndexes.has(idx)) nonCandidateSeriesIndexes.push(idx);
|
|
217
286
|
}
|
|
218
|
-
|
|
219
|
-
return currentNearbySeriesData;
|
|
287
|
+
return processCandidates(candidates, winner, format, seriesFormatMap, chart, nonCandidateSeriesIndexes);
|
|
220
288
|
}
|
|
221
289
|
function legacyCheckforNearbySeries(data, pointInGrid, yBuffer, chart, format) {
|
|
222
290
|
const currentNearbySeriesData = [];
|
|
@@ -334,24 +402,22 @@ function getNearbySeriesData({ mousePos, pinnedPos, data, seriesMapping, chart,
|
|
|
334
402
|
cursorTargetMatchesChart = true;
|
|
335
403
|
}
|
|
336
404
|
if (cursorTargetMatchesChart === false || data === null || chart['_model'] === undefined) return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
337
|
-
// mousemove position undefined when not hovering over chart canvas
|
|
338
405
|
if (mousePos.plotCanvas.x === undefined || mousePos.plotCanvas.y === undefined) return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
339
406
|
const cursorPixelY = mousePos.plotCanvas.y;
|
|
340
|
-
const
|
|
407
|
+
const cursorXPixel = mousePos.plotCanvas.x;
|
|
408
|
+
const pointInGrid = (0, _utils.getPointInGrid)(cursorXPixel, cursorPixelY, chart);
|
|
341
409
|
if (pointInGrid !== null) {
|
|
342
410
|
const chartModel = chart['_model'];
|
|
343
411
|
const yAxisScale = chartModel.getComponent('yAxis').axis.scale;
|
|
344
412
|
const isLogScale = yAxisScale.type === 'log';
|
|
345
413
|
let yInterval = yAxisScale._interval;
|
|
346
|
-
// For
|
|
414
|
+
// For log scales, convert from log-space extent to actual data range and use 1% as the interval.
|
|
347
415
|
if (isLogScale && yAxisScale.base) {
|
|
348
416
|
const logBase = yAxisScale.base;
|
|
349
417
|
const extent = yAxisScale._extent;
|
|
350
|
-
//
|
|
351
|
-
// extent is in log space (e.g., [0, 2] for 10^0 to 10^2)
|
|
418
|
+
// e.g. extent [0, 2] → 10^0..10^2
|
|
352
419
|
const actualMin = logBase ** extent[0];
|
|
353
420
|
const actualMax = logBase ** extent[1];
|
|
354
|
-
// Use a fraction of the actual range as the interval
|
|
355
421
|
yInterval = (actualMax - actualMin) / 100;
|
|
356
422
|
}
|
|
357
423
|
const totalSeries = data.length;
|
|
@@ -360,9 +426,8 @@ function getNearbySeriesData({ mousePos, pinnedPos, data, seriesMapping, chart,
|
|
|
360
426
|
totalSeries,
|
|
361
427
|
showAllSeries
|
|
362
428
|
});
|
|
363
|
-
// Detect if chart has multiple Y-axes by checking if any series uses yAxisIndex > 0
|
|
364
429
|
const hasMultipleYAxes = seriesMapping.some((series)=>series.yAxisIndex !== undefined && series.yAxisIndex > 0);
|
|
365
|
-
return checkforNearbyTimeSeries(data, seriesMapping, pointInGrid, yBuffer, chart, format, seriesFormatMap, hasMultipleYAxes ? cursorPixelY : undefined);
|
|
430
|
+
return checkforNearbyTimeSeries(data, seriesMapping, pointInGrid, yBuffer, chart, format, seriesFormatMap, hasMultipleYAxes ? cursorPixelY : undefined, cursorXPixel);
|
|
366
431
|
}
|
|
367
432
|
// no nearby series found
|
|
368
433
|
return _tooltipmodel.EMPTY_TOOLTIP_DATA;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright The Perses Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
"use strict";
|
|
14
|
+
Object.defineProperty(exports, "__esModule", {
|
|
15
|
+
value: true
|
|
16
|
+
});
|
|
@@ -24,6 +24,21 @@ _export(exports, {
|
|
|
24
24
|
get assembleTransform () {
|
|
25
25
|
return assembleTransform;
|
|
26
26
|
},
|
|
27
|
+
get calculateBarBandwidth () {
|
|
28
|
+
return calculateBarBandwidth;
|
|
29
|
+
},
|
|
30
|
+
get calculateBarSegmentBounds () {
|
|
31
|
+
return calculateBarSegmentBounds;
|
|
32
|
+
},
|
|
33
|
+
get calculateBarYBounds () {
|
|
34
|
+
return calculateBarYBounds;
|
|
35
|
+
},
|
|
36
|
+
get calculateVisualYForSeries () {
|
|
37
|
+
return calculateVisualYForSeries;
|
|
38
|
+
},
|
|
39
|
+
get getPixelXFromGrid () {
|
|
40
|
+
return getPixelXFromGrid;
|
|
41
|
+
},
|
|
27
42
|
get getTooltipStyles () {
|
|
28
43
|
return getTooltipStyles;
|
|
29
44
|
}
|
|
@@ -39,6 +54,9 @@ function assembleTransform(mousePos, pinnedPos, tooltipHeight, tooltipWidth, con
|
|
|
39
54
|
mousePos = pinnedPos;
|
|
40
55
|
}
|
|
41
56
|
if (mousePos.plotCanvas.x === undefined) return undefined;
|
|
57
|
+
// Fall back to max size before the resize observer reports a real measurement.
|
|
58
|
+
const effectiveHeight = tooltipHeight > 0 ? tooltipHeight : _tooltipmodel.TOOLTIP_MAX_HEIGHT;
|
|
59
|
+
const effectiveWidth = tooltipWidth > 0 ? tooltipWidth : _tooltipmodel.TOOLTIP_MAX_WIDTH;
|
|
42
60
|
let x = mousePos.page.x + cursorPaddingX; // Default to right side of the cursor
|
|
43
61
|
let y = mousePos.page.y + cursorPaddingY;
|
|
44
62
|
// If containerElement is defined, adjust coordinates relative to the container
|
|
@@ -48,18 +66,18 @@ function assembleTransform(mousePos, pinnedPos, tooltipHeight, tooltipWidth, con
|
|
|
48
66
|
y = y - containerRect.top + containerElement.scrollTop;
|
|
49
67
|
// Ensure tooltip does not go out of the container's bottom
|
|
50
68
|
const containerBottom = containerRect.top + containerElement.scrollHeight;
|
|
51
|
-
if (y +
|
|
52
|
-
y = Math.max(containerBottom -
|
|
69
|
+
if (y + effectiveHeight > containerBottom) {
|
|
70
|
+
y = Math.max(containerBottom - effectiveHeight - cursorPaddingY, _tooltipmodel.TOOLTIP_PADDING / 2);
|
|
53
71
|
}
|
|
54
72
|
} else {
|
|
55
73
|
// Ensure tooltip does not go out of the screen on the bottom
|
|
56
|
-
if (y +
|
|
57
|
-
y = Math.max(window.innerHeight + window.scrollY -
|
|
74
|
+
if (y + effectiveHeight > window.innerHeight + window.scrollY) {
|
|
75
|
+
y = Math.max(window.innerHeight + window.scrollY - effectiveHeight - cursorPaddingY, _tooltipmodel.TOOLTIP_PADDING / 2);
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
78
|
// Ensure tooltip does not go out of the screen on the right
|
|
61
|
-
if (x +
|
|
62
|
-
x = mousePos.page.x -
|
|
79
|
+
if (x + effectiveWidth > window.innerWidth) {
|
|
80
|
+
x = mousePos.page.x - effectiveWidth - cursorPaddingX; // Move to the left of the cursor
|
|
63
81
|
}
|
|
64
82
|
// Ensure tooltip does not go out of the screen on the left
|
|
65
83
|
if (x < cursorPaddingX) {
|
|
@@ -87,8 +105,9 @@ function getTooltipStyles(theme, pinnedPos, maxHeight) {
|
|
|
87
105
|
fontSize: '11px',
|
|
88
106
|
visibility: 'visible',
|
|
89
107
|
opacity: 1,
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
// Animating transform causes intermediate positions outside the viewport; animate opacity/visibility instead.
|
|
109
|
+
transition: 'opacity 0.1s ease-out, visibility 0.1s ease-out',
|
|
110
|
+
// Pinned tooltip should not float above the drawer/sticky header.
|
|
92
111
|
zIndex: pinnedPos !== null ? 'auto' : theme.zIndex.tooltip,
|
|
93
112
|
overflow: 'hidden',
|
|
94
113
|
'&:hover': {
|
|
@@ -96,3 +115,85 @@ function getTooltipStyles(theme, pinnedPos, maxHeight) {
|
|
|
96
115
|
}
|
|
97
116
|
};
|
|
98
117
|
}
|
|
118
|
+
function getPixelXFromGrid(timestamp, chart) {
|
|
119
|
+
try {
|
|
120
|
+
const pixelCoords = chart.convertToPixel('grid', [
|
|
121
|
+
timestamp,
|
|
122
|
+
0
|
|
123
|
+
]);
|
|
124
|
+
return pixelCoords?.[0] ?? null;
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function calculateVisualYForSeries(seriesIdx, yValue, seriesMapping, stackTotals) {
|
|
130
|
+
const currentSeries = seriesMapping[seriesIdx];
|
|
131
|
+
if (!currentSeries) return yValue;
|
|
132
|
+
const stackId = currentSeries.stack;
|
|
133
|
+
if (!stackId) {
|
|
134
|
+
return yValue;
|
|
135
|
+
}
|
|
136
|
+
const stackIdStr = stackId.toString();
|
|
137
|
+
const currentTotal = stackTotals.get(stackIdStr) ?? 0;
|
|
138
|
+
const newTotal = currentTotal + yValue;
|
|
139
|
+
stackTotals.set(stackIdStr, newTotal);
|
|
140
|
+
return newTotal;
|
|
141
|
+
}
|
|
142
|
+
function calculateBarBandwidth(timestamp, sortedTimestamps, chart) {
|
|
143
|
+
const currentIdx = sortedTimestamps.indexOf(timestamp);
|
|
144
|
+
if (currentIdx === -1) {
|
|
145
|
+
return 20;
|
|
146
|
+
}
|
|
147
|
+
const prevTimestamp = currentIdx > 0 ? sortedTimestamps[currentIdx - 1] ?? null : null;
|
|
148
|
+
const nextTimestamp = currentIdx < sortedTimestamps.length - 1 ? sortedTimestamps[currentIdx + 1] ?? null : null;
|
|
149
|
+
const currentPixelX = getPixelXFromGrid(timestamp, chart);
|
|
150
|
+
if (currentPixelX === null) return 20;
|
|
151
|
+
let leftBound;
|
|
152
|
+
let rightBound;
|
|
153
|
+
if (prevTimestamp !== null && nextTimestamp !== null) {
|
|
154
|
+
const prevPixelX = getPixelXFromGrid(prevTimestamp, chart) ?? currentPixelX;
|
|
155
|
+
const nextPixelX = getPixelXFromGrid(nextTimestamp, chart) ?? currentPixelX;
|
|
156
|
+
leftBound = (currentPixelX + prevPixelX) / 2;
|
|
157
|
+
rightBound = (currentPixelX + nextPixelX) / 2;
|
|
158
|
+
} else if (prevTimestamp !== null) {
|
|
159
|
+
const prevPixelX = getPixelXFromGrid(prevTimestamp, chart) ?? currentPixelX;
|
|
160
|
+
leftBound = (currentPixelX + prevPixelX) / 2;
|
|
161
|
+
rightBound = currentPixelX + (currentPixelX - leftBound);
|
|
162
|
+
} else if (nextTimestamp !== null) {
|
|
163
|
+
const nextPixelX = getPixelXFromGrid(nextTimestamp, chart) ?? currentPixelX;
|
|
164
|
+
rightBound = (currentPixelX + nextPixelX) / 2;
|
|
165
|
+
leftBound = currentPixelX - (rightBound - currentPixelX);
|
|
166
|
+
} else {
|
|
167
|
+
return 20;
|
|
168
|
+
}
|
|
169
|
+
return Math.max(1, rightBound - leftBound);
|
|
170
|
+
}
|
|
171
|
+
function calculateBarSegmentBounds(barRelativeIdx, bandwidth, centerPixelX, barCount) {
|
|
172
|
+
const count = Math.max(1, barCount);
|
|
173
|
+
const segmentWidth = bandwidth / count;
|
|
174
|
+
const segmentLeft = centerPixelX - bandwidth / 2 + barRelativeIdx * segmentWidth;
|
|
175
|
+
return {
|
|
176
|
+
left: segmentLeft,
|
|
177
|
+
right: segmentLeft + segmentWidth
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function calculateBarYBounds(visualYBottom, visualYTop, chart) {
|
|
181
|
+
try {
|
|
182
|
+
const bottomPixel = chart.convertToPixel('grid', [
|
|
183
|
+
0,
|
|
184
|
+
visualYBottom
|
|
185
|
+
]);
|
|
186
|
+
const topPixel = chart.convertToPixel('grid', [
|
|
187
|
+
0,
|
|
188
|
+
visualYTop
|
|
189
|
+
]);
|
|
190
|
+
if (!bottomPixel || !topPixel || bottomPixel[1] === undefined || topPixel[1] === undefined) return null;
|
|
191
|
+
// Y increases downward in pixels; min/max normalizes the mapping for negative bar values.
|
|
192
|
+
return {
|
|
193
|
+
top: Math.min(topPixel[1], bottomPixel[1]),
|
|
194
|
+
bottom: Math.max(topPixel[1], bottomPixel[1])
|
|
195
|
+
};
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -108,12 +108,20 @@ function batchDispatchNearbySeriesActions(chart, nearbySeriesIndexes, emphasized
|
|
|
108
108
|
escapeConnect: true
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
|
-
//
|
|
112
|
-
//
|
|
111
|
+
// Blanket downplay clears axis-triggered emphasis (enlarged "big point" markers) before
|
|
112
|
+
// re-applying emphasis to only the winner series.
|
|
113
|
+
// https://echarts.apache.org/en/api.html#action.downplay
|
|
113
114
|
chart.dispatchAction({
|
|
114
|
-
type: 'downplay'
|
|
115
|
-
seriesIndex: nonEmphasizedSeriesIndexes
|
|
115
|
+
type: 'downplay'
|
|
116
116
|
});
|
|
117
|
+
// Clears emphasis state of all lines that are not emphasized.
|
|
118
|
+
// Emphasized is a subset of just the nearby series that are closest to cursor.
|
|
119
|
+
if (nonEmphasizedSeriesIndexes.length > 0) {
|
|
120
|
+
chart.dispatchAction({
|
|
121
|
+
type: 'downplay',
|
|
122
|
+
seriesIndex: nonEmphasizedSeriesIndexes
|
|
123
|
+
});
|
|
124
|
+
}
|
|
117
125
|
// https://echarts.apache.org/en/api.html#action.highlight
|
|
118
126
|
if (emphasizedSeriesIndexes.length > 0) {
|
|
119
127
|
// Fadeout opacity of all series not closest to cursor.
|
|
@@ -29,6 +29,7 @@ _export(exports, {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
const _variableinterpolation = require("./variable-interpolation");
|
|
32
|
+
const _regexp = require("./regexp");
|
|
32
33
|
function expandQueryParamValue(value, variableState) {
|
|
33
34
|
// If value is an array, process each element
|
|
34
35
|
if (Array.isArray(value)) {
|
|
@@ -40,13 +41,30 @@ function expandQueryParamValue(value, variableState) {
|
|
|
40
41
|
// Find the first multi-value variable that should expand to repeated keys
|
|
41
42
|
for (const [varName, format] of variablesMap){
|
|
42
43
|
const varState = variableState[varName];
|
|
43
|
-
if (!varState
|
|
44
|
+
if (!varState) continue;
|
|
45
|
+
const options = varState.options ?? [];
|
|
46
|
+
const allOptionValues = options.map((o)=>o.value);
|
|
47
|
+
const isArray = Array.isArray(varState.value);
|
|
48
|
+
const isCustomAllValue = varState.customAllValue !== undefined;
|
|
49
|
+
let expandValues;
|
|
50
|
+
if (isArray) {
|
|
51
|
+
expandValues = varState.value;
|
|
52
|
+
} else if (isCustomAllValue) {
|
|
53
|
+
try {
|
|
54
|
+
const regex = (0, _regexp.createRegexFromString)(varState.customAllValue);
|
|
55
|
+
const matched = options.filter((o)=>regex.test(o.value)).map((o)=>o.value);
|
|
56
|
+
expandValues = matched.length > 0 ? matched : allOptionValues;
|
|
57
|
+
} catch {
|
|
58
|
+
expandValues = allOptionValues;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!expandValues) continue;
|
|
44
62
|
// If format is queryparam or not specified (default for query params), expand to array
|
|
45
63
|
if (!format || format === _variableinterpolation.InterpolationFormat.QUERYPARAM) {
|
|
46
64
|
// Build syntax patterns for this variable reference
|
|
47
65
|
const simpleSyntax = '$' + varName;
|
|
48
66
|
const bracketSyntax = format ? '${' + varName + ':' + format + '}' : '${' + varName + '}';
|
|
49
|
-
return
|
|
67
|
+
return expandValues.map((singleValue)=>{
|
|
50
68
|
let text = valueString;
|
|
51
69
|
// Replace the variable reference with the raw value directly.
|
|
52
70
|
// We cannot use replaceVariables here because it would apply QUERYPARAM format
|