@datagouv/components-next 1.0.2-dev.128 → 1.0.2-dev.129

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 { b7 as Ke } from "./main-DKu5SOAl.js";
1
+ import { b8 as Ke } from "./main-CQREzlRN.js";
2
2
  import We from "vue";
3
3
  function Fe(I, 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.128",
3
+ "version": "1.0.2-dev.129",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -22,7 +22,7 @@ const props = defineProps<{
22
22
  chart: Chart | ChartForApi
23
23
  series: {
24
24
  data: Record<string, Array<Record<string, unknown>>>
25
- columns: Record<string, Array<string>>
25
+ columns: Record<string, Array<{ name: string, type: string }>>
26
26
  }
27
27
  }>()
28
28
 
@@ -32,7 +32,7 @@ let echartsInstance: EChartsType | null = null
32
32
 
33
33
  function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' {
34
34
  if (!xAxis) return 'category'
35
- return (xAxis.type ?? 'discrete') === 'continuous' ? 'value' : 'category'
35
+ return xAxis.type === 'continuous' ? 'value' : 'category'
36
36
  }
37
37
 
38
38
  function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefined {
@@ -53,7 +53,8 @@ const echartsOption = computed(() => {
53
53
  const yColumn = s.aggregate_y ? `${s.column_y}__${s.aggregate_y}` : s.column_y
54
54
  const resourceId = s.resource_id
55
55
 
56
- if (!xColumn || !yColumn || !resourceId || !s.type || !props.series.data[resourceId] || !props.series.columns[resourceId]) {
56
+ const resourceColumns = props.series.columns[resourceId]
57
+ if (!xColumn || !yColumn || !resourceId || !s.type || !props.series.data[resourceId] || !resourceColumns) {
57
58
  return null
58
59
  }
59
60
 
@@ -110,7 +111,7 @@ const echartsOption = computed(() => {
110
111
  return {
111
112
  series: {
112
113
  type: s.type === 'histogram' ? 'bar' : 'line',
113
- dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
114
+ dimensions: s.aggregate_y ? [xColumn, yColumn] : resourceColumns.map(c => c.name),
114
115
  name: yColumn,
115
116
  encode: {
116
117
  x: xColumn,
@@ -119,7 +120,7 @@ const echartsOption = computed(() => {
119
120
  } as LineSeriesOption | BarSeriesOption,
120
121
  data: {
121
122
  source: sortedData,
122
- dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
123
+ dimensions: s.aggregate_y ? [xColumn, yColumn] : resourceColumns.map(c => c.name),
123
124
  },
124
125
  }
125
126
  })
@@ -137,10 +138,10 @@ const echartsOption = computed(() => {
137
138
 
138
139
  return {
139
140
  dataset: [...seriesData.data],
140
- title: {
141
- text: props.chart.title,
142
- left: 'center',
141
+ textStyle: {
142
+ fontFamily: 'Marianne, arial, sans-serif',
143
143
  },
144
+ color: '#000091',
144
145
  tooltip: {
145
146
  trigger: 'axis' as const,
146
147
  formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
@@ -154,7 +155,7 @@ const echartsOption = computed(() => {
154
155
  },
155
156
  },
156
157
  legend: {
157
- show: seriesData.series.length > 1,
158
+ show: false,
158
159
  bottom: 0,
159
160
  },
160
161
  grid: {
@@ -18,22 +18,20 @@
18
18
  </template>
19
19
 
20
20
  <script setup lang="ts">
21
- import { reactive, ref, watch } from 'vue'
21
+ import { computed, reactive, ref, watch } from 'vue'
22
22
  import ChartViewer from './ChartViewer.vue'
23
23
  import LoadingBlock from '../../components/LoadingBlock.vue'
24
24
  import { useComponentsConfig } from '../../config'
25
25
  import { fetchTabularData, useGetProfile } from '../../functions/tabularApi'
26
- import type { Chart, ChartForApi } from '../../types/visualizations'
26
+ import type { Chart, ChartForApi, ColumnsDefinition } from '../../types/visualizations'
27
27
  import { useTranslation } from '../../composables/useTranslation'
28
+ import { watchDebounced } from '@vueuse/shared'
29
+ import { buildColumnsFromProfile } from '../../functions/charts'
28
30
 
29
31
  const props = defineProps<{
30
32
  chart: Chart | ChartForApi
31
33
  }>()
32
34
 
33
- const emit = defineEmits<{
34
- columns: [columns: Record<string, Array<string>>]
35
- }>()
36
-
37
35
  const { t } = useTranslation()
38
36
  const config = useComponentsConfig()
39
37
  const getProfile = useGetProfile()
@@ -45,12 +43,18 @@ const pendingOperations = ref(0)
45
43
 
46
44
  const series = reactive<{
47
45
  data: Record<string, Array<Record<string, unknown>>>
48
- columns: Record<string, Array<string>>
46
+ columns: ColumnsDefinition
49
47
  }>({
50
48
  data: {},
51
49
  columns: {},
52
50
  })
53
51
 
52
+ const resourceIds = computed(() => {
53
+ return props.chart.series
54
+ .filter(s => s.resource_id)
55
+ .map(s => s.resource_id)
56
+ })
57
+
54
58
  async function fetchSeriesProfile() {
55
59
  pendingOperations.value++
56
60
  status.value = 'pending'
@@ -78,7 +82,7 @@ async function fetchSeriesProfile() {
78
82
  .map(r => r.value)
79
83
  series.columns = Object.fromEntries(results.map(result => [
80
84
  result.id,
81
- result.profile.profile.header,
85
+ buildColumnsFromProfile(result.profile),
82
86
  ]))
83
87
  }
84
88
  catch (err) {
@@ -86,6 +90,7 @@ async function fetchSeriesProfile() {
86
90
  status.value = 'error'
87
91
  console.error(err)
88
92
  series.columns = {}
93
+ series.data = {}
89
94
  }
90
95
  finally {
91
96
  pendingOperations.value--
@@ -148,17 +153,16 @@ async function fetchSeriesData() {
148
153
  }
149
154
  }
150
155
 
151
- watch(() => props.chart.series, async () => {
156
+ watch(resourceIds, async () => {
152
157
  await fetchSeriesProfile()
153
- }, { immediate: true, deep: true })
158
+ }, { immediate: true })
154
159
 
155
- watch([() => props.chart.series, () => props.chart.x_axis.column_x], async () => {
160
+ watchDebounced([
161
+ () => props.chart.series,
162
+ () => props.chart.x_axis.column_x,
163
+ ], async () => {
156
164
  await fetchSeriesData()
157
- }, { immediate: true, deep: true })
158
-
159
- watch(() => series.columns, () => {
160
- emit('columns', series.columns)
161
- })
165
+ }, { debounce: config.searchDebounce ?? 300, immediate: true, deep: true })
162
166
 
163
167
  watch(pendingOperations, (count) => {
164
168
  if (count === 0) {
@@ -613,7 +613,7 @@ import { useFetch } from '../../functions/api'
613
613
  import { useComponentsConfig } from '../../config'
614
614
  import { useTranslation } from '../../composables/useTranslation'
615
615
  import { injectTabularProfile } from '../../composables/useTabularProfile'
616
- import { buildTypeConfig, buildFormatConfig, humanizeFormat, GENERIC_FORMATS, hasFilterForColumn as _hasFilterForColumn, isTruthy, isFalsy } from '../../functions/tabular'
616
+ import { buildTypeConfig, buildFormatConfig, humanizeFormat, GENERIC_FORMATS, hasFilterForColumn as _hasFilterForColumn, isTruthy, isFalsy, resolveColumnType } from '../../functions/tabular'
617
617
  import ClientOnly from '../ClientOnly.vue'
618
618
  import SimpleBanner from '../SimpleBanner.vue'
619
619
  import BrandedButton from '../BrandedButton.vue'
@@ -999,23 +999,18 @@ function toggleMobileFilterColumn(col: string) {
999
999
  }
1000
1000
 
1001
1001
  // Column type helpers
1002
- function resolveColumnType(col: string): ColumnType {
1002
+ function getColumnTypeFromName(col: string): ColumnType {
1003
1003
  const profile = profileData.value?.profile
1004
1004
  if (!profile) return 'text'
1005
1005
  const colInfo = profile.columns[col]
1006
1006
  if (!colInfo) return 'text'
1007
- if (['int', 'float'].includes(colInfo.python_type)) return 'number'
1008
- if (colInfo.format === 'year') return 'date'
1009
- if (['date', 'datetime'].includes(colInfo.python_type)) return 'date'
1010
- if (colInfo.python_type === 'bool') return 'boolean'
1011
- if (profile.categorical.includes(col)) return 'categorical'
1012
- return 'text'
1007
+ return resolveColumnType(colInfo, profile.categorical.includes(col))
1013
1008
  }
1014
1009
 
1015
1010
  const columnTypesMap = computed(() => {
1016
1011
  const map: Record<string, ColumnType> = {}
1017
1012
  for (const col of allColumns.value) {
1018
- map[col] = resolveColumnType(col)
1013
+ map[col] = getColumnTypeFromName(col)
1019
1014
  }
1020
1015
  return map
1021
1016
  })
@@ -19,6 +19,9 @@ export type TabularRow = Record<string, unknown> & { __id: number }
19
19
  /** Response from /api/resources/{rid}/profile/ */
20
20
  export type TabularProfileResponse = {
21
21
  profile: TabularProfile
22
+ deleted_at: string | null
23
+ dataset_id: string
24
+ indexes: null
22
25
  }
23
26
 
24
27
  export type TabularProfile = {
@@ -31,8 +34,8 @@ export type TabularProfile = {
31
34
  categorical: string[]
32
35
  total_lines: number
33
36
  nb_duplicates: number
34
- columns_fields: unknown
35
- columns_labels: unknown
37
+ columns_fields: Record<string, TabularColumnInfo>
38
+ columns_labels: Record<string, TabularColumnInfo>
36
39
  header_row_idx: number
37
40
  heading_columns: number
38
41
  trailing_columns: number
@@ -1,4 +1,6 @@
1
- import type { Chart, ChartForm, ChartForApi, Filter } from '../types/visualizations'
1
+ import type { Chart, ChartForm, ChartForApi, Filter, ColumnDefinition } from '../types/visualizations'
2
+ import type { TabularProfile } from '../components/TabularExplorer/types'
3
+ import { resolveColumnType } from './tabular'
2
4
 
3
5
  export function toChartForm(chart: Chart) {
4
6
  const seriesFilter = chart.series[0]?.filters as Filter | null
@@ -66,3 +68,12 @@ export function toChartApi(chartForm: ChartForm): ChartForApi {
66
68
  extras: chartForm.extras,
67
69
  }
68
70
  }
71
+
72
+ export function buildColumnsFromProfile(profile: { profile: TabularProfile }): Array<ColumnDefinition> {
73
+ return profile.profile.header.map((name) => {
74
+ const colInfo = profile.profile.columns[name]
75
+ const isCategorical = profile.profile.categorical.includes(name)
76
+ const colType = resolveColumnType(colInfo ?? { python_type: 'unknown', format: undefined }, isCategorical)
77
+ return { name, type: colType }
78
+ })
79
+ }
@@ -45,6 +45,15 @@ export function buildTypeConfig(t: TranslationFunction): Record<ColumnType, Type
45
45
  }
46
46
  }
47
47
 
48
+ export function resolveColumnType(colInfo: { python_type: string, format?: string }, isCategorical: boolean): ColumnType {
49
+ if (['int', 'float'].includes(colInfo.python_type)) return 'number'
50
+ if (colInfo.format === 'year') return 'date'
51
+ if (['date', 'datetime'].includes(colInfo.python_type)) return 'date'
52
+ if (colInfo.python_type === 'bool') return 'boolean'
53
+ if (isCategorical) return 'categorical'
54
+ return 'text'
55
+ }
56
+
48
57
  // csv-detective formats that carry no semantic meaning beyond the generic
49
58
  // display type already resolved by `ColumnType` — these fall back to
50
59
  // `buildTypeConfig` (Texte / Nombre / Date / Booléen) in the header.
@@ -1,7 +1,7 @@
1
1
  import { ofetch } from 'ofetch'
2
2
  import { useComponentsConfig, type PluginConfig } from '../config'
3
3
  import type { GenericFilter } from '../types/visualizations'
4
- import type { SortConfig } from '../components/TabularExplorer/types'
4
+ import type { SortConfig, TabularProfileResponse } from '../components/TabularExplorer/types'
5
5
 
6
6
  export type { SortConfig }
7
7
 
@@ -31,48 +31,6 @@ export type FetchTabularDataOptions = {
31
31
  filters?: GenericFilter | undefined
32
32
  }
33
33
 
34
- export type TabularProfileResponse = {
35
- profile: {
36
- header: Array<string>
37
- columns: Record<string, {
38
- score: number
39
- format: string
40
- python_type: string
41
- }>
42
- formats: Record<string, Array<string>>
43
- profile: Record<string, {
44
- tops: Array<{ count: number, value: string }>
45
- nb_distinct: number
46
- nb_missing_values: number
47
- min?: number
48
- max?: number
49
- std?: number
50
- mean?: number
51
- }>
52
- encoding: string
53
- separator: string
54
- categorical: Array<string>
55
- total_lines: number
56
- nb_duplicates: number
57
- columns_fields: Record<string, {
58
- score: number
59
- format: string
60
- python_type: string
61
- }>
62
- columns_labels: Record<string, {
63
- score: number
64
- format: string
65
- python_type: string
66
- }>
67
- header_row_idx: number
68
- heading_columns: number
69
- trailing_columns: number
70
- }
71
- deleted_at: string | null
72
- dataset_id: string
73
- indexes: null
74
- }
75
-
76
34
  /**
77
35
  * Call Tabular-api to get table content with options object
78
36
  */
package/src/main.ts CHANGED
@@ -23,7 +23,8 @@ import type { Site } from './types/site'
23
23
  import type { Weight, WellType } from './types/ui'
24
24
  import type { User, UserReference } from './types/users'
25
25
  import type { Report, ReportSubject, ReportReason } from './types/reports'
26
- import type { Chart, ChartForm, ChartForApi, FilterCondition, Filter, AndFilters, GenericFilter, XAxisType, XAxisSortBy, SortDirection, XAxis, XAxisForm, UnitPosition, YAxis, DataSeriesType, DataSeries, DataSeriesForm } from './types/visualizations'
26
+ import type { Chart, ChartForm, ChartForApi, FilterCondition, Filter, AndFilters, GenericFilter, XAxisType, XAxisSortBy, SortDirection, XAxis, XAxisForm, UnitPosition, YAxis, DataSeriesType, DataSeries, DataSeriesForm, CombinedSort, ColumnDefinition, ColumnsDefinition } from './types/visualizations'
27
+ import type { ColumnType } from './components/TabularExplorer/types'
27
28
  import type { GlobalSearchConfig, SearchType, SearchTypeConfig, SortOption, HiddenFilter, BuiltInFilterKey, DatasetSearchConfig, DatasetSearchFilters, DataserviceSearchConfig, DataserviceSearchFilters, ReuseSearchConfig, ReuseSearchFilters, OrganizationSearchConfig, OrganizationSearchFilters, TopicSearchConfig, TopicSearchFilters } from './types/search'
28
29
  import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
29
30
  import { useSearchFilter } from './composables/useSearchFilter'
@@ -251,6 +252,10 @@ export type {
251
252
  Chart,
252
253
  ChartForm,
253
254
  ChartForApi,
255
+ CombinedSort,
256
+ ColumnDefinition,
257
+ ColumnType,
258
+ ColumnsDefinition,
254
259
  FilterCondition,
255
260
  Filter,
256
261
  AndFilters,
@@ -1,5 +1,6 @@
1
1
  import type { TabularAggregateType } from '../functions/tabularApi'
2
2
  import type { Owned, OwnedWithId } from './owned'
3
+ import type { ColumnType } from '../components/TabularExplorer/types'
3
4
 
4
5
  export type FilterCondition = 'exact' | 'differs' | 'is_null' | 'is_not_null' | 'greater' | 'less' | 'strictly_greater' | 'strictly_less'
5
6
  export type Filter = {
@@ -87,3 +88,6 @@ export type ChartForm = Omit<ChartForApi, 'x_axis' | 'series' | 'owner' | 'organ
87
88
  chart_type?: DataSeriesType | null
88
89
  filter: GenericFilter | null
89
90
  }
91
+
92
+ export type ColumnDefinition = { name: string, type: ColumnType }
93
+ export type ColumnsDefinition = Record<string, Array<ColumnDefinition>>