@datagouv/components-next 1.0.2-dev.2 → 1.0.2-dev.4
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-E5D6ePRC.js → Datafair.client-c1cUKkQR.js} +1 -1
- package/dist/{JsonPreview.client-C-6eBbPw.js → JsonPreview.client-CAs9XTCX.js} +1 -1
- package/dist/{MapContainer.client-DRkAmdOc.js → MapContainer.client-DjjvdKBp.js} +2 -2
- package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-CsvKU0Aq.js} +1 -1
- package/dist/{Pmtiles.client-BR7_ldHY.js → Pmtiles.client-uqg1fwOl.js} +1 -1
- package/dist/Swagger.client-BGrkka3l.js +4 -0
- package/dist/{XmlPreview.client-Dl2VCgXF.js → XmlPreview.client-BWbKzLte.js} +2 -2
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +91 -87
- package/dist/components.css +1 -1
- package/dist/{index-SrYZwgCT.js → index-PMeuFwWj.js} +1 -1
- package/dist/{main-B2kXxWRG.js → main-ByqZlhiZ.js} +12354 -12346
- package/dist/{vue3-xml-viewer.common-BRxsqI9j.js → vue3-xml-viewer.common-DFrGHXJC.js} +1 -1
- package/package.json +4 -2
- package/src/components/Chart/ChartViewer.vue +152 -0
- package/src/components/Chart/ChartViewerWrapper.vue +194 -0
- package/src/components/ResourceAccordion/Preview.vue +1 -1
- package/src/functions/tabularApi.ts +84 -7
- package/src/main.ts +20 -0
- package/src/types/visualizations.ts +84 -0
- package/dist/Swagger.client-D4-F6yEf.js +0 -4
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.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@floating-ui/vue": "^1.1.8",
|
|
20
|
-
"@types/leaflet": "^1.9.17",
|
|
21
20
|
"@headlessui/vue": "^1.7.23",
|
|
22
21
|
"@remixicon/vue": "^4.5.0",
|
|
23
22
|
"@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",
|
|
28
29
|
"geopf-extensions-openlayers": "^1.0.0-beta.5",
|
|
29
30
|
"leaflet": "^1.9.4",
|
|
30
31
|
"maplibre-gl": "^5.6.2",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"unist-util-visit": "^5.0.0",
|
|
51
52
|
"vue": "^3.5.13",
|
|
52
53
|
"vue-content-loader": "^2.0.1",
|
|
54
|
+
"vue-echarts": "^8.0.1",
|
|
53
55
|
"vue-router": "^4.5.0",
|
|
54
56
|
"vue-sonner": "^2.0.9",
|
|
55
57
|
"vue3-json-viewer": "^2.4.1",
|
|
@@ -0,0 +1,152 @@
|
|
|
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>
|
|
@@ -0,0 +1,194 @@
|
|
|
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>
|
|
@@ -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 && response.data
|
|
150
|
+
if ('data' in response && response.data && 0 in response.data) {
|
|
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,15 +6,92 @@ 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
|
+
|
|
9
71
|
/**
|
|
10
|
-
* Call Tabular-api to get table content
|
|
72
|
+
* Call Tabular-api to get table content with options object
|
|
11
73
|
*/
|
|
12
|
-
export async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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}`
|
|
16
83
|
}
|
|
17
|
-
|
|
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
|
+
/**
|
|
91
|
+
* Call Tabular-api to get table content
|
|
92
|
+
*/
|
|
93
|
+
export function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig) {
|
|
94
|
+
return fetchTabularData(config, { resourceId: id, page, sort: sortConfig })
|
|
18
95
|
}
|
|
19
96
|
|
|
20
97
|
/**
|
|
@@ -22,5 +99,5 @@ export async function getData(config: PluginConfig, id: string, page: number, so
|
|
|
22
99
|
*/
|
|
23
100
|
export function useGetProfile() {
|
|
24
101
|
const config = useComponentsConfig()
|
|
25
|
-
return (id: string) => ofetch(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
|
|
102
|
+
return (id: string) => ofetch<TabularProfileResponse>(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
|
|
26
103
|
}
|
package/src/main.ts
CHANGED
|
@@ -23,6 +23,7 @@ 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
27
|
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
|
|
27
28
|
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
|
|
28
29
|
|
|
@@ -100,6 +101,7 @@ import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
|
|
|
100
101
|
export { Toaster, toast } from 'vue-sonner'
|
|
101
102
|
|
|
102
103
|
export * from './composables/useActiveDescendant'
|
|
104
|
+
export * from './composables/useDebouncedRef'
|
|
103
105
|
export * from './composables/useMetrics'
|
|
104
106
|
export * from './composables/useReuseType'
|
|
105
107
|
export * from './composables/useTranslation'
|
|
@@ -121,6 +123,7 @@ export * from './functions/resources'
|
|
|
121
123
|
export * from './functions/reuses'
|
|
122
124
|
export * from './functions/schemas'
|
|
123
125
|
export * from './functions/users'
|
|
126
|
+
export * from './functions/tabularApi'
|
|
124
127
|
export * from './types/access_types'
|
|
125
128
|
|
|
126
129
|
export type {
|
|
@@ -218,6 +221,23 @@ export type {
|
|
|
218
221
|
ValidataError,
|
|
219
222
|
Weight,
|
|
220
223
|
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,
|
|
221
241
|
}
|
|
222
242
|
|
|
223
243
|
export {
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
}
|