@datagouv/components-next 1.0.2-dev.155 → 1.0.2-dev.156
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/{Datafair.client-DtRZzOSC.js → Datafair.client-bp15NLRY.js} +1 -1
- package/dist/{JsonPreview.client-BUKUgRg5.js → JsonPreview.client-yqVpZTmF.js} +2 -2
- package/dist/{MapContainer.client-CJEunt9n.js → MapContainer.client-2lywLnVm.js} +2 -2
- package/dist/{PdfPreview.client-NEarDRLg.js → PdfPreview.client-BG7MXWMB.js} +2 -2
- package/dist/{Pmtiles.client-Cd7YYm67.js → Pmtiles.client-5ghpJ7mq.js} +1 -1
- package/dist/{PreviewWrapper.vue_vue_type_script_setup_true_lang-CxKTCDXD.js → PreviewWrapper.vue_vue_type_script_setup_true_lang-C-sK1BJV.js} +1 -1
- package/dist/{XmlPreview.client-B3d67EsR.js → XmlPreview.client-DWzKxXoL.js} +3 -3
- package/dist/components-next.js +1 -1
- package/dist/{index-Brxv657p.js → index-B6iEXWF6.js} +1 -1
- package/dist/{leaflet-src-C4j3Fhuc.js → leaflet-src-CpoKI-p2.js} +1 -1
- package/dist/{main-B3yaCPy_.js → main-uIKdSuDJ.js} +4741 -4727
- package/dist/{vue3-xml-viewer.common-BFa35npg.js → vue3-xml-viewer.common-C2hQ2aXh.js} +1 -1
- package/package.json +1 -1
- package/src/components/Chart/ChartViewer.vue +97 -10
- package/src/components/TabularExplorer/types.ts +2 -2
- package/src/functions/charts.ts +18 -2
- package/src/types/visualizations.ts +1 -1
package/package.json
CHANGED
|
@@ -13,7 +13,8 @@ import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, Datas
|
|
|
13
13
|
import { init, type ECharts as EChartsType } from 'echarts'
|
|
14
14
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
15
15
|
import { summarize } from '../../functions/helpers'
|
|
16
|
-
import
|
|
16
|
+
import { useFormatDate } from '../../functions/dates'
|
|
17
|
+
import type { Chart, ColumnDefinition, ColumnsDefinition, XAxis, YAxis, XAxisForm, ChartForApi } from '../../types/visualizations'
|
|
17
18
|
import { useTranslation } from '../../composables/useTranslation'
|
|
18
19
|
|
|
19
20
|
use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent])
|
|
@@ -22,11 +23,12 @@ const props = defineProps<{
|
|
|
22
23
|
chart: Chart | ChartForApi
|
|
23
24
|
series: {
|
|
24
25
|
data: Record<string, Array<Record<string, unknown>>>
|
|
25
|
-
columns:
|
|
26
|
+
columns: ColumnsDefinition
|
|
26
27
|
}
|
|
27
28
|
}>()
|
|
28
29
|
|
|
29
30
|
const { locale } = useTranslation()
|
|
31
|
+
const { formatDate } = useFormatDate()
|
|
30
32
|
const chartContainer = ref<HTMLElement | null>(null)
|
|
31
33
|
let echartsInstance: EChartsType | null = null
|
|
32
34
|
|
|
@@ -36,9 +38,11 @@ function capture(): string | null {
|
|
|
36
38
|
|
|
37
39
|
defineExpose({ capture })
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
// ECharts has a dedicated 'time' axis type for continuous date values.
|
|
42
|
+
function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' | 'time' {
|
|
40
43
|
if (!xAxis) return 'category'
|
|
41
|
-
|
|
44
|
+
if (xAxis.type !== 'continuous') return 'category'
|
|
45
|
+
return isDateAxis.value ? 'time' : 'value'
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefined {
|
|
@@ -50,6 +54,77 @@ function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefi
|
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
function buildXAxisFormatter(column: ColumnDefinition | null, options?: Intl.DateTimeFormatOptions): ((value: string | number | Date) => string) | undefined {
|
|
58
|
+
if (column?.type !== 'date') return undefined
|
|
59
|
+
return (value: string | number | Date) => {
|
|
60
|
+
if (value === null || value === undefined) return ''
|
|
61
|
+
const date = value instanceof Date ? value : new Date(value)
|
|
62
|
+
if (Number.isNaN(date.getTime())) return String(value)
|
|
63
|
+
return formatDate(date, options ?? { dateStyle: 'short' })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseXValueAsDate(row: Record<string, unknown>, xColumn: string): Record<string, unknown> {
|
|
68
|
+
const value = row[xColumn]
|
|
69
|
+
if (value === null || value === undefined || value instanceof Date) return row
|
|
70
|
+
if (typeof value === 'string') {
|
|
71
|
+
const date = new Date(value)
|
|
72
|
+
if (!Number.isNaN(date.getTime())) {
|
|
73
|
+
return { ...row, [xColumn]: date }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return row
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const xAxisColumn = computed(() => {
|
|
80
|
+
const columnName = props.chart.x_axis.column_x
|
|
81
|
+
if (!columnName) return null
|
|
82
|
+
for (const columns of Object.values(props.series.columns)) {
|
|
83
|
+
const column = columns.find(c => c.name === columnName)
|
|
84
|
+
if (column) return column
|
|
85
|
+
}
|
|
86
|
+
return null
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const isDateAxis = computed(() => xAxisColumn.value?.type === 'date')
|
|
90
|
+
|
|
91
|
+
const xAxisBounds = computed(() => {
|
|
92
|
+
const columnName = props.chart.x_axis.column_x
|
|
93
|
+
if (!columnName) return { min: undefined, max: undefined }
|
|
94
|
+
|
|
95
|
+
let min: number | undefined
|
|
96
|
+
let max: number | undefined
|
|
97
|
+
|
|
98
|
+
for (const s of props.chart.series) {
|
|
99
|
+
const xColumn = s.column_x_name_override ?? columnName
|
|
100
|
+
const data = props.series.data[s.resource_id]
|
|
101
|
+
if (!data) continue
|
|
102
|
+
|
|
103
|
+
for (const row of data) {
|
|
104
|
+
const value = row[xColumn]
|
|
105
|
+
if (value === null || value === undefined) continue
|
|
106
|
+
|
|
107
|
+
let numericValue: number | undefined
|
|
108
|
+
if (value instanceof Date) {
|
|
109
|
+
numericValue = value.getTime()
|
|
110
|
+
}
|
|
111
|
+
else if (isDateAxis.value && typeof value === 'string') {
|
|
112
|
+
numericValue = new Date(value).getTime()
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
numericValue = Number(value)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (numericValue === undefined || Number.isNaN(numericValue)) continue
|
|
119
|
+
|
|
120
|
+
if (min === undefined || numericValue < min) min = numericValue
|
|
121
|
+
if (max === undefined || numericValue > max) max = numericValue
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { min, max }
|
|
126
|
+
})
|
|
127
|
+
|
|
53
128
|
const echartsOption = computed(() => {
|
|
54
129
|
const seriesCount = props.chart.series.length
|
|
55
130
|
if (!props.chart.series || seriesCount === 0) return
|
|
@@ -64,12 +139,15 @@ const echartsOption = computed(() => {
|
|
|
64
139
|
return null
|
|
65
140
|
}
|
|
66
141
|
|
|
67
|
-
const
|
|
68
|
-
const
|
|
142
|
+
const rawData = props.series.data[resourceId]
|
|
143
|
+
const sortedData = isDateAxis.value
|
|
144
|
+
? rawData.map(row => parseXValueAsDate(row, xColumn))
|
|
145
|
+
: [...rawData]
|
|
146
|
+
const sortBy = props.chart.x_axis.sort_x_by ?? (isDateAxis.value ? 'axis_x' : null)
|
|
69
147
|
const sortDirection = props.chart.x_axis.sort_x_direction ?? 'asc'
|
|
70
148
|
|
|
71
149
|
if (sortBy && sortDirection && props.chart.x_axis.column_x) {
|
|
72
|
-
const sortKey = sortBy === '
|
|
150
|
+
const sortKey = sortBy === 'axis_y' ? yColumn : xColumn
|
|
73
151
|
sortedData.sort((a, b) => {
|
|
74
152
|
const valA = a[sortKey]
|
|
75
153
|
const valB = b[sortKey]
|
|
@@ -142,6 +220,8 @@ const echartsOption = computed(() => {
|
|
|
142
220
|
seriesData.data.push(curr.data)
|
|
143
221
|
}
|
|
144
222
|
|
|
223
|
+
const xAxisType = mapXAxisType(props.chart.x_axis)
|
|
224
|
+
|
|
145
225
|
return {
|
|
146
226
|
dataset: [...seriesData.data],
|
|
147
227
|
textStyle: {
|
|
@@ -152,10 +232,12 @@ const echartsOption = computed(() => {
|
|
|
152
232
|
trigger: 'axis' as const,
|
|
153
233
|
formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
|
|
154
234
|
let tooltip = ''
|
|
155
|
-
const
|
|
235
|
+
const valueFormatter = new Intl.NumberFormat(locale)
|
|
236
|
+
const xAxisFormatter = buildXAxisFormatter(xAxisColumn.value, { dateStyle: 'medium' })
|
|
156
237
|
for (const param of params) {
|
|
157
238
|
const seriesName = param.seriesName
|
|
158
|
-
|
|
239
|
+
const axisLabel = xAxisFormatter ? xAxisFormatter(param.axisValueLabel) : param.axisValueLabel
|
|
240
|
+
tooltip += `${format.encodeHTML(String(axisLabel))}: <strong>${valueFormatter.format(Number(param.value[seriesName]))}</strong><br>`
|
|
159
241
|
}
|
|
160
242
|
return tooltip
|
|
161
243
|
},
|
|
@@ -172,8 +254,13 @@ const echartsOption = computed(() => {
|
|
|
172
254
|
containLabel: true,
|
|
173
255
|
},
|
|
174
256
|
xAxis: {
|
|
175
|
-
type:
|
|
257
|
+
type: xAxisType,
|
|
176
258
|
name: (props.chart.x_axis as XAxis).column_x,
|
|
259
|
+
min: xAxisType !== 'category' ? xAxisColumn.value?.min ?? xAxisBounds.value.min ?? undefined : undefined,
|
|
260
|
+
max: xAxisType !== 'category' ? xAxisColumn.value?.max ?? xAxisBounds.value.max ?? undefined : undefined,
|
|
261
|
+
axisLabel: xAxisType === 'time'
|
|
262
|
+
? { formatter: buildXAxisFormatter(xAxisColumn.value) }
|
|
263
|
+
: undefined,
|
|
177
264
|
},
|
|
178
265
|
yAxis: {
|
|
179
266
|
type: 'value' as const,
|
package/src/functions/charts.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Chart, ChartForm, ChartForApi, Filter, ColumnDefinition } from '../types/visualizations'
|
|
2
|
-
import type { TabularProfile } from '../components/TabularExplorer/types'
|
|
2
|
+
import type { ColumnType, TabularProfile } from '../components/TabularExplorer/types'
|
|
3
3
|
import { resolveColumnType } from './tabular'
|
|
4
4
|
|
|
5
5
|
export function toChartForm(chart: Chart) {
|
|
@@ -74,6 +74,22 @@ export function buildColumnsFromProfile(profile: { profile: TabularProfile }): A
|
|
|
74
74
|
const colInfo = profile.profile.columns[name]
|
|
75
75
|
const isCategorical = profile.profile.categorical.includes(name)
|
|
76
76
|
const colType = resolveColumnType(colInfo ?? { python_type: 'unknown', format: undefined }, isCategorical)
|
|
77
|
-
|
|
77
|
+
const colProfile = profile.profile.profile[name]
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
type: colType,
|
|
81
|
+
min: normalizeProfileNumber(colProfile?.min, colType),
|
|
82
|
+
max: normalizeProfileNumber(colProfile?.max, colType),
|
|
83
|
+
}
|
|
78
84
|
})
|
|
79
85
|
}
|
|
86
|
+
|
|
87
|
+
function normalizeProfileNumber(value: number | string | undefined, type: ColumnType): number | undefined {
|
|
88
|
+
if (value === undefined || value === null) return undefined
|
|
89
|
+
if (type === 'date' && typeof value === 'string') {
|
|
90
|
+
const timestamp = new Date(value).getTime()
|
|
91
|
+
return Number.isNaN(timestamp) ? undefined : timestamp
|
|
92
|
+
}
|
|
93
|
+
const parsed = Number(value)
|
|
94
|
+
return Number.isNaN(parsed) ? undefined : parsed
|
|
95
|
+
}
|
|
@@ -89,5 +89,5 @@ export type ChartForm = Omit<ChartForApi, 'x_axis' | 'series' | 'owner' | 'organ
|
|
|
89
89
|
filter: GenericFilter | null
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export type ColumnDefinition = { name: string, type: ColumnType }
|
|
92
|
+
export type ColumnDefinition = { name: string, type: ColumnType, min?: number, max?: number }
|
|
93
93
|
export type ColumnsDefinition = Record<string, Array<ColumnDefinition>>
|