@carto/ps-react-ui 6.3.0 → 6.5.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/actions-D_PVFZaM.js +1272 -0
- package/dist/actions-D_PVFZaM.js.map +1 -0
- package/dist/{category-DULnoxvk.js → category-UIGvFN3D.js} +263 -246
- package/dist/category-UIGvFN3D.js.map +1 -0
- package/dist/chat.js +472 -470
- package/dist/chat.js.map +1 -1
- package/dist/{components-CTUpdQuj.js → components-DDJU4m0t.js} +8 -8
- package/dist/components-DDJU4m0t.js.map +1 -0
- package/dist/components.js +2 -2
- package/dist/{download-B4sykOFm.js → download-DIj1Iz0t.js} +2 -2
- package/dist/{download-B4sykOFm.js.map → download-DIj1Iz0t.js.map} +1 -1
- package/dist/{formula-DJS-JLpk.js → formula-CSHhAoyW.js} +4 -4
- package/dist/{formula-DJS-JLpk.js.map → formula-CSHhAoyW.js.map} +1 -1
- package/dist/{item-color-B7xkIpDZ.js → item-color-7o7pFBR2.js} +26 -18
- package/dist/item-color-7o7pFBR2.js.map +1 -0
- package/dist/markdown-B8lLR-NY.js +106 -0
- package/dist/markdown-B8lLR-NY.js.map +1 -0
- package/dist/scale-Bhx8HKEB.js +29 -0
- package/dist/scale-Bhx8HKEB.js.map +1 -0
- package/dist/{spread-CQwFIwuz.js → spread-BRfPPIep.js} +3 -3
- package/dist/{spread-CQwFIwuz.js.map → spread-BRfPPIep.js.map} +1 -1
- package/dist/style-DTLuIo84.js +87 -0
- package/dist/style-DTLuIo84.js.map +1 -0
- package/dist/{styles-BCEBm7xh.js → styles-yKcpfyNV.js} +5 -5
- package/dist/styles-yKcpfyNV.js.map +1 -0
- package/dist/{table-DEdW_fDj.js → table-Col_FRvb.js} +2 -2
- package/dist/{table-DEdW_fDj.js.map → table-Col_FRvb.js.map} +1 -1
- package/dist/theme/tailwind.css +373 -0
- package/dist/theme.js +123 -0
- package/dist/theme.js.map +1 -0
- package/dist/types/components/lasso-tool/styles.d.ts +4 -4
- package/dist/types/theme/create-carto-theme.d.ts +7 -0
- package/dist/types/theme/deep-merge.d.ts +6 -0
- package/dist/types/theme/generate-tailwind-css.d.ts +8 -0
- package/dist/types/theme/index.d.ts +2 -0
- package/dist/types/widgets/actions/index.d.ts +1 -1
- package/dist/types/widgets/actions/relative-data/index.d.ts +1 -0
- package/dist/types/widgets/actions/relative-data/labels.d.ts +3 -0
- package/dist/types/widgets/actions/relative-data/relative-data.d.ts +16 -2
- package/dist/types/widgets/actions/relative-data/scale.d.ts +26 -0
- package/dist/types/widgets/actions/relative-data/style.d.ts +12 -0
- package/dist/types/widgets/category/category.d.ts +2 -1
- package/dist/types/widgets/index.d.ts +1 -1
- package/dist/types/widgets/markdown/style.d.ts +74 -0
- package/dist/types/widgets/utils/spacing-px.d.ts +13 -0
- package/dist/{utils-C-eWwOhE.js → utils-Dw4y8tiq.js} +2 -2
- package/dist/{utils-C-eWwOhE.js.map → utils-Dw4y8tiq.js.map} +1 -1
- package/dist/widgets/actions.js +3 -3
- package/dist/widgets/bar.js +85 -84
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/category.js +1 -1
- package/dist/widgets/formula.js +1 -1
- package/dist/widgets/histogram.js +109 -108
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/markdown.js +1 -1
- package/dist/widgets/pie.js +78 -78
- package/dist/widgets/pie.js.map +1 -1
- package/dist/widgets/scatterplot.js +121 -120
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/spread.js +1 -1
- package/dist/widgets/table.js +1 -1
- package/dist/widgets/timeseries.js +91 -90
- package/dist/widgets/timeseries.js.map +1 -1
- package/dist/widgets/utils.js +1 -1
- package/dist/widgets.js +283 -279
- package/dist/widgets.js.map +1 -1
- package/dist/{zoom-toggle-MmLjMTEw.js → zoom-toggle-DqrV_0Ji.js} +2 -2
- package/dist/{zoom-toggle-MmLjMTEw.js.map → zoom-toggle-DqrV_0Ji.js.map} +1 -1
- package/package.json +8 -1
- package/src/chat/bubbles/styles.ts +2 -0
- package/src/components/lasso-tool/lasso-tool-inline.test.tsx +4 -4
- package/src/components/lasso-tool/lasso-tool-inline.tsx +6 -6
- package/src/components/lasso-tool/styles.ts +4 -4
- package/src/theme/create-carto-theme.test.ts +53 -0
- package/src/theme/create-carto-theme.ts +21 -0
- package/src/theme/deep-merge.test.ts +25 -0
- package/src/theme/deep-merge.ts +22 -0
- package/src/theme/generate-tailwind-css.test.ts +87 -0
- package/src/theme/generate-tailwind-css.ts +178 -0
- package/src/theme/index.ts +2 -0
- package/src/theme/tailwind-compile.test.ts +71 -0
- package/src/theme/tailwind.css +373 -0
- package/src/widgets/actions/index.ts +1 -0
- package/src/widgets/actions/relative-data/index.ts +1 -0
- package/src/widgets/actions/relative-data/labels.ts +6 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +78 -1
- package/src/widgets/actions/relative-data/relative-data.tsx +137 -20
- package/src/widgets/actions/relative-data/scale.ts +48 -0
- package/src/widgets/actions/relative-data/style.ts +10 -0
- package/src/widgets/bar/options.test.ts +16 -0
- package/src/widgets/bar/options.ts +13 -8
- package/src/widgets/category/category.relative-data.test.tsx +51 -63
- package/src/widgets/category/category.tsx +25 -11
- package/src/widgets/histogram/options.ts +13 -8
- package/src/widgets/index.ts +1 -0
- package/src/widgets/markdown/style.ts +40 -0
- package/src/widgets/note/widget-note.tsx +4 -1
- package/src/widgets/pie/options.ts +5 -4
- package/src/widgets/scatterplot/options.test.ts +12 -0
- package/src/widgets/scatterplot/options.ts +25 -10
- package/src/widgets/timeseries/options.ts +13 -8
- package/src/widgets/utils/css-variables-theme.test.ts +32 -0
- package/src/widgets/utils/option-builders.ts +4 -6
- package/src/widgets/utils/spacing-px.test.ts +42 -0
- package/src/widgets/utils/spacing-px.ts +24 -0
- package/dist/actions-CkxKe8-J.js +0 -1160
- package/dist/actions-CkxKe8-J.js.map +0 -1
- package/dist/category-DULnoxvk.js.map +0 -1
- package/dist/components-CTUpdQuj.js.map +0 -1
- package/dist/item-color-B7xkIpDZ.js.map +0 -1
- package/dist/markdown-DmvLi3vx.js +0 -159
- package/dist/markdown-DmvLi3vx.js.map +0 -1
- package/dist/styles-BCEBm7xh.js.map +0 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { ZOOM_LAYOUT } from '../actions/zoom-toggle'
|
|
13
13
|
import type { OptionFactory } from '../echart'
|
|
14
14
|
import { mergeOptions, resolveThemeColor } from '../utils'
|
|
15
|
+
import { spacingPx } from '../utils/spacing-px'
|
|
15
16
|
import { createItemColorResolver } from '../utils/item-color'
|
|
16
17
|
import { getHistogramBinValue } from './bins'
|
|
17
18
|
import { positionDataZoomForLegend } from '../utils/data-zoom-layout'
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
HistogramBin,
|
|
23
24
|
HistogramWidgetData,
|
|
24
25
|
} from './types'
|
|
26
|
+
import { fixedAxisBounds } from '../actions/relative-data/scale'
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Builds the **structural** ECharts option for a histogram widget — axes,
|
|
@@ -55,9 +57,9 @@ export function histogramOptions({
|
|
|
55
57
|
|
|
56
58
|
return {
|
|
57
59
|
grid: {
|
|
58
|
-
left:
|
|
59
|
-
top:
|
|
60
|
-
right:
|
|
60
|
+
left: spacingPx(theme, 1),
|
|
61
|
+
top: spacingPx(theme, 3),
|
|
62
|
+
right: spacingPx(theme, 1),
|
|
61
63
|
// Default: no legend. Merger bumps this when there are >1 series.
|
|
62
64
|
...buildGridConfig(false, theme),
|
|
63
65
|
containLabel: true,
|
|
@@ -66,7 +68,7 @@ export function histogramOptions({
|
|
|
66
68
|
trigger: 'axis',
|
|
67
69
|
backgroundColor: theme.palette.grey[900],
|
|
68
70
|
borderWidth: 0,
|
|
69
|
-
padding: [
|
|
71
|
+
padding: [spacingPx(theme, 1), spacingPx(theme, 1)],
|
|
70
72
|
textStyle: {
|
|
71
73
|
color: theme.palette.common.white,
|
|
72
74
|
fontSize: 11,
|
|
@@ -97,7 +99,7 @@ export function histogramOptions({
|
|
|
97
99
|
axisTick: { show: false },
|
|
98
100
|
axisLabel: {
|
|
99
101
|
...buildAxisLabelStyle(theme),
|
|
100
|
-
padding: [
|
|
102
|
+
padding: [spacingPx(theme, 0.5), 0, 0, 0],
|
|
101
103
|
margin: 0,
|
|
102
104
|
// `interval: 0` makes every bin label a candidate so `hideOverlap`
|
|
103
105
|
// can pack the maximum number that physically fit. The category-axis
|
|
@@ -125,7 +127,7 @@ export function histogramOptions({
|
|
|
125
127
|
},
|
|
126
128
|
axisLabel: {
|
|
127
129
|
...buildAxisLabelStyle(theme),
|
|
128
|
-
margin:
|
|
130
|
+
margin: spacingPx(theme, 1),
|
|
129
131
|
show: true,
|
|
130
132
|
showMaxLabel: true,
|
|
131
133
|
showMinLabel: true,
|
|
@@ -216,6 +218,7 @@ export function createHistogramOptionFactory(
|
|
|
216
218
|
typeof option.yAxis === 'object' && !Array.isArray(option.yAxis)
|
|
217
219
|
? option.yAxis
|
|
218
220
|
: {}
|
|
221
|
+
const fixedY = fixedAxisBounds(baseYAxis)
|
|
219
222
|
const baseGrid =
|
|
220
223
|
typeof option.grid === 'object' && !Array.isArray(option.grid)
|
|
221
224
|
? option.grid
|
|
@@ -354,12 +357,14 @@ export function createHistogramOptionFactory(
|
|
|
354
357
|
...(dataZoomLayout ? { dataZoom: dataZoomLayout } : {}),
|
|
355
358
|
yAxis: {
|
|
356
359
|
...baseYAxis,
|
|
360
|
+
// A config transform (RelativeData's `'total'` scale) may pin the
|
|
361
|
+
// ends; each applies only on the side the data reaches.
|
|
357
362
|
min: (extent: { min: number }) => {
|
|
358
|
-
niceMin = extent.min < 0 ? niceNum(extent.min) : 0
|
|
363
|
+
niceMin = extent.min < 0 ? (fixedY.min ?? niceNum(extent.min)) : 0
|
|
359
364
|
return niceMin
|
|
360
365
|
},
|
|
361
366
|
max: (extent: { min: number; max: number }) => {
|
|
362
|
-
niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
|
|
367
|
+
niceMax = extent.max <= 0 ? 1 : (fixedY.max ?? niceNum(extent.max))
|
|
363
368
|
return niceMax
|
|
364
369
|
},
|
|
365
370
|
axisLabel: {
|
package/src/widgets/index.ts
CHANGED
|
@@ -1,7 +1,47 @@
|
|
|
1
1
|
import type { SxProps, Theme } from '@mui/material'
|
|
2
|
+
import type { CSSObject } from '@mui/material/styles'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Browser defaults that rendered Markdown relies on, restated with `revert`
|
|
6
|
+
* so a CSS reset in the app (Tailwind's preflight, for one) cannot strip
|
|
7
|
+
* them: list bullets and indent, link underline, blockquote margins, inline
|
|
8
|
+
* images, monospace fonts, `pre` margins, `sub`/`sup` placement. `revert`
|
|
9
|
+
* rolls back to the browser's own value, so pages without a reset render
|
|
10
|
+
* exactly as before.
|
|
11
|
+
*
|
|
12
|
+
* Wrapped in `:where()` so any element rule a consumer sets on top wins.
|
|
13
|
+
*/
|
|
14
|
+
export const markdownElementDefaults = {
|
|
15
|
+
'& :where(ul, ol)': {
|
|
16
|
+
listStyle: 'revert',
|
|
17
|
+
margin: 'revert',
|
|
18
|
+
padding: 'revert',
|
|
19
|
+
},
|
|
20
|
+
'& :where(a)': { textDecoration: 'revert' },
|
|
21
|
+
'& :where(blockquote)': { margin: 'revert' },
|
|
22
|
+
'& :where(code, kbd, samp)': { fontFamily: 'revert' },
|
|
23
|
+
'& :where(img)': {
|
|
24
|
+
display: 'revert',
|
|
25
|
+
verticalAlign: 'revert',
|
|
26
|
+
maxWidth: 'revert',
|
|
27
|
+
height: 'revert',
|
|
28
|
+
},
|
|
29
|
+
'& :where(pre)': {
|
|
30
|
+
margin: 'revert',
|
|
31
|
+
fontFamily: 'revert',
|
|
32
|
+
fontSize: 'revert',
|
|
33
|
+
},
|
|
34
|
+
'& :where(sub, sup)': {
|
|
35
|
+
fontSize: 'revert',
|
|
36
|
+
lineHeight: 'revert',
|
|
37
|
+
position: 'revert',
|
|
38
|
+
verticalAlign: 'revert',
|
|
39
|
+
},
|
|
40
|
+
} satisfies CSSObject
|
|
2
41
|
|
|
3
42
|
export const styles = {
|
|
4
43
|
root: {
|
|
44
|
+
...markdownElementDefaults,
|
|
5
45
|
color: 'text.primary',
|
|
6
46
|
'& a': { color: 'primary.main' },
|
|
7
47
|
'& code': {
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
|
|
|
2
2
|
import { Box, Link, Typography } from '@mui/material'
|
|
3
3
|
import type { Components } from 'react-markdown'
|
|
4
4
|
import { MarkdownContent } from '../markdown/markdown-content'
|
|
5
|
+
import { markdownElementDefaults } from '../markdown/style'
|
|
5
6
|
import { observeResize } from '../echart/shared-resize-observer'
|
|
6
7
|
import { DEFAULT_NOTE_LABELS, type NoteLabels } from './labels'
|
|
7
8
|
import { styles } from './style'
|
|
@@ -107,7 +108,9 @@ function NoteBase({ lineClamp = 3, labels, children }: NoteProps) {
|
|
|
107
108
|
function NoteMarkdown({ content, lineClamp = 3, labels }: NoteMarkdownProps) {
|
|
108
109
|
return (
|
|
109
110
|
<NoteShell lineClamp={lineClamp} labels={labels} dependency={content}>
|
|
110
|
-
<
|
|
111
|
+
<Box sx={markdownElementDefaults}>
|
|
112
|
+
<MarkdownContent content={content} components={NOTE_COMPONENTS} />
|
|
113
|
+
</Box>
|
|
111
114
|
</NoteShell>
|
|
112
115
|
)
|
|
113
116
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../utils/option-builders'
|
|
13
13
|
import type { OptionFactory, OptionFactoryContext } from '../echart'
|
|
14
14
|
import { mergeOptions, resolveThemeColor } from '../utils'
|
|
15
|
+
import { spacingPx } from '../utils/spacing-px'
|
|
15
16
|
import { createItemColorResolver } from '../utils/item-color'
|
|
16
17
|
import type { WidgetSeries } from '../types'
|
|
17
18
|
import type {
|
|
@@ -67,7 +68,7 @@ export function pieOptions({
|
|
|
67
68
|
trigger: 'item',
|
|
68
69
|
backgroundColor: theme.palette.grey[900],
|
|
69
70
|
borderWidth: 0,
|
|
70
|
-
padding: [
|
|
71
|
+
padding: [spacingPx(theme, 1), spacingPx(theme, 1)],
|
|
71
72
|
textStyle: {
|
|
72
73
|
color: theme.palette.common.white,
|
|
73
74
|
fontSize: 11,
|
|
@@ -466,7 +467,7 @@ function buildMultiSeriesBarFusion(
|
|
|
466
467
|
grid: {
|
|
467
468
|
...baseGrid,
|
|
468
469
|
...buildGridConfig(true, theme),
|
|
469
|
-
right:
|
|
470
|
+
right: spacingPx(theme, 4),
|
|
470
471
|
containLabel: true,
|
|
471
472
|
},
|
|
472
473
|
// Drop pie-specific structural keys that shouldn't render in the
|
|
@@ -511,7 +512,7 @@ function buildMultiSeriesBarFusion(
|
|
|
511
512
|
axisLabel: {
|
|
512
513
|
fontSize: theme.typography.overlineDelicate?.fontSize,
|
|
513
514
|
fontFamily: theme.typography.overlineDelicate?.fontFamily,
|
|
514
|
-
margin:
|
|
515
|
+
margin: spacingPx(theme, 1),
|
|
515
516
|
showMaxLabel: true,
|
|
516
517
|
showMinLabel: true,
|
|
517
518
|
formatter: (value: number) => {
|
|
@@ -526,7 +527,7 @@ function buildMultiSeriesBarFusion(
|
|
|
526
527
|
axisLine: { show: false },
|
|
527
528
|
axisTick: { show: false },
|
|
528
529
|
axisLabel: {
|
|
529
|
-
padding: [
|
|
530
|
+
padding: [spacingPx(theme, 0.5), 0, 0, 0],
|
|
530
531
|
...(labelFormatter && {
|
|
531
532
|
formatter: (value: string | number) => String(labelFormatter(value)),
|
|
532
533
|
}),
|
|
@@ -213,6 +213,18 @@ describe('createScatterplotOptionFactory', () => {
|
|
|
213
213
|
expect(out.yAxis.min).toBe(0)
|
|
214
214
|
})
|
|
215
215
|
|
|
216
|
+
it("pins the y ends a config transform set (RelativeData 'total')", () => {
|
|
217
|
+
const merge = createScatterplotOptionFactory({ theme })
|
|
218
|
+
const out = merge({ yAxis: { min: -100, max: 100 } }, [
|
|
219
|
+
[
|
|
220
|
+
[1, 47],
|
|
221
|
+
[2, 3],
|
|
222
|
+
],
|
|
223
|
+
]) as { yAxis: { min: number; max: number } }
|
|
224
|
+
expect(out.yAxis.max).toBe(100)
|
|
225
|
+
expect(out.yAxis.min).toBe(0)
|
|
226
|
+
})
|
|
227
|
+
|
|
216
228
|
it('always emits itemStyle.color as a callback (passthrough when no selection)', () => {
|
|
217
229
|
const merge = createScatterplotOptionFactory({ theme })
|
|
218
230
|
const out = merge({}, [[[1, 2]]]) as {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { ZOOM_LAYOUT } from '../actions/zoom-toggle'
|
|
13
13
|
import type { OptionFactory } from '../echart'
|
|
14
14
|
import { mergeOptions, resolveThemeColor } from '../utils'
|
|
15
|
+
import { spacingPx } from '../utils/spacing-px'
|
|
15
16
|
import { createItemColorResolver } from '../utils/item-color'
|
|
16
17
|
import { getScatterplotXY } from './points'
|
|
17
18
|
import type {
|
|
@@ -21,6 +22,7 @@ import type {
|
|
|
21
22
|
ScatterplotItem,
|
|
22
23
|
ScatterplotWidgetData,
|
|
23
24
|
} from './types'
|
|
25
|
+
import { fixedAxisBounds } from '../actions/relative-data/scale'
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Builds the **structural** ECharts option for a scatterplot widget — both
|
|
@@ -44,9 +46,9 @@ export function scatterplotOptions({
|
|
|
44
46
|
}: ScatterplotOptionsInput): ScatterplotEChartsOption {
|
|
45
47
|
return {
|
|
46
48
|
grid: {
|
|
47
|
-
left:
|
|
48
|
-
top:
|
|
49
|
-
right:
|
|
49
|
+
left: spacingPx(theme, 1),
|
|
50
|
+
top: spacingPx(theme, 3),
|
|
51
|
+
right: spacingPx(theme, 1),
|
|
50
52
|
// Default: no legend. Merger bumps `bottom` when there are >1 series.
|
|
51
53
|
...buildGridConfig(false, theme),
|
|
52
54
|
containLabel: true,
|
|
@@ -57,7 +59,7 @@ export function scatterplotOptions({
|
|
|
57
59
|
trigger: 'item',
|
|
58
60
|
backgroundColor: theme.palette.grey[900],
|
|
59
61
|
borderWidth: 0,
|
|
60
|
-
padding: [
|
|
62
|
+
padding: [spacingPx(theme, 1), spacingPx(theme, 1)],
|
|
61
63
|
textStyle: {
|
|
62
64
|
color: theme.palette.common.white,
|
|
63
65
|
fontSize: 11,
|
|
@@ -85,7 +87,7 @@ export function scatterplotOptions({
|
|
|
85
87
|
axisTick: { show: false },
|
|
86
88
|
axisLabel: {
|
|
87
89
|
...buildAxisLabelStyle(theme),
|
|
88
|
-
margin:
|
|
90
|
+
margin: spacingPx(theme, 1),
|
|
89
91
|
hideOverlap: true,
|
|
90
92
|
showMinLabel: true,
|
|
91
93
|
showMaxLabel: true,
|
|
@@ -102,7 +104,7 @@ export function scatterplotOptions({
|
|
|
102
104
|
axisTick: { show: false },
|
|
103
105
|
axisLabel: {
|
|
104
106
|
...buildAxisLabelStyle(theme),
|
|
105
|
-
margin:
|
|
107
|
+
margin: spacingPx(theme, 1),
|
|
106
108
|
hideOverlap: true,
|
|
107
109
|
showMinLabel: true,
|
|
108
110
|
showMaxLabel: true,
|
|
@@ -189,8 +191,12 @@ export function createScatterplotOptionFactory(
|
|
|
189
191
|
|
|
190
192
|
const reactiveFormatter = ctx?.formatter
|
|
191
193
|
|
|
192
|
-
const { niceMinX, niceMaxX, niceMinY, niceMaxY } =
|
|
193
|
-
|
|
194
|
+
const { niceMinX, niceMaxX, niceMinY, niceMaxY } = computeScatterBounds(
|
|
195
|
+
seriesArr,
|
|
196
|
+
// A config transform (RelativeData's `'total'` scale) may pin the
|
|
197
|
+
// y ends; each applies only on the side the data reaches.
|
|
198
|
+
fixedAxisBounds(baseYAxis),
|
|
199
|
+
)
|
|
194
200
|
|
|
195
201
|
// Dim non-selected points via `series.itemStyle.color`. The selection
|
|
196
202
|
// key is `${seriesIndex}:${dataIndex}`; we read those off the params
|
|
@@ -410,6 +416,7 @@ function buildScatterTooltipFormatter(
|
|
|
410
416
|
|
|
411
417
|
function computeScatterBounds(
|
|
412
418
|
seriesArr: ScatterplotWidgetData<ScatterplotItem>,
|
|
419
|
+
fixedY: { min?: number; max?: number } = {},
|
|
413
420
|
): {
|
|
414
421
|
niceMinX: number
|
|
415
422
|
niceMaxX: number
|
|
@@ -439,8 +446,16 @@ function computeScatterBounds(
|
|
|
439
446
|
// negative mins, and floor max=0 to 1 so the chart always has range.
|
|
440
447
|
// Scatter can have free coordinates so we apply this per-axis.
|
|
441
448
|
const niceMaxX = Number.isFinite(maxX) ? (maxX <= 0 ? 1 : niceNum(maxX)) : 1
|
|
442
|
-
const niceMaxY = Number.isFinite(maxY)
|
|
449
|
+
const niceMaxY = Number.isFinite(maxY)
|
|
450
|
+
? maxY <= 0
|
|
451
|
+
? 1
|
|
452
|
+
: (fixedY.max ?? niceNum(maxY))
|
|
453
|
+
: 1
|
|
443
454
|
const niceMinX = Number.isFinite(minX) ? (minX < 0 ? niceNum(minX) : 0) : 0
|
|
444
|
-
const niceMinY = Number.isFinite(minY)
|
|
455
|
+
const niceMinY = Number.isFinite(minY)
|
|
456
|
+
? minY < 0
|
|
457
|
+
? (fixedY.min ?? niceNum(minY))
|
|
458
|
+
: 0
|
|
459
|
+
: 0
|
|
445
460
|
return { niceMinX, niceMaxX, niceMinY, niceMaxY }
|
|
446
461
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { ZOOM_LAYOUT } from '../actions/zoom-toggle'
|
|
13
13
|
import type { OptionFactory } from '../echart'
|
|
14
14
|
import { mergeOptions, resolveThemeColor } from '../utils'
|
|
15
|
+
import { spacingPx } from '../utils/spacing-px'
|
|
15
16
|
import { createItemColorResolver } from '../utils/item-color'
|
|
16
17
|
import { positionDataZoomForLegend } from '../utils/data-zoom-layout'
|
|
17
18
|
import type {
|
|
@@ -20,6 +21,7 @@ import type {
|
|
|
20
21
|
TimeseriesOptionsInput,
|
|
21
22
|
TimeseriesWidgetData,
|
|
22
23
|
} from './types'
|
|
24
|
+
import { fixedAxisBounds } from '../actions/relative-data/scale'
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Builds the **structural** ECharts option for a timeseries widget —
|
|
@@ -52,9 +54,9 @@ export function timeseriesOptions({
|
|
|
52
54
|
|
|
53
55
|
return {
|
|
54
56
|
grid: {
|
|
55
|
-
left:
|
|
56
|
-
top:
|
|
57
|
-
right:
|
|
57
|
+
left: spacingPx(theme, 1),
|
|
58
|
+
top: spacingPx(theme, 3),
|
|
59
|
+
right: spacingPx(theme, 1),
|
|
58
60
|
// Default: no legend. Merger bumps this when there are >1 series.
|
|
59
61
|
...buildGridConfig(false, theme),
|
|
60
62
|
containLabel: true,
|
|
@@ -63,7 +65,7 @@ export function timeseriesOptions({
|
|
|
63
65
|
trigger: 'axis',
|
|
64
66
|
backgroundColor: theme.palette.grey[900],
|
|
65
67
|
borderWidth: 0,
|
|
66
|
-
padding: [
|
|
68
|
+
padding: [spacingPx(theme, 1), spacingPx(theme, 1)],
|
|
67
69
|
textStyle: {
|
|
68
70
|
color: theme.palette.common.white,
|
|
69
71
|
fontSize: 11,
|
|
@@ -92,7 +94,7 @@ export function timeseriesOptions({
|
|
|
92
94
|
axisTick: { show: false },
|
|
93
95
|
axisLabel: {
|
|
94
96
|
...buildAxisLabelStyle(theme),
|
|
95
|
-
padding: [
|
|
97
|
+
padding: [spacingPx(theme, 0.5), 0, 0, 0],
|
|
96
98
|
margin: 0,
|
|
97
99
|
hideOverlap: true,
|
|
98
100
|
...(labelFormatter && {
|
|
@@ -118,7 +120,7 @@ export function timeseriesOptions({
|
|
|
118
120
|
},
|
|
119
121
|
axisLabel: {
|
|
120
122
|
...buildAxisLabelStyle(theme),
|
|
121
|
-
margin:
|
|
123
|
+
margin: spacingPx(theme, 1),
|
|
122
124
|
show: true,
|
|
123
125
|
showMaxLabel: true,
|
|
124
126
|
showMinLabel: true,
|
|
@@ -188,6 +190,7 @@ export function createTimeseriesOptionFactory(
|
|
|
188
190
|
typeof option.yAxis === 'object' && !Array.isArray(option.yAxis)
|
|
189
191
|
? option.yAxis
|
|
190
192
|
: {}
|
|
193
|
+
const fixedY = fixedAxisBounds(baseYAxis)
|
|
191
194
|
const baseGrid =
|
|
192
195
|
typeof option.grid === 'object' && !Array.isArray(option.grid)
|
|
193
196
|
? option.grid
|
|
@@ -302,12 +305,14 @@ export function createTimeseriesOptionFactory(
|
|
|
302
305
|
...(dataZoomLayout ? { dataZoom: dataZoomLayout } : {}),
|
|
303
306
|
yAxis: {
|
|
304
307
|
...baseYAxis,
|
|
308
|
+
// A config transform (RelativeData's `'total'` scale) may pin the
|
|
309
|
+
// ends; each applies only on the side the data reaches.
|
|
305
310
|
min: (extent: { min: number }) => {
|
|
306
|
-
niceMin = extent.min < 0 ? niceNum(extent.min) : 0
|
|
311
|
+
niceMin = extent.min < 0 ? (fixedY.min ?? niceNum(extent.min)) : 0
|
|
307
312
|
return niceMin
|
|
308
313
|
},
|
|
309
314
|
max: (extent: { min: number; max: number }) => {
|
|
310
|
-
niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
|
|
315
|
+
niceMax = extent.max <= 0 ? 1 : (fixedY.max ?? niceNum(extent.max))
|
|
311
316
|
return niceMax
|
|
312
317
|
},
|
|
313
318
|
axisLabel: {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createCartoTheme } from '../../theme/create-carto-theme'
|
|
3
|
+
import { barOptions } from '../bar/options'
|
|
4
|
+
import { histogramOptions } from '../histogram/options'
|
|
5
|
+
import { pieOptions } from '../pie/options'
|
|
6
|
+
import { scatterplotOptions } from '../scatterplot/options'
|
|
7
|
+
import { timeseriesOptions } from '../timeseries/options'
|
|
8
|
+
|
|
9
|
+
const theme = createCartoTheme()
|
|
10
|
+
|
|
11
|
+
function nanPaths(value: unknown, path = '$'): string[] {
|
|
12
|
+
if (typeof value === 'number') return Number.isNaN(value) ? [path] : []
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.flatMap((v, i) => nanPaths(v, `${path}[${i}]`))
|
|
15
|
+
if (value && typeof value === 'object')
|
|
16
|
+
return Object.entries(value).flatMap(([k, v]) =>
|
|
17
|
+
nanPaths(v, `${path}.${k}`),
|
|
18
|
+
)
|
|
19
|
+
return []
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('ECharts options with a cssVariables theme', () => {
|
|
23
|
+
it.each([
|
|
24
|
+
['bar', () => barOptions({ theme })],
|
|
25
|
+
['histogram', () => histogramOptions({ theme })],
|
|
26
|
+
['pie', () => pieOptions({ theme })],
|
|
27
|
+
['scatterplot', () => scatterplotOptions({ theme })],
|
|
28
|
+
['timeseries', () => timeseriesOptions({ theme })],
|
|
29
|
+
])('%s has no NaN in its options', (_name, build) => {
|
|
30
|
+
expect(nanPaths(build())).toEqual([])
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CallbackDataParams,
|
|
5
5
|
TopLevelFormatterParams,
|
|
6
6
|
} from 'echarts/types/dist/shared'
|
|
7
|
+
import { spacingPx } from './spacing-px'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Shared EChart configuration builders for chart widgets
|
|
@@ -61,8 +62,8 @@ export function buildLegendConfig({
|
|
|
61
62
|
*/
|
|
62
63
|
export function buildGridConfig(hasLegend: boolean, theme: Theme) {
|
|
63
64
|
return {
|
|
64
|
-
...(!hasLegend && { bottom:
|
|
65
|
-
...(hasLegend && { bottom:
|
|
65
|
+
...(!hasLegend && { bottom: spacingPx(theme, 3) }),
|
|
66
|
+
...(hasLegend && { bottom: spacingPx(theme, 7) }),
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -106,10 +107,7 @@ export function createTooltipPositioner(theme: Theme) {
|
|
|
106
107
|
_rect: unknown,
|
|
107
108
|
size: { contentSize: [number, number]; viewSize: [number, number] },
|
|
108
109
|
) {
|
|
109
|
-
const position = { top:
|
|
110
|
-
string,
|
|
111
|
-
number
|
|
112
|
-
>
|
|
110
|
+
const position = { top: spacingPx(theme, 0.5) } as Record<string, number>
|
|
113
111
|
|
|
114
112
|
// Position tooltip left or right based on available space
|
|
115
113
|
if (size.contentSize[0] < size.viewSize[0] - point[0]) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createTheme, responsiveFontSizes, type Theme } from '@mui/material'
|
|
3
|
+
import {
|
|
4
|
+
cartoThemeOptions,
|
|
5
|
+
theme as cartoTheme,
|
|
6
|
+
} from '@carto/meridian-ds/theme'
|
|
7
|
+
import { createCartoTheme } from '../../theme/create-carto-theme'
|
|
8
|
+
import { spacingPx } from './spacing-px'
|
|
9
|
+
|
|
10
|
+
const FACTORS = [0.5, 1, 3, 4, 7]
|
|
11
|
+
|
|
12
|
+
describe('spacingPx', () => {
|
|
13
|
+
it('matches the previous parseInt(theme.spacing(n)) for the current CARTO theme', () => {
|
|
14
|
+
const theme = createTheme(cartoTheme)
|
|
15
|
+
for (const factor of FACTORS) {
|
|
16
|
+
expect(spacingPx(theme, factor)).toBe(parseInt(theme.spacing(factor)))
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('resolves numbers for a CARTO theme with cssVariables', () => {
|
|
21
|
+
const theme = responsiveFontSizes(
|
|
22
|
+
createTheme({ ...cartoThemeOptions, cssVariables: true }),
|
|
23
|
+
)
|
|
24
|
+
expect(theme.spacing(1)).toContain('var(')
|
|
25
|
+
expect(FACTORS.map((f) => spacingPx(theme, f))).toEqual([4, 8, 24, 32, 56])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('falls back to 8px per unit for a plain MUI theme with cssVariables', () => {
|
|
29
|
+
const theme = createTheme({ cssVariables: true }) as unknown as Theme
|
|
30
|
+
expect(FACTORS.map((f) => spacingPx(theme, f))).toEqual([4, 8, 24, 32, 56])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('honours a numeric spacing override on createCartoTheme', () => {
|
|
34
|
+
const theme = createCartoTheme({ spacing: 4 })
|
|
35
|
+
expect(FACTORS.map((f) => spacingPx(theme, f))).toEqual([2, 4, 12, 16, 28])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('honours a function spacing override under cssVariables', () => {
|
|
39
|
+
const theme = createCartoTheme({ spacing: (f: number) => f * 6 })
|
|
40
|
+
expect(spacingPx(theme, 1)).toBe(6)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Theme } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Theme spacing as a pixel number, for APIs that need numbers (ECharts).
|
|
5
|
+
*
|
|
6
|
+
* `theme.spacing(n)` returns `"8px"` for a regular theme but
|
|
7
|
+
* `"var(--mui-spacing, 8px)"` / `"calc(...)"` once the theme uses
|
|
8
|
+
* `cssVariables`, which does not parse. Resolution order:
|
|
9
|
+
* 1. `parseFloat` of `theme.spacing(factor)` (regular themes);
|
|
10
|
+
* 2. the px unit MUI writes into `theme.spacing(1)` (`var(--mui-spacing, Npx)`),
|
|
11
|
+
* which honours a `spacing` override such as `createCartoTheme({ spacing: 4 })`;
|
|
12
|
+
* 3. meridian's numeric `spacingValue`, or MUI's default 8px.
|
|
13
|
+
*/
|
|
14
|
+
export function spacingPx(theme: Theme, factor: number): number {
|
|
15
|
+
const parsed = parseFloat(theme.spacing(factor))
|
|
16
|
+
if (Number.isFinite(parsed)) return parsed
|
|
17
|
+
// `spacing(1)` is `var(--mui-spacing, Npx)`; larger factors are
|
|
18
|
+
// `calc(f * var(--mui-spacing, Npx))`, so read the unit from the former.
|
|
19
|
+
const fallback = /,\s*(-?[\d.]+)px\)\s*$/.exec(theme.spacing(1))
|
|
20
|
+
const px = fallback ? Number(fallback[1]) : NaN
|
|
21
|
+
if (Number.isFinite(px)) return px * factor
|
|
22
|
+
const unit = (theme.spacingValue as number | undefined) ?? 8
|
|
23
|
+
return unit * factor
|
|
24
|
+
}
|