@datagouv/components-next 1.0.2-dev.4 → 1.0.2-dev.5
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-c1cUKkQR.js → Datafair.client-E5D6ePRC.js} +1 -1
- package/dist/{JsonPreview.client-CAs9XTCX.js → JsonPreview.client-C-6eBbPw.js} +1 -1
- package/dist/{MapContainer.client-DjjvdKBp.js → MapContainer.client-DRkAmdOc.js} +2 -2
- package/dist/{PdfPreview.client-CsvKU0Aq.js → PdfPreview.client-C-w6-w44.js} +1 -1
- package/dist/{Pmtiles.client-uqg1fwOl.js → Pmtiles.client-BR7_ldHY.js} +1 -1
- package/dist/Swagger.client-D4-F6yEf.js +4 -0
- package/dist/{XmlPreview.client-BWbKzLte.js → XmlPreview.client-Dl2VCgXF.js} +2 -2
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +87 -91
- package/dist/components.css +1 -1
- package/dist/{index-PMeuFwWj.js → index-SrYZwgCT.js} +1 -1
- package/dist/{main-ByqZlhiZ.js → main-B2kXxWRG.js} +12346 -12354
- package/dist/{vue3-xml-viewer.common-DFrGHXJC.js → vue3-xml-viewer.common-BRxsqI9j.js} +1 -1
- package/package.json +2 -4
- package/src/components/ResourceAccordion/Preview.vue +1 -1
- package/src/functions/tabularApi.ts +7 -84
- package/src/main.ts +0 -20
- package/dist/Swagger.client-BGrkka3l.js +0 -4
- package/src/components/Chart/ChartViewer.vue +0 -152
- package/src/components/Chart/ChartViewerWrapper.vue +0 -194
- package/src/types/visualizations.ts +0 -84
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagouv/components-next",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -17,15 +17,14 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@floating-ui/vue": "^1.1.8",
|
|
20
|
+
"@types/leaflet": "^1.9.17",
|
|
20
21
|
"@headlessui/vue": "^1.7.23",
|
|
21
22
|
"@remixicon/vue": "^4.5.0",
|
|
22
23
|
"@types/hast": "^3.0.4",
|
|
23
|
-
"@types/leaflet": "^1.9.17",
|
|
24
24
|
"@vueuse/core": "^13.1.0",
|
|
25
25
|
"@vueuse/router": "^13.1.0",
|
|
26
26
|
"chart.js": "^4.4.8",
|
|
27
27
|
"dompurify": "^3.2.5",
|
|
28
|
-
"echarts": "^6.0.0",
|
|
29
28
|
"geopf-extensions-openlayers": "^1.0.0-beta.5",
|
|
30
29
|
"leaflet": "^1.9.4",
|
|
31
30
|
"maplibre-gl": "^5.6.2",
|
|
@@ -51,7 +50,6 @@
|
|
|
51
50
|
"unist-util-visit": "^5.0.0",
|
|
52
51
|
"vue": "^3.5.13",
|
|
53
52
|
"vue-content-loader": "^2.0.1",
|
|
54
|
-
"vue-echarts": "^8.0.1",
|
|
55
53
|
"vue-router": "^4.5.0",
|
|
56
54
|
"vue-sonner": "^2.0.9",
|
|
57
55
|
"vue3-json-viewer": "^2.4.1",
|
|
@@ -147,7 +147,7 @@ async function getTableInfos(page: number, sortConfig?: SortConfig) {
|
|
|
147
147
|
try {
|
|
148
148
|
// Check that this function return wanted data
|
|
149
149
|
const response = await getData(config, props.resource.id, page, sortConfig)
|
|
150
|
-
if ('data' in response && response.data &&
|
|
150
|
+
if ('data' in response && response.data && response.data.length > 0) {
|
|
151
151
|
// Update existing rows
|
|
152
152
|
rows.value = response.data
|
|
153
153
|
columns.value = Object.keys(response.data[0]).filter(item => item !== '__id')
|
|
@@ -6,92 +6,15 @@ export type SortConfig = {
|
|
|
6
6
|
type: string
|
|
7
7
|
} | null
|
|
8
8
|
|
|
9
|
-
export type TabularDataResponse = {
|
|
10
|
-
data: Array<Record<string, unknown>>
|
|
11
|
-
meta: { total: number }
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type TabularAggregateType = 'avg' | 'sum' | 'count' | 'min' | 'max'
|
|
15
|
-
|
|
16
|
-
export type FetchTabularDataOptions = {
|
|
17
|
-
resourceId: string
|
|
18
|
-
page?: number
|
|
19
|
-
pageSize?: number
|
|
20
|
-
columns?: Array<string> | undefined
|
|
21
|
-
sort?: SortConfig
|
|
22
|
-
groupBy?: string | undefined
|
|
23
|
-
aggregation?: {
|
|
24
|
-
column: string
|
|
25
|
-
type: TabularAggregateType
|
|
26
|
-
} | undefined
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type TabularProfileResponse = {
|
|
30
|
-
profile: {
|
|
31
|
-
header: Array<string>
|
|
32
|
-
columns: Record<string, {
|
|
33
|
-
score: number
|
|
34
|
-
format: string
|
|
35
|
-
python_type: string
|
|
36
|
-
}>
|
|
37
|
-
formats: Record<string, Array<string>>
|
|
38
|
-
profile: Record<string, {
|
|
39
|
-
tops: Array<{ count: number, value: string }>
|
|
40
|
-
nb_distinct: number
|
|
41
|
-
nb_missing_values: number
|
|
42
|
-
min?: number
|
|
43
|
-
max?: number
|
|
44
|
-
std?: number
|
|
45
|
-
mean?: number
|
|
46
|
-
}>
|
|
47
|
-
encoding: string
|
|
48
|
-
separator: string
|
|
49
|
-
categorical: Array<string>
|
|
50
|
-
total_lines: number
|
|
51
|
-
nb_duplicates: number
|
|
52
|
-
columns_fields: Record<string, {
|
|
53
|
-
score: number
|
|
54
|
-
format: string
|
|
55
|
-
python_type: string
|
|
56
|
-
}>
|
|
57
|
-
columns_labels: Record<string, {
|
|
58
|
-
score: number
|
|
59
|
-
format: string
|
|
60
|
-
python_type: string
|
|
61
|
-
}>
|
|
62
|
-
header_row_idx: number
|
|
63
|
-
heading_columns: number
|
|
64
|
-
trailing_columns: number
|
|
65
|
-
}
|
|
66
|
-
deleted_at: string | null
|
|
67
|
-
dataset_id: string
|
|
68
|
-
indexes: null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Call Tabular-api to get table content with options object
|
|
73
|
-
*/
|
|
74
|
-
export async function fetchTabularData(config: PluginConfig, options: FetchTabularDataOptions): Promise<TabularDataResponse> {
|
|
75
|
-
const page = options.page ?? 1
|
|
76
|
-
const pageSize = options.pageSize ?? config.tabularApiPageSize ?? 15
|
|
77
|
-
let url = `${config.tabularApiUrl}/api/resources/${options.resourceId}/data/?page=${page}&page_size=${pageSize}`
|
|
78
|
-
if (options.columns) {
|
|
79
|
-
url += `&columns=${options.columns.join(',')}`
|
|
80
|
-
}
|
|
81
|
-
if (options.sort) {
|
|
82
|
-
url += `&${options.sort.column}__sort=${options.sort.type}`
|
|
83
|
-
}
|
|
84
|
-
if (options.groupBy && options.aggregation?.type) {
|
|
85
|
-
url += `&${options.groupBy}__groupby&${options.aggregation.column}__${options.aggregation.type}`
|
|
86
|
-
}
|
|
87
|
-
return await ofetch<TabularDataResponse>(url)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
9
|
/**
|
|
91
10
|
* Call Tabular-api to get table content
|
|
92
11
|
*/
|
|
93
|
-
export function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig) {
|
|
94
|
-
|
|
12
|
+
export async function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig) {
|
|
13
|
+
let url = `${config.tabularApiUrl}/api/resources/${id}/data/?page=${page}&page_size=${config.tabularApiPageSize || 15}`
|
|
14
|
+
if (sortConfig) {
|
|
15
|
+
url = url + `&${sortConfig.column}__sort=${sortConfig.type}`
|
|
16
|
+
}
|
|
17
|
+
return await ofetch(url)
|
|
95
18
|
}
|
|
96
19
|
|
|
97
20
|
/**
|
|
@@ -99,5 +22,5 @@ export function getData(config: PluginConfig, id: string, page: number, sortConf
|
|
|
99
22
|
*/
|
|
100
23
|
export function useGetProfile() {
|
|
101
24
|
const config = useComponentsConfig()
|
|
102
|
-
return (id: string) => ofetch
|
|
25
|
+
return (id: string) => ofetch(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
|
|
103
26
|
}
|
package/src/main.ts
CHANGED
|
@@ -23,7 +23,6 @@ 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'
|
|
27
26
|
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
|
|
28
27
|
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
|
|
29
28
|
|
|
@@ -101,7 +100,6 @@ import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
|
|
|
101
100
|
export { Toaster, toast } from 'vue-sonner'
|
|
102
101
|
|
|
103
102
|
export * from './composables/useActiveDescendant'
|
|
104
|
-
export * from './composables/useDebouncedRef'
|
|
105
103
|
export * from './composables/useMetrics'
|
|
106
104
|
export * from './composables/useReuseType'
|
|
107
105
|
export * from './composables/useTranslation'
|
|
@@ -123,7 +121,6 @@ export * from './functions/resources'
|
|
|
123
121
|
export * from './functions/reuses'
|
|
124
122
|
export * from './functions/schemas'
|
|
125
123
|
export * from './functions/users'
|
|
126
|
-
export * from './functions/tabularApi'
|
|
127
124
|
export * from './types/access_types'
|
|
128
125
|
|
|
129
126
|
export type {
|
|
@@ -221,23 +218,6 @@ export type {
|
|
|
221
218
|
ValidataError,
|
|
222
219
|
Weight,
|
|
223
220
|
WellType,
|
|
224
|
-
Chart,
|
|
225
|
-
ChartForm,
|
|
226
|
-
ChartForApi,
|
|
227
|
-
FilterCondition,
|
|
228
|
-
Filter,
|
|
229
|
-
AndFilters,
|
|
230
|
-
GenericFilter,
|
|
231
|
-
XAxisType,
|
|
232
|
-
XAxisSortBy,
|
|
233
|
-
SortDirection,
|
|
234
|
-
XAxis,
|
|
235
|
-
XAxisForm,
|
|
236
|
-
UnitPosition,
|
|
237
|
-
YAxis,
|
|
238
|
-
DataSeriesType,
|
|
239
|
-
DataSeries,
|
|
240
|
-
DataSeriesForm,
|
|
241
221
|
}
|
|
242
222
|
|
|
243
223
|
export {
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<VChart
|
|
3
|
-
class="w-full min-h-96"
|
|
4
|
-
:option="echartsOption"
|
|
5
|
-
autoresize
|
|
6
|
-
/>
|
|
7
|
-
</template>
|
|
8
|
-
|
|
9
|
-
<script setup lang="ts">
|
|
10
|
-
import { format, use, type ComposeOption } from 'echarts/core'
|
|
11
|
-
import { CanvasRenderer } from 'echarts/renderers'
|
|
12
|
-
import { LineChart, BarChart, type BarSeriesOption, type LineSeriesOption } from 'echarts/charts'
|
|
13
|
-
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent } from 'echarts/components'
|
|
14
|
-
import VChart from 'vue-echarts'
|
|
15
|
-
import { computed } from 'vue'
|
|
16
|
-
import { summarize } from '../../functions/helpers'
|
|
17
|
-
import type { Chart, DataSeries, XAxis, YAxis, ChartForm, XAxisForm } from '../../types/visualizations'
|
|
18
|
-
|
|
19
|
-
use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent])
|
|
20
|
-
|
|
21
|
-
const props = defineProps<{
|
|
22
|
-
chart: Chart | ChartForm
|
|
23
|
-
series: {
|
|
24
|
-
data: Record<string, Array<Record<string, unknown>>>
|
|
25
|
-
columns: Record<string, Array<string>>
|
|
26
|
-
}
|
|
27
|
-
}>()
|
|
28
|
-
|
|
29
|
-
function mapSeriesType(type: DataSeries['type']): 'line' | 'bar' {
|
|
30
|
-
return (type ?? 'line') === 'histogram' ? 'bar' : 'line'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' {
|
|
34
|
-
if (!xAxis) return 'category'
|
|
35
|
-
return (xAxis.type ?? 'discrete') === 'continuous' ? 'value' : 'category'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefined {
|
|
39
|
-
return (value: number) => {
|
|
40
|
-
const v = summarize(value)
|
|
41
|
-
if (!yAxis.unit) return v
|
|
42
|
-
if (yAxis.unit_position === 'prefix') return `${yAxis.unit} ${v}`
|
|
43
|
-
return `${v} ${yAxis.unit}`
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const echartsOption = computed(() => {
|
|
48
|
-
const seriesCount = props.chart.series.length
|
|
49
|
-
if (!props.chart.series || seriesCount === 0) return
|
|
50
|
-
|
|
51
|
-
// Create series configuration with data mapping
|
|
52
|
-
const seriesData = props.chart.series.map((s) => {
|
|
53
|
-
const xColumn = s.column_x_name_override ?? props.chart.x_axis.column_x
|
|
54
|
-
const yColumn = s.aggregate_y ? `${s.column_y}__${s.aggregate_y}` : s.column_y
|
|
55
|
-
const resourceId = s.resource_id
|
|
56
|
-
const seriesType = s.type
|
|
57
|
-
|
|
58
|
-
if (!xColumn || !yColumn || !resourceId || !seriesType || !props.series.data[resourceId] || !props.series.columns[resourceId]) {
|
|
59
|
-
return null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Sort data before passing to ECharts to avoid transform issues
|
|
63
|
-
const sortedData = [...props.series.data[resourceId]]
|
|
64
|
-
const sortBy = props.chart.x_axis.sort_x_by
|
|
65
|
-
const sortDirection = props.chart.x_axis.sort_x_direction ?? 'asc'
|
|
66
|
-
|
|
67
|
-
if (sortBy && sortDirection && props.chart.x_axis.column_x) {
|
|
68
|
-
const sortKey = sortBy === 'axis_x' ? xColumn : yColumn
|
|
69
|
-
sortedData.sort((a, b) => {
|
|
70
|
-
const valA = a[sortKey] as number
|
|
71
|
-
const valB = b[sortKey] as number
|
|
72
|
-
if (valA < valB) return sortDirection === 'asc' ? -1 : 1
|
|
73
|
-
if (valA > valB) return sortDirection === 'asc' ? 1 : -1
|
|
74
|
-
return 0
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
series: {
|
|
80
|
-
type: mapSeriesType(seriesType),
|
|
81
|
-
dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
|
|
82
|
-
name: s.column_y,
|
|
83
|
-
encode: {
|
|
84
|
-
x: xColumn,
|
|
85
|
-
y: yColumn,
|
|
86
|
-
},
|
|
87
|
-
} as LineSeriesOption | BarSeriesOption,
|
|
88
|
-
data: {
|
|
89
|
-
source: sortedData,
|
|
90
|
-
dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
}).filter(Boolean).reduce((acc: { series: Array<LineSeriesOption | BarSeriesOption>, data: Array<Record<string, unknown>> }, curr) => {
|
|
94
|
-
if (curr) {
|
|
95
|
-
acc.series.push(curr.series)
|
|
96
|
-
acc.data.push(curr.data)
|
|
97
|
-
}
|
|
98
|
-
return acc
|
|
99
|
-
}, {
|
|
100
|
-
series: [],
|
|
101
|
-
data: [],
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
dataset: [...seriesData.data],
|
|
106
|
-
title: {
|
|
107
|
-
text: props.chart.title,
|
|
108
|
-
left: 'center',
|
|
109
|
-
},
|
|
110
|
-
tooltip: {
|
|
111
|
-
trigger: 'axis' as const,
|
|
112
|
-
formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
|
|
113
|
-
let tooltip = ''
|
|
114
|
-
for (const param of params) {
|
|
115
|
-
const keys = Object.keys(param.value)
|
|
116
|
-
const col = keys.find(key => key.startsWith(param.seriesName))!
|
|
117
|
-
const formatter = new Intl.NumberFormat('fr-FR')
|
|
118
|
-
tooltip += `${format.encodeHTML(param.axisValueLabel)}: <strong>${formatter.format(Number(param.value[col]))}</strong><br>`
|
|
119
|
-
}
|
|
120
|
-
return tooltip
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
legend: {
|
|
124
|
-
bottom: 0,
|
|
125
|
-
},
|
|
126
|
-
grid: {
|
|
127
|
-
top: 60,
|
|
128
|
-
bottom: 40,
|
|
129
|
-
left: 20,
|
|
130
|
-
right: 20,
|
|
131
|
-
containLabel: true,
|
|
132
|
-
},
|
|
133
|
-
xAxis: {
|
|
134
|
-
type: mapXAxisType(props.chart.x_axis),
|
|
135
|
-
name: (props.chart.x_axis as XAxis).column_x,
|
|
136
|
-
},
|
|
137
|
-
yAxis: {
|
|
138
|
-
type: 'value' as const,
|
|
139
|
-
name: props.chart.y_axis.label ?? undefined,
|
|
140
|
-
min: props.chart.y_axis.min ?? undefined,
|
|
141
|
-
max: props.chart.y_axis.max ?? undefined,
|
|
142
|
-
axisLabel: {
|
|
143
|
-
formatter: buildYAxisFormatter(props.chart.y_axis),
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
series: seriesData.series,
|
|
147
|
-
} satisfies ComposeOption<
|
|
148
|
-
| BarSeriesOption
|
|
149
|
-
| LineSeriesOption
|
|
150
|
-
>
|
|
151
|
-
})
|
|
152
|
-
</script>
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<LoadingBlock
|
|
3
|
-
:status="status"
|
|
4
|
-
:data="series"
|
|
5
|
-
>
|
|
6
|
-
<template #default="{ data }">
|
|
7
|
-
<ChartViewer
|
|
8
|
-
:chart="chart"
|
|
9
|
-
:series="data"
|
|
10
|
-
/>
|
|
11
|
-
</template>
|
|
12
|
-
<template #error>
|
|
13
|
-
<div class="text-center py-8 text-gray-500">
|
|
14
|
-
Une erreur est survenue lors du chargement des données du graphique.
|
|
15
|
-
</div>
|
|
16
|
-
</template>
|
|
17
|
-
</LoadingBlock>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<script setup lang="ts">
|
|
21
|
-
import { reactive, ref, watch } from 'vue'
|
|
22
|
-
import ChartViewer from './ChartViewer.vue'
|
|
23
|
-
import LoadingBlock from '../../components/LoadingBlock.vue'
|
|
24
|
-
import { useComponentsConfig } from '../../config'
|
|
25
|
-
import { fetchTabularData, useGetProfile, type TabularDataResponse, type TabularProfileResponse } from '../../functions/tabularApi'
|
|
26
|
-
import type { Chart, ChartForm } from '../../types/visualizations'
|
|
27
|
-
|
|
28
|
-
const chart = defineModel<Chart | ChartForm>({ required: true })
|
|
29
|
-
|
|
30
|
-
const emit = defineEmits<{
|
|
31
|
-
columns: [columns: Record<string, Array<string>>]
|
|
32
|
-
}>()
|
|
33
|
-
|
|
34
|
-
const config = useComponentsConfig()
|
|
35
|
-
const getProfile = useGetProfile()
|
|
36
|
-
|
|
37
|
-
// Loading and error states
|
|
38
|
-
const status = ref<'idle' | 'pending' | 'success' | 'error'>('idle')
|
|
39
|
-
const error = ref<Error | null>(null)
|
|
40
|
-
|
|
41
|
-
// Dataset source for the chart
|
|
42
|
-
const series = reactive<{
|
|
43
|
-
data: Record<string, Array<Record<string, unknown>>>
|
|
44
|
-
columns: Record<string, Array<string>>
|
|
45
|
-
}>({
|
|
46
|
-
data: {},
|
|
47
|
-
columns: {},
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
async function fetchSeriesProfile() {
|
|
51
|
-
status.value = 'pending'
|
|
52
|
-
error.value = null
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
if (chart.value.series.length === 0) {
|
|
56
|
-
status.value = 'success'
|
|
57
|
-
series.data = {}
|
|
58
|
-
series.columns = {}
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Fetch data for all series in parallel
|
|
63
|
-
const fetchPromises = chart.value.series.map(async (serie) => {
|
|
64
|
-
if (!serie.resource_id) return
|
|
65
|
-
return {
|
|
66
|
-
id: serie.resource_id,
|
|
67
|
-
profile: await getProfile(serie.resource_id),
|
|
68
|
-
}
|
|
69
|
-
}).filter(Boolean) as Array<Promise<{
|
|
70
|
-
id: string
|
|
71
|
-
profile: TabularProfileResponse
|
|
72
|
-
}>>
|
|
73
|
-
|
|
74
|
-
const results = (await Promise.allSettled(fetchPromises))
|
|
75
|
-
.filter(r => r.status === 'fulfilled')
|
|
76
|
-
.map(r => r.value)
|
|
77
|
-
series.columns = Object.fromEntries(results.map(result => [
|
|
78
|
-
result.id,
|
|
79
|
-
result.profile.profile.header,
|
|
80
|
-
]))
|
|
81
|
-
if (results.length > 0) {
|
|
82
|
-
const columns = results[0]?.profile.profile.header ?? []
|
|
83
|
-
const firstColumn = columns.filter(c => c !== '__id')[0]
|
|
84
|
-
if (!chart.value.x_axis.column_x && firstColumn) {
|
|
85
|
-
chart.value.x_axis.column_x = firstColumn
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
status.value = 'success'
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
error.value = err instanceof Error ? err : new Error('Failed to fetch series profile')
|
|
92
|
-
status.value = 'error'
|
|
93
|
-
console.log(err)
|
|
94
|
-
series.columns = {}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function fetchSeriesData() {
|
|
99
|
-
status.value = 'pending'
|
|
100
|
-
error.value = null
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
if (chart.value.series.length === 0 || !chart.value.x_axis.column_x) {
|
|
104
|
-
status.value = 'success'
|
|
105
|
-
series.data = {}
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Fetch data for all series in parallel
|
|
110
|
-
const fetchPromises = chart.value.series.map(async (serie) => {
|
|
111
|
-
const xColumn = serie.column_x_name_override ?? chart.value.x_axis.column_x
|
|
112
|
-
if (!xColumn || !serie.resource_id || !serie.column_y) return
|
|
113
|
-
return {
|
|
114
|
-
id: serie.resource_id,
|
|
115
|
-
profile: await getProfile(serie.resource_id),
|
|
116
|
-
data: await fetchTabularData(config, {
|
|
117
|
-
columns: serie.aggregate_y ? undefined : [xColumn, serie.column_y],
|
|
118
|
-
resourceId: serie.resource_id,
|
|
119
|
-
page: 1,
|
|
120
|
-
pageSize: 100,
|
|
121
|
-
groupBy: xColumn,
|
|
122
|
-
aggregation: serie.column_y && serie.aggregate_y
|
|
123
|
-
? {
|
|
124
|
-
column: serie.column_y,
|
|
125
|
-
type: serie.aggregate_y,
|
|
126
|
-
}
|
|
127
|
-
: undefined,
|
|
128
|
-
}),
|
|
129
|
-
}
|
|
130
|
-
}).filter(Boolean) as Array<Promise<{
|
|
131
|
-
id: string
|
|
132
|
-
data: TabularDataResponse
|
|
133
|
-
}>>
|
|
134
|
-
|
|
135
|
-
const results = (await Promise.allSettled(fetchPromises))
|
|
136
|
-
.filter(r => r.status === 'fulfilled')
|
|
137
|
-
.map(r => r.value)
|
|
138
|
-
// Transform data into echarts format
|
|
139
|
-
series.data = Object.fromEntries(results.map(result => [
|
|
140
|
-
result.id,
|
|
141
|
-
result.data.data,
|
|
142
|
-
]))
|
|
143
|
-
status.value = 'success'
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
error.value = err instanceof Error ? err : new Error('Failed to fetch series data')
|
|
147
|
-
status.value = 'error'
|
|
148
|
-
series.data = {}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Watch for changes in the chart or its series
|
|
153
|
-
watch(() => chart.value.series, async () => {
|
|
154
|
-
await fetchSeriesProfile()
|
|
155
|
-
}, { immediate: true, deep: true })
|
|
156
|
-
|
|
157
|
-
// Watch for changes in the chart or its series
|
|
158
|
-
watch([() => chart.value.series, () => chart.value.x_axis.column_x], async () => {
|
|
159
|
-
await fetchSeriesData()
|
|
160
|
-
}, { immediate: true, deep: true })
|
|
161
|
-
|
|
162
|
-
watch(() => series.columns, () => {
|
|
163
|
-
emit('columns', series.columns)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Combines data from multiple series into a single echarts dataset source
|
|
168
|
-
*/
|
|
169
|
-
// function combineSeriesData(seriesData: Array<TabularDataResponse | null>): Array<Array<unknown>> {
|
|
170
|
-
// if (!seriesData.length || seriesData.every(data => !data || !data.data || data.data.length === 0)) {
|
|
171
|
-
// return []
|
|
172
|
-
// }
|
|
173
|
-
|
|
174
|
-
// // Get all unique columns from all series
|
|
175
|
-
// const allColumns = Array.from(new Set(seriesData.flatMap(data =>
|
|
176
|
-
// data?.data?.flatMap(Object.keys) || []
|
|
177
|
-
// )))
|
|
178
|
-
|
|
179
|
-
// // Build the source array with headers
|
|
180
|
-
// const source: Array<Array<unknown>> = [allColumns]
|
|
181
|
-
|
|
182
|
-
// // Add data rows - for simplicity, we'll use data from the first series
|
|
183
|
-
// // In a real implementation, you'd want to merge/combine the data appropriately
|
|
184
|
-
// const firstData = seriesData.find(data => data?.data?.length)?.data
|
|
185
|
-
// if (firstData) {
|
|
186
|
-
// firstData.forEach(row => {
|
|
187
|
-
// const rowValues = allColumns.map(column => row[column])
|
|
188
|
-
// source.push(rowValues)
|
|
189
|
-
// })
|
|
190
|
-
// }
|
|
191
|
-
|
|
192
|
-
// return source
|
|
193
|
-
// }
|
|
194
|
-
</script>
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { TabularAggregateType } from '../functions/tabularApi'
|
|
2
|
-
import type { Owned, OwnedWithId } from './owned'
|
|
3
|
-
|
|
4
|
-
export type FilterCondition = 'equal' | 'greater'
|
|
5
|
-
|
|
6
|
-
export type Filter = {
|
|
7
|
-
column: string
|
|
8
|
-
condition: FilterCondition
|
|
9
|
-
value?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type AndFilters = {
|
|
13
|
-
filters: Array<Filter | AndFilters>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type GenericFilter = Filter | AndFilters
|
|
17
|
-
|
|
18
|
-
export type XAxisType = 'discrete' | 'continuous'
|
|
19
|
-
|
|
20
|
-
export type XAxisSortBy = 'axis_x' | 'axis_y'
|
|
21
|
-
|
|
22
|
-
export type SortDirection = 'asc' | 'desc'
|
|
23
|
-
|
|
24
|
-
export type XAxis = {
|
|
25
|
-
column_x: string
|
|
26
|
-
sort_x_by: XAxisSortBy | null
|
|
27
|
-
sort_x_direction: SortDirection | null
|
|
28
|
-
type: XAxisType
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type XAxisForm = Omit<XAxis, 'sort_x_by'> & { sort_x_by: XAxisSortBy | '' | null }
|
|
32
|
-
|
|
33
|
-
export type UnitPosition = 'prefix' | 'suffix'
|
|
34
|
-
|
|
35
|
-
export type YAxis = {
|
|
36
|
-
min: number | null
|
|
37
|
-
max: number | null
|
|
38
|
-
label: string | null
|
|
39
|
-
unit: string | null
|
|
40
|
-
unit_position: UnitPosition | null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type DataSeriesType = 'line' | 'histogram'
|
|
44
|
-
|
|
45
|
-
export type DataSeries = {
|
|
46
|
-
type: DataSeriesType
|
|
47
|
-
column_y: string
|
|
48
|
-
aggregate_y: TabularAggregateType | null
|
|
49
|
-
resource_id: string
|
|
50
|
-
column_x_name_override: string | null
|
|
51
|
-
filters: GenericFilter | null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Type for form where aggregate_y can be empty string for select binding
|
|
55
|
-
export type DataSeriesForm = Omit<DataSeries, 'aggregate_y'> & { aggregate_y: TabularAggregateType | '' | null }
|
|
56
|
-
|
|
57
|
-
export type Chart = Owned & {
|
|
58
|
-
id: string
|
|
59
|
-
title: string
|
|
60
|
-
slug: string
|
|
61
|
-
description: string
|
|
62
|
-
private: boolean
|
|
63
|
-
created_at: string
|
|
64
|
-
last_modified: string
|
|
65
|
-
deleted_at: string | null
|
|
66
|
-
uri: string
|
|
67
|
-
page: string
|
|
68
|
-
x_axis: XAxis
|
|
69
|
-
y_axis: YAxis
|
|
70
|
-
series: Array<DataSeries>
|
|
71
|
-
extras: Record<string, unknown>
|
|
72
|
-
permissions: { delete: boolean, edit: boolean, read: boolean }
|
|
73
|
-
metrics: {
|
|
74
|
-
views: number
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export type ChartForApi = OwnedWithId & Pick<Chart, 'title' | 'description' | 'private' | 'x_axis' | 'y_axis' | 'series' | 'extras'>
|
|
79
|
-
|
|
80
|
-
export type ChartForm = Omit<ChartForApi, 'x_axis' | 'series' | 'owner' | 'organization'> & {
|
|
81
|
-
owned: OwnedWithId
|
|
82
|
-
x_axis: XAxisForm
|
|
83
|
-
series: Array<DataSeriesForm>
|
|
84
|
-
}
|