@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.
@@ -1,4 +1,4 @@
1
- import { g as Ke } from "./main-B3yaCPy_.js";
1
+ import { g as Ke } from "./main-uIKdSuDJ.js";
2
2
  import We from "vue";
3
3
  function Fe($, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.155",
3
+ "version": "1.0.2-dev.156",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -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 type { Chart, XAxis, YAxis, XAxisForm, ChartForApi } from '../../types/visualizations'
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: Record<string, Array<{ name: string, type: string }>>
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
- function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' {
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
- return xAxis.type === 'continuous' ? 'value' : 'category'
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 sortedData = [...props.series.data[resourceId]]
68
- const sortBy = props.chart.x_axis.sort_x_by
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 === 'axis_x' ? xColumn : yColumn
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 formatter = new Intl.NumberFormat('fr-FR')
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
- tooltip += `${format.encodeHTML(param.axisValueLabel)}: <strong>${formatter.format(Number(param.value[seriesName]))}</strong><br>`
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: mapXAxisType(props.chart.x_axis),
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,
@@ -51,8 +51,8 @@ export type TabularColumnProfile = {
51
51
  tops: TabularTopValue[]
52
52
  nb_distinct: number
53
53
  nb_missing_values: number
54
- min?: number
55
- max?: number
54
+ min?: number | string
55
+ max?: number | string
56
56
  std?: number
57
57
  mean?: number
58
58
  }
@@ -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
- return { name, type: colType }
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>>