@deck.gl-community/infovis-layers 9.3.5 → 9.3.7
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/index.cjs +2998 -114
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/layers/animation-layer/animation-layer.d.ts +30 -0
- package/dist/layers/animation-layer/animation-layer.d.ts.map +1 -0
- package/dist/layers/animation-layer/animation-layer.js +53 -0
- package/dist/layers/animation-layer/animation-layer.js.map +1 -0
- package/dist/layers/animation-layer/animation.d.ts +86 -0
- package/dist/layers/animation-layer/animation.d.ts.map +1 -0
- package/dist/layers/animation-layer/animation.js +214 -0
- package/dist/layers/animation-layer/animation.js.map +1 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts +3 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.js +41 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.js.map +1 -0
- package/dist/layers/block-layer/block-layer-uniforms.d.ts +27 -0
- package/dist/layers/block-layer/block-layer-uniforms.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-uniforms.js +26 -0
- package/dist/layers/block-layer/block-layer-uniforms.js.map +1 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts +3 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.js +67 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.js.map +1 -0
- package/dist/layers/block-layer/block-layer.d.ts +71 -0
- package/dist/layers/block-layer/block-layer.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer.js +108 -0
- package/dist/layers/block-layer/block-layer.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.d.ts +148 -0
- package/dist/layers/fast-text-layer/fast-text-layer.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.js +862 -0
- package/dist/layers/fast-text-layer/fast-text-layer.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts +2 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.js +300 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layout.d.ts +179 -0
- package/dist/layers/fast-text-layer/fast-text-layout.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layout.js +665 -0
- package/dist/layers/fast-text-layer/fast-text-layout.js.map +1 -0
- package/dist/layers/fast-text-layer/font-atlas.d.ts +98 -0
- package/dist/layers/fast-text-layer/font-atlas.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/font-atlas.js +337 -0
- package/dist/layers/fast-text-layer/font-atlas.js.map +1 -0
- package/dist/layers/fast-text-layer/index.d.ts +4 -0
- package/dist/layers/fast-text-layer/index.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/index.js +4 -0
- package/dist/layers/fast-text-layer/index.js.map +1 -0
- package/dist/layers/time-delta-layer.d.ts +22 -60
- package/dist/layers/time-delta-layer.d.ts.map +1 -1
- package/dist/layers/time-delta-layer.js +23 -45
- package/dist/layers/time-delta-layer.js.map +1 -1
- package/dist/synchronized-views/synchronized-views.js +1 -1
- package/dist/synchronized-views/synchronized-views.js.map +1 -1
- package/dist/utils/format-utils.d.ts +27 -3
- package/dist/utils/format-utils.d.ts.map +1 -1
- package/dist/utils/format-utils.js +72 -40
- package/dist/utils/format-utils.js.map +1 -1
- package/dist/utils/utf8-string-view.d.ts +53 -0
- package/dist/utils/utf8-string-view.d.ts.map +1 -0
- package/dist/utils/utf8-string-view.js +169 -0
- package/dist/utils/utf8-string-view.js.map +1 -0
- package/dist/utils/utf8-string-view.test.d.ts +2 -0
- package/dist/utils/utf8-string-view.test.d.ts.map +1 -0
- package/dist/utils/utf8-string-view.test.js +88 -0
- package/dist/utils/utf8-string-view.test.js.map +1 -0
- package/dist/views/bounds-utils.d.ts +23 -0
- package/dist/views/bounds-utils.d.ts.map +1 -0
- package/dist/views/bounds-utils.js +65 -0
- package/dist/views/bounds-utils.js.map +1 -0
- package/dist/views/layer-bounds-filter.d.ts +48 -0
- package/dist/views/layer-bounds-filter.d.ts.map +1 -0
- package/dist/views/layer-bounds-filter.js +99 -0
- package/dist/views/layer-bounds-filter.js.map +1 -0
- package/dist/views/orthographic-utils.d.ts +30 -2
- package/dist/views/orthographic-utils.d.ts.map +1 -1
- package/dist/views/orthographic-utils.js +33 -14
- package/dist/views/orthographic-utils.js.map +1 -1
- package/dist/views/view-layout/build-views-from-view-layout.d.ts +37 -0
- package/dist/views/view-layout/build-views-from-view-layout.d.ts.map +1 -0
- package/dist/views/view-layout/build-views-from-view-layout.js +413 -0
- package/dist/views/view-layout/build-views-from-view-layout.js.map +1 -0
- package/dist/views/view-layout/index.d.ts +3 -0
- package/dist/views/view-layout/index.d.ts.map +1 -0
- package/dist/views/view-layout/index.js +3 -0
- package/dist/views/view-layout/index.js.map +1 -0
- package/dist/views/view-layout/view-layout-item.d.ts +74 -0
- package/dist/views/view-layout/view-layout-item.d.ts.map +1 -0
- package/dist/views/view-layout/view-layout-item.js +48 -0
- package/dist/views/view-layout/view-layout-item.js.map +1 -0
- package/dist/views/view-layout/view-layout.test.d.ts +2 -0
- package/dist/views/view-layout/view-layout.test.d.ts.map +1 -0
- package/dist/views/view-layout/view-layout.test.js +112 -0
- package/dist/views/view-layout/view-layout.test.js.map +1 -0
- package/dist/views/viewport-bounds-utils.d.ts +55 -0
- package/dist/views/viewport-bounds-utils.d.ts.map +1 -0
- package/dist/views/viewport-bounds-utils.js +70 -0
- package/dist/views/viewport-bounds-utils.js.map +1 -0
- package/package.json +11 -4
- package/src/index.ts +91 -2
- package/src/layers/animation-layer/animation-layer.ts +92 -0
- package/src/layers/animation-layer/animation.ts +328 -0
- package/src/layers/block-layer/block-layer-fragment.glsl.ts +41 -0
- package/src/layers/block-layer/block-layer-uniforms.ts +43 -0
- package/src/layers/block-layer/block-layer-vertex.glsl.ts +67 -0
- package/src/layers/block-layer/block-layer.ts +199 -0
- package/src/layers/fast-text-layer/fast-text-layer.test.ts +359 -0
- package/src/layers/fast-text-layer/fast-text-layer.ts +1208 -0
- package/src/layers/fast-text-layer/fast-text-layout.ts +1124 -0
- package/src/layers/fast-text-layer/font-atlas.ts +574 -0
- package/src/layers/fast-text-layer/index.ts +37 -0
- package/src/layers/time-delta-layer.ts +47 -62
- package/src/synchronized-views/synchronized-views.ts +1 -1
- package/src/utils/format-utils.ts +90 -40
- package/src/utils/utf8-string-view.test.ts +119 -0
- package/src/utils/utf8-string-view.ts +273 -0
- package/src/views/bounds-utils.ts +88 -0
- package/src/views/layer-bounds-filter.ts +160 -0
- package/src/views/orthographic-utils.ts +65 -18
- package/src/views/view-layout/build-views-from-view-layout.ts +547 -0
- package/src/views/view-layout/index.ts +18 -0
- package/src/views/view-layout/view-layout-item.ts +125 -0
- package/src/views/view-layout/view-layout.test.ts +129 -0
- package/src/views/viewport-bounds-utils.ts +104 -0
|
@@ -2,36 +2,47 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {CompositeLayer, type LayerProps} from '@deck.gl/core';
|
|
6
|
-
import {LineLayer, TextLayer} from '@deck.gl/layers';
|
|
5
|
+
import {CompositeLayer, type DefaultProps, type Layer, type LayerProps} from '@deck.gl/core';
|
|
6
|
+
import {LineLayer, TextLayer, type TextLayerProps} from '@deck.gl/layers';
|
|
7
7
|
|
|
8
8
|
// import {PathStyleExtension} from '@deck.gl/extensions';
|
|
9
9
|
|
|
10
10
|
import {formatTimeMs} from '../utils/format-utils';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** Properties supported by {@link TimeDeltaLayer}. */
|
|
13
|
+
export type TimeDeltaLayerProps = LayerProps & _TimeDeltaLayerProps;
|
|
14
|
+
|
|
15
|
+
type _TimeDeltaLayerProps = Pick<TextLayerProps, 'fontFamily' | 'fontSettings' | 'fontWeight'> & {
|
|
16
|
+
/** Legacy time label unit retained for compatibility. @defaultValue 'timestamp' */
|
|
13
17
|
unit: 'timestamp' | 'milliseconds';
|
|
18
|
+
/** Minimum trace time represented by the surrounding view. @defaultValue 0 */
|
|
14
19
|
minTimeMs: number;
|
|
20
|
+
/** Maximum trace time represented by the surrounding view. @defaultValue 100 */
|
|
15
21
|
maxTimeMs: number;
|
|
16
|
-
/** Start time in milliseconds since epoch */
|
|
22
|
+
/** Start time in milliseconds since epoch. @defaultValue 0 */
|
|
17
23
|
startTimeMs: number;
|
|
18
|
-
/** End time in milliseconds since epoch */
|
|
24
|
+
/** End time in milliseconds since epoch. @defaultValue 100 */
|
|
19
25
|
endTimeMs: number;
|
|
20
|
-
/**
|
|
26
|
+
/** Y coordinate for the header interval guide. @defaultValue 0 */
|
|
21
27
|
y?: number;
|
|
28
|
+
/** Whether to render the compact header label instead of full-height guide lines. @defaultValue false */
|
|
22
29
|
header: boolean;
|
|
23
30
|
|
|
24
|
-
/**
|
|
31
|
+
/** Header label text size. @defaultValue 12 */
|
|
32
|
+
fontSize?: number;
|
|
33
|
+
|
|
34
|
+
/** RGBA color for interval guides and labels. @defaultValue [0, 0, 0, 255] */
|
|
25
35
|
color?: [number, number, number, number];
|
|
26
|
-
/** Minimum Y
|
|
36
|
+
/** Minimum Y coordinate for full-height guide lines. @defaultValue -1e6 */
|
|
27
37
|
yMin?: number;
|
|
28
|
-
/** Maximum Y
|
|
38
|
+
/** Maximum Y coordinate for full-height guide lines. @defaultValue 1e6 */
|
|
29
39
|
yMax?: number;
|
|
30
40
|
};
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
/** Renders a selected time interval as header or viewport guide lines. */
|
|
43
|
+
export class TimeDeltaLayer extends CompositeLayer<Required<_TimeDeltaLayerProps>> {
|
|
33
44
|
static override layerName = 'TimeDeltaLayer';
|
|
34
|
-
static override defaultProps:
|
|
45
|
+
static override defaultProps: DefaultProps<_TimeDeltaLayerProps> = {
|
|
35
46
|
header: false,
|
|
36
47
|
minTimeMs: 0,
|
|
37
48
|
maxTimeMs: 100,
|
|
@@ -41,16 +52,16 @@ export class TimeDeltaLayer extends CompositeLayer<TimeDeltaLayerProps> {
|
|
|
41
52
|
color: [0, 0, 0, 255],
|
|
42
53
|
unit: 'timestamp',
|
|
43
54
|
yMin: -1e6, // Should cover full viewport height in most cases
|
|
44
|
-
yMax: 1e6 // Should cover full viewport height in most cases
|
|
55
|
+
yMax: 1e6, // Should cover full viewport height in most cases
|
|
56
|
+
fontSize: 12,
|
|
57
|
+
fontFamily: TextLayer.defaultProps.fontFamily,
|
|
58
|
+
fontSettings: TextLayer.defaultProps.fontSettings,
|
|
59
|
+
fontWeight: TextLayer.defaultProps.fontWeight
|
|
45
60
|
};
|
|
46
61
|
|
|
47
|
-
override renderLayers() {
|
|
62
|
+
override renderLayers(): Layer[] {
|
|
48
63
|
const {startTimeMs, endTimeMs, color = [0, 0, 0, 255], yMin, yMax} = this.props;
|
|
49
64
|
|
|
50
|
-
const timeDeltaPosition = [(startTimeMs + endTimeMs) / 2, 10];
|
|
51
|
-
const timeDeltaMs = Math.abs(endTimeMs - startTimeMs);
|
|
52
|
-
const timeDeltaLabel = formatTimeMs(timeDeltaMs, false);
|
|
53
|
-
|
|
54
65
|
if (!this.props.header) {
|
|
55
66
|
const timeLines = [
|
|
56
67
|
{
|
|
@@ -76,50 +87,20 @@ export class TimeDeltaLayer extends CompositeLayer<TimeDeltaLayerProps> {
|
|
|
76
87
|
];
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// getColor: color,
|
|
86
|
-
// getWidth: 1,
|
|
87
|
-
// }),
|
|
88
|
-
|
|
89
|
-
// TODO - triggers broader root-level file updates
|
|
90
|
-
// new PathLayer({
|
|
91
|
-
// id: 'dotted-path',
|
|
92
|
-
// data: route,
|
|
93
|
-
// getPath: d => d.path,
|
|
94
|
-
// getWidth: 4,
|
|
95
|
-
// getColor: [255, 0, 0],
|
|
96
|
-
|
|
97
|
-
// // Enable rounded caps on each dash
|
|
98
|
-
// rounded: true,
|
|
99
|
-
|
|
100
|
-
// // Add the dash extension
|
|
101
|
-
// extensions: [
|
|
102
|
-
// new PathStyleExtension({
|
|
103
|
-
// dash: true,
|
|
104
|
-
// // highPrecisionDash: true, // uncomment for finer control at low zoom
|
|
105
|
-
// capRounded: true // draw dash ends as semicircles
|
|
106
|
-
// })
|
|
107
|
-
// ],
|
|
108
|
-
|
|
109
|
-
// // [dashLength, gapLength] in pixels
|
|
110
|
-
// // small dash + equal or larger gap = dotted effect
|
|
111
|
-
// getDashArray: () => [2, 6]
|
|
112
|
-
// })
|
|
113
|
-
// ]
|
|
114
|
-
const HEADER_Y = 12;
|
|
90
|
+
const {y, fontSize, fontFamily, fontSettings, fontWeight} = this.props;
|
|
91
|
+
|
|
92
|
+
const timeDeltaPosition = [(startTimeMs + endTimeMs) / 2, y - 10];
|
|
93
|
+
const timeDeltaMs = Math.abs(endTimeMs - startTimeMs);
|
|
94
|
+
const timeDeltaLabel = formatTimeMs(timeDeltaMs, {space: false});
|
|
95
|
+
|
|
115
96
|
const timeLines = [
|
|
116
97
|
{
|
|
117
|
-
sourcePosition: [startTimeMs,
|
|
118
|
-
targetPosition: [startTimeMs,
|
|
98
|
+
sourcePosition: [startTimeMs, y],
|
|
99
|
+
targetPosition: [startTimeMs, y - 7]
|
|
119
100
|
},
|
|
120
101
|
{
|
|
121
|
-
sourcePosition: [endTimeMs,
|
|
122
|
-
targetPosition: [endTimeMs,
|
|
102
|
+
sourcePosition: [endTimeMs, y],
|
|
103
|
+
targetPosition: [endTimeMs, y - 7]
|
|
123
104
|
}
|
|
124
105
|
];
|
|
125
106
|
|
|
@@ -140,8 +121,8 @@ export class TimeDeltaLayer extends CompositeLayer<TimeDeltaLayerProps> {
|
|
|
140
121
|
id: 'header-time-delta-dotted-line',
|
|
141
122
|
data: [
|
|
142
123
|
{
|
|
143
|
-
sourcePosition: [startTimeMs,
|
|
144
|
-
targetPosition: [endTimeMs,
|
|
124
|
+
sourcePosition: [startTimeMs, y - 7],
|
|
125
|
+
targetPosition: [endTimeMs, y - 7]
|
|
145
126
|
}
|
|
146
127
|
],
|
|
147
128
|
getSourcePosition: d => d.sourcePosition,
|
|
@@ -157,12 +138,16 @@ export class TimeDeltaLayer extends CompositeLayer<TimeDeltaLayerProps> {
|
|
|
157
138
|
data: [{position: timeDeltaPosition, text: timeDeltaLabel}],
|
|
158
139
|
getPosition: d => d.position,
|
|
159
140
|
getText: d => d.text,
|
|
160
|
-
|
|
141
|
+
characterSet: '-0123456789.dhmsµ',
|
|
142
|
+
getSize: fontSize,
|
|
143
|
+
fontFamily,
|
|
144
|
+
fontSettings,
|
|
145
|
+
fontWeight,
|
|
161
146
|
getColor: color,
|
|
162
147
|
getTextAnchor: 'middle',
|
|
163
|
-
getAlignmentBaseline: '
|
|
148
|
+
getAlignmentBaseline: 'center',
|
|
164
149
|
background: true,
|
|
165
|
-
getBackgroundColor: [255, 255, 255, 255],
|
|
150
|
+
getBackgroundColor: [255 - color[0], 255 - color[1], 255 - color[2], 255],
|
|
166
151
|
backgroundPadding: [4, 2] // Horizontal and vertical padding
|
|
167
152
|
})
|
|
168
153
|
];
|
|
@@ -19,7 +19,7 @@ export const SYNCHRONIZED_VIEWS = [
|
|
|
19
19
|
flipY: false,
|
|
20
20
|
clear: true, // [1, 1, 1, 1],
|
|
21
21
|
x: LEGEND_VIEW_WIDTH,
|
|
22
|
-
width: `calc(100% - ${LEGEND_VIEW_WIDTH}px`,
|
|
22
|
+
width: `calc(100% - ${LEGEND_VIEW_WIDTH}px)`,
|
|
23
23
|
y: HEADER_VIEW_HEIGHT,
|
|
24
24
|
height: `calc(100% - ${HEADER_VIEW_HEIGHT}px)`,
|
|
25
25
|
controller: {
|
|
@@ -3,78 +3,128 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @param us Time in microseconds
|
|
6
|
+
* Options for formatting millisecond duration labels.
|
|
8
7
|
*/
|
|
9
|
-
export
|
|
8
|
+
export type FormatTimeMsOptions = {
|
|
9
|
+
/** Whether to include a space between the value and unit. @defaultValue true */
|
|
10
|
+
space?: boolean;
|
|
11
|
+
/** Number of significant digits used for fractional values. @defaultValue 5 */
|
|
12
|
+
roundDigits?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert a time in milliseconds to a human-readable string.
|
|
17
|
+
* @param timeMs Time in milliseconds.
|
|
18
|
+
* @param options Formatting options, or the legacy `space` boolean argument.
|
|
19
|
+
* @returns Human-readable duration label.
|
|
20
|
+
*/
|
|
21
|
+
export function formatTimeMs(timeMs: number, options: boolean | FormatTimeMsOptions = {}): string {
|
|
22
|
+
const normalizedOptions: FormatTimeMsOptions =
|
|
23
|
+
typeof options === 'boolean' ? {space: options} : options;
|
|
24
|
+
const {space = true, roundDigits = 5} = normalizedOptions;
|
|
10
25
|
const sep = space ? ' ' : '';
|
|
11
26
|
const us = timeMs * 1000;
|
|
12
27
|
if (us === 0) {
|
|
13
28
|
return '0s';
|
|
14
29
|
}
|
|
15
30
|
if (Math.abs(us) < 1000) {
|
|
16
|
-
return `${floatToStr(us)}${sep}µs`;
|
|
31
|
+
return `${floatToStr(us, roundDigits)}${sep}µs`;
|
|
17
32
|
}
|
|
18
33
|
const ms = us / 1000;
|
|
19
34
|
if (Math.abs(ms) < 1000) {
|
|
20
|
-
return `${floatToStr(ms)}${sep}
|
|
35
|
+
return `${floatToStr(ms, roundDigits)}${sep}ms`;
|
|
21
36
|
}
|
|
22
37
|
const s = ms / 1000;
|
|
23
38
|
if (Math.abs(s) < 60) {
|
|
24
|
-
return `${floatToStr(s)}${sep}
|
|
39
|
+
return `${floatToStr(s, roundDigits)}${sep}s`;
|
|
25
40
|
}
|
|
26
41
|
const m = s / 60;
|
|
27
42
|
if (Math.abs(m) < 60) {
|
|
28
|
-
return
|
|
43
|
+
return formatMinuteDuration(s);
|
|
29
44
|
}
|
|
30
45
|
const h = m / 60;
|
|
31
46
|
if (Math.abs(h) < 24) {
|
|
32
|
-
return
|
|
47
|
+
return formatHourDuration(s);
|
|
33
48
|
}
|
|
34
|
-
|
|
35
|
-
return `${floatToStr(d)}${sep} days`;
|
|
49
|
+
return formatDayDuration(s);
|
|
36
50
|
}
|
|
37
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Convert a millisecond time range to a human-readable string.
|
|
54
|
+
* @param startMs Range start in milliseconds.
|
|
55
|
+
* @param endMs Range end in milliseconds.
|
|
56
|
+
* @returns Human-readable start and end labels joined by a separator.
|
|
57
|
+
*/
|
|
38
58
|
export function formatTimeRangeMs(startMs: number, endMs: number): string {
|
|
39
59
|
return `${formatTimeMs(startMs)} - ${formatTimeMs(endMs)}`;
|
|
40
60
|
}
|
|
41
61
|
|
|
42
62
|
/**
|
|
43
|
-
* Convert a float to a string
|
|
63
|
+
* Convert a float to a string with a specified number of significant digits.
|
|
64
|
+
* @param f The float to convert.
|
|
65
|
+
* @param roundDigits The number of significant digits to round to (default is 5).
|
|
66
|
+
* @returns The float as a string, rounded to the specified precision.
|
|
44
67
|
*/
|
|
45
|
-
function floatToStr(f: number, roundDigits: number = 5): string {
|
|
46
|
-
if (Number.isInteger(f)) {
|
|
68
|
+
export function floatToStr(f: number, roundDigits: number = 5): string {
|
|
69
|
+
if (roundDigits < 1 || Number.isInteger(f)) {
|
|
47
70
|
return f.toString();
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
// Round the float to the specified precision
|
|
74
|
+
const precision = f.toPrecision(roundDigits);
|
|
75
|
+
return parseFloat(precision).toString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format a duration between one minute and one hour with whole seconds.
|
|
80
|
+
* @param seconds Duration in seconds.
|
|
81
|
+
* @returns A compact minutes-and-seconds duration label.
|
|
82
|
+
*/
|
|
83
|
+
function formatMinuteDuration(seconds: number): string {
|
|
84
|
+
const sign = seconds < 0 ? '-' : '';
|
|
85
|
+
const totalSeconds = Math.floor(Math.abs(seconds));
|
|
86
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
87
|
+
const remainingSeconds = totalSeconds % 60;
|
|
88
|
+
|
|
89
|
+
if (remainingSeconds === 0) {
|
|
90
|
+
return `${sign}${minutes}m`;
|
|
55
91
|
}
|
|
56
92
|
|
|
57
|
-
return
|
|
93
|
+
return `${sign}${minutes}m${remainingSeconds}s`;
|
|
58
94
|
}
|
|
59
95
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Format a duration between one hour and one day with whole minutes.
|
|
98
|
+
* @param seconds Duration in seconds.
|
|
99
|
+
* @returns A compact hours-and-minutes duration label.
|
|
100
|
+
*/
|
|
101
|
+
function formatHourDuration(seconds: number): string {
|
|
102
|
+
const sign = seconds < 0 ? '-' : '';
|
|
103
|
+
const totalMinutes = Math.floor(Math.abs(seconds) / 60);
|
|
104
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
105
|
+
const remainingMinutes = totalMinutes % 60;
|
|
106
|
+
|
|
107
|
+
if (remainingMinutes === 0) {
|
|
108
|
+
return `${sign}${hours}h`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return `${sign}${hours}h${remainingMinutes}m`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Format a duration of at least one day with whole hours.
|
|
116
|
+
* @param seconds Duration in seconds.
|
|
117
|
+
* @returns A compact days-and-hours duration label.
|
|
118
|
+
*/
|
|
119
|
+
function formatDayDuration(seconds: number): string {
|
|
120
|
+
const sign = seconds < 0 ? '-' : '';
|
|
121
|
+
const totalHours = Math.floor(Math.abs(seconds) / 3600);
|
|
122
|
+
const days = Math.floor(totalHours / 24);
|
|
123
|
+
const remainingHours = totalHours % 24;
|
|
124
|
+
|
|
125
|
+
if (remainingHours === 0) {
|
|
126
|
+
return `${sign}${days}d`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return `${sign}${days}d${remainingHours}h`;
|
|
130
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as arrow from 'apache-arrow';
|
|
2
|
+
import {describe, expect, it} from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
arrowFindUtf8,
|
|
6
|
+
getArrowUtf8ColumnSource,
|
|
7
|
+
getArrowUtf8RowView,
|
|
8
|
+
getUtf8ColumnSourceRowView,
|
|
9
|
+
makeUtf8StringView
|
|
10
|
+
} from './utf8-string-view';
|
|
11
|
+
|
|
12
|
+
describe('utf8-string-view', () => {
|
|
13
|
+
it('finds matching Utf8 rows without materializing a lookup map', () => {
|
|
14
|
+
const column = arrow.vectorFromArray(['alpha', null, '6149800612493239450'], new arrow.Utf8());
|
|
15
|
+
|
|
16
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('alpha'))).toBe(0);
|
|
17
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('6149800612493239450'))).toBe(2);
|
|
18
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('alpha'), 1)).toBe(-1);
|
|
19
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('missing'))).toBe(-1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('finds exact UTF-8 strings in Arrow columns', () => {
|
|
23
|
+
const column = buildArrowUtf8Vector([
|
|
24
|
+
'span-a',
|
|
25
|
+
'6149800612493239450',
|
|
26
|
+
'span-b',
|
|
27
|
+
'6149800612493239450'
|
|
28
|
+
]);
|
|
29
|
+
const view = makeUtf8StringView('6149800612493239450');
|
|
30
|
+
|
|
31
|
+
expect(arrowFindUtf8(column, view)).toBe(1);
|
|
32
|
+
expect(arrowFindUtf8(column, view, 2)).toBe(3);
|
|
33
|
+
expect(arrowFindUtf8(column, view, 4)).toBe(-1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('falls back safely for nullable Arrow string columns', () => {
|
|
37
|
+
const column = arrow.vectorFromArray(['span-a', null, 'span-c'], new arrow.Utf8());
|
|
38
|
+
|
|
39
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('span-c'))).toBe(2);
|
|
40
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('missing'))).toBe(-1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('fills a reusable row view for direct Utf8 values', () => {
|
|
44
|
+
const column = buildArrowUtf8Vector(['alpha', '', 'gamma']);
|
|
45
|
+
const out = makeEmptyUtf8View();
|
|
46
|
+
|
|
47
|
+
expect(getArrowUtf8RowView(column, 0, out)).toBe(true);
|
|
48
|
+
expect(readUtf8ViewBytes(out)).toEqual([97, 108, 112, 104, 97]);
|
|
49
|
+
expect(getArrowUtf8RowView(column, 1, out)).toBe(true);
|
|
50
|
+
expect(readUtf8ViewBytes(out)).toEqual([]);
|
|
51
|
+
expect(getArrowUtf8RowView(column, 3, out)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('fills a reusable row view for nullable Utf8 values', () => {
|
|
55
|
+
const column = arrow.vectorFromArray(['span-a', null, 'span-c'], new arrow.Utf8());
|
|
56
|
+
const out = makeEmptyUtf8View();
|
|
57
|
+
|
|
58
|
+
expect(getArrowUtf8RowView(column, 1, out)).toBe(false);
|
|
59
|
+
expect(getArrowUtf8RowView(column, 2, out)).toBe(true);
|
|
60
|
+
expect(readUtf8ViewBytes(out)).toEqual([115, 112, 97, 110, 45, 99]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('fills a reusable row view for sliced Utf8 values', () => {
|
|
64
|
+
const column = buildArrowUtf8Vector(['skip', 'first', 'second']).slice(
|
|
65
|
+
1,
|
|
66
|
+
3
|
|
67
|
+
) as arrow.Vector<arrow.Utf8>;
|
|
68
|
+
const out = makeEmptyUtf8View();
|
|
69
|
+
|
|
70
|
+
expect(getArrowUtf8RowView(column, 0, out)).toBe(true);
|
|
71
|
+
expect(readUtf8ViewBytes(out)).toEqual([102, 105, 114, 115, 116]);
|
|
72
|
+
expect(getArrowUtf8RowView(column, 1, out)).toBe(true);
|
|
73
|
+
expect(readUtf8ViewBytes(out)).toEqual([115, 101, 99, 111, 110, 100]);
|
|
74
|
+
expect(arrowFindUtf8(column, makeUtf8StringView('second'))).toBe(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('fills normalized column source row views for sliced Utf8 values', () => {
|
|
78
|
+
const column = buildArrowUtf8Vector(['skip', 'first', 'second']).slice(
|
|
79
|
+
1,
|
|
80
|
+
3
|
|
81
|
+
) as arrow.Vector<arrow.Utf8>;
|
|
82
|
+
const source = getArrowUtf8ColumnSource(column);
|
|
83
|
+
const out = makeEmptyUtf8View();
|
|
84
|
+
|
|
85
|
+
expect(source).not.toBeNull();
|
|
86
|
+
expect(getUtf8ColumnSourceRowView(source!, 0, out)).toBe(true);
|
|
87
|
+
expect(readUtf8ViewBytes(out)).toEqual([102, 105, 114, 115, 116]);
|
|
88
|
+
expect(getUtf8ColumnSourceRowView(source!, 1, out)).toBe(true);
|
|
89
|
+
expect(readUtf8ViewBytes(out)).toEqual([115, 101, 99, 111, 110, 100]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('fills a reusable row view for chunked Utf8 values', () => {
|
|
93
|
+
const leftTable = new arrow.Table({
|
|
94
|
+
name: arrow.vectorFromArray(['left'], new arrow.Utf8())
|
|
95
|
+
});
|
|
96
|
+
const rightTable = new arrow.Table({
|
|
97
|
+
name: arrow.vectorFromArray(['right', 'last'], new arrow.Utf8())
|
|
98
|
+
});
|
|
99
|
+
const column = leftTable.concat(rightTable).getChild('name') as arrow.Vector<arrow.Utf8>;
|
|
100
|
+
const out = makeEmptyUtf8View();
|
|
101
|
+
|
|
102
|
+
expect(getArrowUtf8RowView(column, 0, out)).toBe(true);
|
|
103
|
+
expect(readUtf8ViewBytes(out)).toEqual([108, 101, 102, 116]);
|
|
104
|
+
expect(getArrowUtf8RowView(column, 2, out)).toBe(true);
|
|
105
|
+
expect(readUtf8ViewBytes(out)).toEqual([108, 97, 115, 116]);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function makeEmptyUtf8View() {
|
|
110
|
+
return {data: new Uint8Array(), start: 0, end: 0};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readUtf8ViewBytes(view: ReturnType<typeof makeEmptyUtf8View>): number[] {
|
|
114
|
+
return Array.from(view.data.subarray(view.start, view.end));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildArrowUtf8Vector(values: readonly string[]): arrow.Vector<arrow.Utf8> {
|
|
118
|
+
return arrow.vectorFromArray(values, new arrow.Utf8()) as arrow.Vector<arrow.Utf8>;
|
|
119
|
+
}
|