@carto/ps-react-ui 4.6.3 → 4.7.1
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/{download-config-C3I0jWIL.js → download-config-DNLkypdN.js} +8 -7
- package/dist/{download-config-C3I0jWIL.js.map → download-config-DNLkypdN.js.map} +1 -1
- package/dist/shared-resize-observer-98b1SK1e.js +17 -0
- package/dist/shared-resize-observer-98b1SK1e.js.map +1 -0
- package/dist/types/widgets/actions/brush-toggle/brush-overlay.d.ts +24 -0
- package/dist/types/widgets/actions/brush-toggle/brush-toggle.d.ts +15 -10
- package/dist/types/widgets/actions/brush-toggle/hit-test.d.ts +19 -0
- package/dist/types/widgets/actions/brush-toggle/hit-test.test.d.ts +1 -0
- package/dist/types/widgets/actions/brush-toggle/style.d.ts +8 -0
- package/dist/types/widgets/actions/brush-toggle/types.d.ts +35 -1
- package/dist/widgets/actions.js +1020 -787
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +1 -1
- package/dist/widgets/category.js +9 -8
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/echart.js +79 -91
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +43 -42
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +37 -36
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/markdown.js +15 -14
- package/dist/widgets/markdown.js.map +1 -1
- package/dist/widgets/pie.js +1 -1
- package/dist/widgets/scatterplot.js +1 -1
- package/dist/widgets/spread.js +47 -46
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/table.js +17 -16
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +1 -1
- package/dist/widgets/utils.js +1 -1
- package/package.json +3 -1
- package/src/widgets/actions/brush-toggle/brush-overlay.tsx +408 -0
- package/src/widgets/actions/brush-toggle/brush-toggle.tsx +88 -152
- package/src/widgets/actions/brush-toggle/hit-test.test.ts +65 -0
- package/src/widgets/actions/brush-toggle/hit-test.ts +45 -0
- package/src/widgets/actions/brush-toggle/style.ts +32 -0
- package/src/widgets/actions/brush-toggle/types.ts +36 -1
- package/src/widgets/histogram/config.ts +1 -3
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest'
|
|
2
|
+
import { hitTestCategoryRange, hitTestRects } from './hit-test'
|
|
3
|
+
|
|
4
|
+
describe('hitTestCategoryRange', () => {
|
|
5
|
+
test('returns empty when dataLength is zero', () => {
|
|
6
|
+
expect(hitTestCategoryRange(0, 5, 0)).toEqual([])
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('snaps fractional range outward so partial overlaps are included', () => {
|
|
10
|
+
// xStart=1.3, xEnd=3.7 → floor(1.3)=1, ceil(3.7)=4 → [1, 2, 3, 4]
|
|
11
|
+
expect(hitTestCategoryRange(1.3, 3.7, 10)).toEqual([1, 2, 3, 4])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('handles integer endpoints inclusively', () => {
|
|
15
|
+
expect(hitTestCategoryRange(2, 4, 10)).toEqual([2, 3, 4])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('clamps to [0, dataLength - 1]', () => {
|
|
19
|
+
expect(hitTestCategoryRange(-2.5, 12.8, 5)).toEqual([0, 1, 2, 3, 4])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('normalizes reversed inputs (xEnd < xStart)', () => {
|
|
23
|
+
expect(hitTestCategoryRange(3.7, 1.3, 10)).toEqual([1, 2, 3, 4])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('returns empty when range is entirely out of bounds', () => {
|
|
27
|
+
expect(hitTestCategoryRange(10, 12, 5)).toEqual([])
|
|
28
|
+
expect(hitTestCategoryRange(-5, -1, 5)).toEqual([])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('returns adjacent indices for a very narrow fractional range when snapping outward', () => {
|
|
32
|
+
// xStart=2.1, xEnd=2.3 partially overlaps both categories, so floor=2 and ceil=3 → [2, 3]
|
|
33
|
+
expect(hitTestCategoryRange(2.1, 2.3, 10)).toEqual([2, 3])
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('hitTestRects', () => {
|
|
38
|
+
test('returns empty for no rects', () => {
|
|
39
|
+
expect(hitTestRects([], 10)).toEqual([])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('unions and dedups across overlapping rects', () => {
|
|
43
|
+
const rects = [
|
|
44
|
+
{ xStart: 1, xEnd: 3 },
|
|
45
|
+
{ xStart: 2, xEnd: 4 },
|
|
46
|
+
]
|
|
47
|
+
expect(hitTestRects(rects, 10)).toEqual([1, 2, 3, 4])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('returns sorted ascending indices for disjoint rects', () => {
|
|
51
|
+
const rects = [
|
|
52
|
+
{ xStart: 7, xEnd: 8 },
|
|
53
|
+
{ xStart: 1, xEnd: 2 },
|
|
54
|
+
]
|
|
55
|
+
expect(hitTestRects(rects, 10)).toEqual([1, 2, 7, 8])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('drops rects entirely out of bounds', () => {
|
|
59
|
+
const rects = [
|
|
60
|
+
{ xStart: 20, xEnd: 25 },
|
|
61
|
+
{ xStart: 1, xEnd: 2 },
|
|
62
|
+
]
|
|
63
|
+
expect(hitTestRects(rects, 10)).toEqual([1, 2])
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { BrushRect } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns integer dataIndices covered by a brush rectangle on a category axis.
|
|
5
|
+
*
|
|
6
|
+
* `xStart` / `xEnd` come from `instance.convertFromPixel({ xAxisIndex: 0 }, …)`
|
|
7
|
+
* which, for a category axis, returns fractional positions around integer
|
|
8
|
+
* category indices (e.g. `1.3` lands between categories 1 and 2). We snap the
|
|
9
|
+
* range outward (`floor` / `ceil`) so a drag that visually covers any part of
|
|
10
|
+
* a category's bar includes that index.
|
|
11
|
+
*
|
|
12
|
+
* Results are clamped to `[0, dataLength - 1]` and returned in ascending order.
|
|
13
|
+
*/
|
|
14
|
+
export function hitTestCategoryRange(
|
|
15
|
+
xStart: number,
|
|
16
|
+
xEnd: number,
|
|
17
|
+
dataLength: number,
|
|
18
|
+
): number[] {
|
|
19
|
+
if (dataLength <= 0) return []
|
|
20
|
+
const [lo, hi] = xStart <= xEnd ? [xStart, xEnd] : [xEnd, xStart]
|
|
21
|
+
|
|
22
|
+
const start = Math.max(0, Math.floor(lo))
|
|
23
|
+
const end = Math.min(dataLength - 1, Math.ceil(hi))
|
|
24
|
+
if (start > end) return []
|
|
25
|
+
|
|
26
|
+
const indices: number[] = []
|
|
27
|
+
for (let i = start; i <= end; i += 1) indices.push(i)
|
|
28
|
+
return indices
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Union of `hitTestCategoryRange` across multiple rectangles. Used for
|
|
33
|
+
* multi-brush selections where each drawn rectangle contributes to the
|
|
34
|
+
* combined selection.
|
|
35
|
+
*/
|
|
36
|
+
export function hitTestRects(rects: BrushRect[], dataLength: number): number[] {
|
|
37
|
+
if (rects.length === 0) return []
|
|
38
|
+
const seen = new Set<number>()
|
|
39
|
+
for (const rect of rects) {
|
|
40
|
+
for (const i of hitTestCategoryRange(rect.xStart, rect.xEnd, dataLength)) {
|
|
41
|
+
seen.add(i)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return Array.from(seen).sort((a, b) => a - b)
|
|
45
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SxProps, Theme } from '@mui/material'
|
|
2
|
+
import type { CSSProperties } from 'react'
|
|
2
3
|
|
|
3
4
|
export const styles = {
|
|
4
5
|
container: {
|
|
@@ -12,3 +13,34 @@ export const styles = {
|
|
|
12
13
|
},
|
|
13
14
|
},
|
|
14
15
|
} satisfies Record<string, SxProps<Theme>>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Plain CSS objects for the brush overlay — we're outside MUI's sx pipeline
|
|
19
|
+
* here because the rectangles are rendered via a React portal into the chart
|
|
20
|
+
* div (not part of the MUI tree). Colors borrow from the base palette; if
|
|
21
|
+
* theme-aware styling is needed later, move to styled components.
|
|
22
|
+
*/
|
|
23
|
+
export const overlayStyles: Record<string, CSSProperties> = {
|
|
24
|
+
// `top` and `height` are filled in at render time from the chart's plot
|
|
25
|
+
// rect so rectangles don't bleed over the legend / axis labels.
|
|
26
|
+
//
|
|
27
|
+
// Colors track ECharts 6's `backgroundTint` token (used by the default
|
|
28
|
+
// brush) — a barely-visible pale blue-gray translucent fill that washes
|
|
29
|
+
// bars underneath rather than masking them. Border is a hairline dark
|
|
30
|
+
// alpha so the selection edges are just findable, not prominent.
|
|
31
|
+
rect: {
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
background: 'rgba(234, 237, 245, 0.2)',
|
|
34
|
+
border: '1px dashed rgba(0, 0, 0, 0.15)',
|
|
35
|
+
pointerEvents: 'none',
|
|
36
|
+
},
|
|
37
|
+
// Live drag preview — identical to committed rect (no "in-progress" vs
|
|
38
|
+
// "committed" visual distinction; the preview just disappears on
|
|
39
|
+
// `pointerup` as the rect takes its place).
|
|
40
|
+
rectPreview: {
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
background: 'rgba(234, 237, 245, 0.6)',
|
|
43
|
+
border: '1px dashed rgba(0, 0, 0, 0.15)',
|
|
44
|
+
pointerEvents: 'none',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
@@ -14,10 +14,30 @@ export interface BrushSelectedItems {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* A selection rectangle in x-axis data coords. Stored in the widget store so
|
|
18
|
+
* it survives config-pipeline re-renders (which call `setOption` with
|
|
19
|
+
* `notMerge: true` and wipe any ECharts-side state). The overlay re-projects
|
|
20
|
+
* these to pixel positions on every chart render / resize.
|
|
21
|
+
*
|
|
22
|
+
* Both bar and histogram widgets use `xAxis.type === 'category'`, so the
|
|
23
|
+
* coords are category indices (possibly fractional — `convertFromPixel`
|
|
24
|
+
* returns floats like `1.3` between categories 1 and 2).
|
|
25
|
+
*/
|
|
26
|
+
export interface BrushRect {
|
|
27
|
+
xStart: number
|
|
28
|
+
xEnd: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* State stored in widget store for brush functionality.
|
|
18
33
|
*/
|
|
19
34
|
export interface BrushConfig {
|
|
35
|
+
/** Whether brush mode is enabled. */
|
|
20
36
|
brush?: boolean
|
|
37
|
+
/** Persisted selection rectangles in data-axis coords. */
|
|
38
|
+
brushRects?: BrushRect[]
|
|
39
|
+
/** Last selection emitted to the `onBrushSelected` callback. */
|
|
40
|
+
brushSelection?: BrushSelectedItems
|
|
21
41
|
}
|
|
22
42
|
|
|
23
43
|
export type BrushState<T = unknown> = BaseWidgetState<T & BrushConfig>
|
|
@@ -27,6 +47,21 @@ export interface BrushToggleProps {
|
|
|
27
47
|
id: string
|
|
28
48
|
/** Callback fired when items are selected via brush */
|
|
29
49
|
onBrushSelected?: (items: BrushSelectedItems) => void
|
|
50
|
+
/** When true, allows multiple brush selection areas. Brush stays active after each selection. */
|
|
51
|
+
multiBrush?: boolean
|
|
52
|
+
/**
|
|
53
|
+
* Initial brush-enabled state. Only applied on first mount if the widget
|
|
54
|
+
* store has no `brush` value yet — subsequent remounts preserve whatever
|
|
55
|
+
* state the user has toggled to.
|
|
56
|
+
*/
|
|
57
|
+
defaultEnabled?: boolean
|
|
58
|
+
/**
|
|
59
|
+
* Current selection count from the consumer (e.g. `selectedItems.length`).
|
|
60
|
+
* When this transitions to `0` (typically when the user presses the clear
|
|
61
|
+
* button in `WidgetSelectionSummary`), BrushToggle clears its persisted
|
|
62
|
+
* brush rectangles and the on-chart selection.
|
|
63
|
+
*/
|
|
64
|
+
selections?: number
|
|
30
65
|
/** Custom labels for the action */
|
|
31
66
|
labels?: {
|
|
32
67
|
/** Tooltip when brush is disabled (button will enable brush) */
|
|
@@ -211,9 +211,7 @@ function getOption({
|
|
|
211
211
|
|
|
212
212
|
const marker = typeof item.marker === 'string' ? item.marker : ''
|
|
213
213
|
const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
|
|
214
|
-
const name =
|
|
215
|
-
? String(labelFormatter(item.name ?? ''))
|
|
216
|
-
: (item.name ?? '')
|
|
214
|
+
const name = item.name
|
|
217
215
|
|
|
218
216
|
return { name, seriesName, marker, value: formattedValue }
|
|
219
217
|
}),
|