@dnax/ui 0.0.67 → 0.0.69
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/components/QChart.vue +329 -19
- package/components/QSpreadsheet.vue +35 -17
- package/index.ts +11 -1
- package/lib/chart.axis.test.ts +1 -1
- package/lib/chart.bi.test.ts +839 -0
- package/lib/chart.modules.test.ts +232 -0
- package/lib/chart.test.ts +13 -4
- package/lib/chart.ts +1400 -80
- package/lib/chartFormat.ts +222 -0
- package/lib/chartStats.ts +178 -0
- package/lib/history.test.ts +78 -0
- package/lib/history.ts +72 -0
- package/package.json +1 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Vérifie que la liste de modules **enregistrés par `QChart.vue`** suffit à rendre chaque
|
|
2
|
+
// famille de marque. Le SFC charge ECharts en dynamique (`echarts/core` + `use([...])`) : un
|
|
3
|
+
// module oublié n'échoue pas — il est **silencieusement ignoré**. Ce test rejoue exactement les
|
|
4
|
+
// mêmes imports tree-shakés (avec le rendu SVG) pour que l'oubli devienne visible.
|
|
5
|
+
//
|
|
6
|
+
// ⚠️ Toute nouvelle série / composant doit être ajouté ici **et** dans `QChart.vue`.
|
|
7
|
+
import { describe, expect, it } from "bun:test"
|
|
8
|
+
import * as echarts from "echarts/core"
|
|
9
|
+
import {
|
|
10
|
+
BarChart,
|
|
11
|
+
BoxplotChart,
|
|
12
|
+
FunnelChart,
|
|
13
|
+
GaugeChart,
|
|
14
|
+
HeatmapChart,
|
|
15
|
+
LineChart,
|
|
16
|
+
PieChart,
|
|
17
|
+
RadarChart,
|
|
18
|
+
SankeyChart,
|
|
19
|
+
ScatterChart,
|
|
20
|
+
SunburstChart,
|
|
21
|
+
TreemapChart,
|
|
22
|
+
} from "echarts/charts"
|
|
23
|
+
import {
|
|
24
|
+
GridComponent,
|
|
25
|
+
LegendComponent,
|
|
26
|
+
MarkAreaComponent,
|
|
27
|
+
MarkLineComponent,
|
|
28
|
+
RadarComponent,
|
|
29
|
+
TitleComponent,
|
|
30
|
+
TooltipComponent,
|
|
31
|
+
VisualMapComponent,
|
|
32
|
+
} from "echarts/components"
|
|
33
|
+
import { SVGRenderer } from "echarts/renderers"
|
|
34
|
+
import { LegacyGridContainLabel } from "echarts/features"
|
|
35
|
+
import { chartToECharts } from "./chart"
|
|
36
|
+
import type { QChartConfig } from "./chart"
|
|
37
|
+
|
|
38
|
+
echarts.use([
|
|
39
|
+
BarChart,
|
|
40
|
+
LineChart,
|
|
41
|
+
ScatterChart,
|
|
42
|
+
PieChart,
|
|
43
|
+
HeatmapChart,
|
|
44
|
+
BoxplotChart,
|
|
45
|
+
GaugeChart,
|
|
46
|
+
FunnelChart,
|
|
47
|
+
RadarChart,
|
|
48
|
+
TreemapChart,
|
|
49
|
+
SunburstChart,
|
|
50
|
+
SankeyChart,
|
|
51
|
+
GridComponent,
|
|
52
|
+
RadarComponent,
|
|
53
|
+
TooltipComponent,
|
|
54
|
+
LegendComponent,
|
|
55
|
+
TitleComponent,
|
|
56
|
+
MarkLineComponent,
|
|
57
|
+
MarkAreaComponent,
|
|
58
|
+
VisualMapComponent,
|
|
59
|
+
SVGRenderer,
|
|
60
|
+
LegacyGridContainLabel,
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const THEME = { text: "#111111", muted: "#666666", grid: "#e5e5e5" }
|
|
64
|
+
|
|
65
|
+
const render = (config: QChartConfig) => {
|
|
66
|
+
const chart = echarts.init(null as any, null, { renderer: "svg", ssr: true, width: 420, height: 280 })
|
|
67
|
+
chart.setOption(chartToECharts({ theme: THEME, ...config, marks: config.marks ?? [] }))
|
|
68
|
+
const svg = chart.renderToSVGString()
|
|
69
|
+
chart.dispose()
|
|
70
|
+
return svg
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const monthly = [
|
|
74
|
+
{ month: "Jan", value: 42 },
|
|
75
|
+
{ month: "Feb", value: 51 },
|
|
76
|
+
{ month: "Mar", value: 47 },
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
describe("modules ECharts enregistrés par QChart", () => {
|
|
80
|
+
it("les marques cartésiennes et les annotations se dessinent", () => {
|
|
81
|
+
const svg = render({
|
|
82
|
+
marks: [
|
|
83
|
+
{ type: "bar", data: monthly, x: "month", y: "value", name: "Bars", labels: true },
|
|
84
|
+
{ type: "line", data: monthly, x: "month", y: "value", name: "Line" },
|
|
85
|
+
{ type: "band", y0: 45, y1: 55, fill: "warning" },
|
|
86
|
+
{ type: "rule", y: [50], label: "Target" },
|
|
87
|
+
],
|
|
88
|
+
})
|
|
89
|
+
expect(svg).toContain("<rect")
|
|
90
|
+
expect(svg).toContain("<path")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("les familles hors axes se dessinent", () => {
|
|
94
|
+
const cases: [string, QChartConfig][] = [
|
|
95
|
+
["pie", { marks: [{ type: "pie", data: monthly, x: "month", y: "value" }] }],
|
|
96
|
+
[
|
|
97
|
+
"heatmap",
|
|
98
|
+
{
|
|
99
|
+
marks: [
|
|
100
|
+
{
|
|
101
|
+
type: "heatmap",
|
|
102
|
+
data: [
|
|
103
|
+
{ day: "Mon", hour: "9h", v: 1 },
|
|
104
|
+
{ day: "Tue", hour: "9h", v: 2 },
|
|
105
|
+
],
|
|
106
|
+
x: "hour",
|
|
107
|
+
y: "day",
|
|
108
|
+
fill: "v",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
"gauge",
|
|
115
|
+
{
|
|
116
|
+
marks: [
|
|
117
|
+
{
|
|
118
|
+
type: "gauge",
|
|
119
|
+
data: [{ label: "Uptime", value: 99 }],
|
|
120
|
+
x: "label",
|
|
121
|
+
y: "value",
|
|
122
|
+
target: 99.5,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
["funnel", { marks: [{ type: "funnel", data: monthly, x: "month", y: "value" }] }],
|
|
128
|
+
[
|
|
129
|
+
"radar",
|
|
130
|
+
{
|
|
131
|
+
marks: [
|
|
132
|
+
{
|
|
133
|
+
type: "radar",
|
|
134
|
+
data: [
|
|
135
|
+
{ skill: "Speed", value: 8 },
|
|
136
|
+
{ skill: "Power", value: 6 },
|
|
137
|
+
],
|
|
138
|
+
x: "skill",
|
|
139
|
+
y: "value",
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
"treemap",
|
|
146
|
+
{
|
|
147
|
+
marks: [
|
|
148
|
+
{
|
|
149
|
+
type: "treemap",
|
|
150
|
+
data: [
|
|
151
|
+
{ name: "Root", parent: "", value: 0 },
|
|
152
|
+
{ name: "Leaf", parent: "Root", value: 10 },
|
|
153
|
+
],
|
|
154
|
+
x: "name",
|
|
155
|
+
parent: "parent",
|
|
156
|
+
value: "value",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
"sunburst",
|
|
163
|
+
{
|
|
164
|
+
marks: [
|
|
165
|
+
{
|
|
166
|
+
type: "sunburst",
|
|
167
|
+
data: [
|
|
168
|
+
{ name: "Root", parent: "", value: 0 },
|
|
169
|
+
{ name: "Leaf", parent: "Root", value: 10 },
|
|
170
|
+
],
|
|
171
|
+
x: "name",
|
|
172
|
+
parent: "parent",
|
|
173
|
+
value: "value",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
[
|
|
179
|
+
"sankey",
|
|
180
|
+
{
|
|
181
|
+
marks: [
|
|
182
|
+
{
|
|
183
|
+
type: "sankey",
|
|
184
|
+
data: [{ source: "A", target: "B", value: 10 }],
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
"boxplot",
|
|
191
|
+
{
|
|
192
|
+
marks: [{ type: "boxplot", data: [{ team: "A", s: [1, 2, 3, 4, 5] }], x: "team", values: "s" }],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
"histogram",
|
|
197
|
+
{ marks: [{ type: "histogram", data: [1, 2, 2, 3, 5, 8], x: (d: any) => d, bins: 3 }] },
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
"waterfall",
|
|
201
|
+
{
|
|
202
|
+
marks: [
|
|
203
|
+
{
|
|
204
|
+
type: "waterfall",
|
|
205
|
+
data: [
|
|
206
|
+
{ step: "A", delta: 10 },
|
|
207
|
+
{ step: "B", delta: -4 },
|
|
208
|
+
],
|
|
209
|
+
x: "step",
|
|
210
|
+
y: "delta",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
]
|
|
216
|
+
for (const [label, config] of cases) {
|
|
217
|
+
const svg = render(config)
|
|
218
|
+
expect(svg.startsWith("<svg"), label).toBe(true)
|
|
219
|
+
expect(svg.length, label).toBeGreaterThan(200)
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it("le second axe Y se dessine", () => {
|
|
224
|
+
const svg = render({
|
|
225
|
+
marks: [
|
|
226
|
+
{ type: "bar", data: monthly, x: "month", y: "value", name: "Volume" },
|
|
227
|
+
{ type: "line", data: monthly, x: "month", y: "value", yAxis: "right", name: "Rate" },
|
|
228
|
+
],
|
|
229
|
+
})
|
|
230
|
+
expect(svg).toContain("Volume")
|
|
231
|
+
})
|
|
232
|
+
})
|
package/lib/chart.test.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
pickFromEvent,
|
|
13
13
|
sameSelection,
|
|
14
14
|
tableModel,
|
|
15
|
-
tableModel,
|
|
16
15
|
textValue,
|
|
17
16
|
} from "./chart"
|
|
18
17
|
import type { QChartConfig, QChartMark } from "./chart"
|
|
@@ -523,7 +522,17 @@ describe("marque `table`", () => {
|
|
|
523
522
|
})
|
|
524
523
|
|
|
525
524
|
it("`separator` est bien une option de la marque `table` (table d'API générée)", () => {
|
|
526
|
-
expect(MARK_OPTIONS.table).toEqual([
|
|
525
|
+
expect(MARK_OPTIONS.table).toEqual([
|
|
526
|
+
"data",
|
|
527
|
+
"link",
|
|
528
|
+
"columns",
|
|
529
|
+
"separator",
|
|
530
|
+
"sortBy",
|
|
531
|
+
"sortDesc",
|
|
532
|
+
"pageSize",
|
|
533
|
+
"page",
|
|
534
|
+
"totals",
|
|
535
|
+
])
|
|
527
536
|
})
|
|
528
537
|
})
|
|
529
538
|
|
|
@@ -693,7 +702,7 @@ describe("marque `table` (`tableModel`)", () => {
|
|
|
693
702
|
{ field: "month", label: "Month" },
|
|
694
703
|
{ field: "revenue", label: "Revenue", align: "end", format: expect.any(Function) },
|
|
695
704
|
])
|
|
696
|
-
expect(model.columns[1]
|
|
705
|
+
expect(model.columns[1]!.format?.(42, rows[0])).toBe("42 €")
|
|
697
706
|
})
|
|
698
707
|
|
|
699
708
|
it("accepte les colonnes en abrégé (`'month'`)", () => {
|
|
@@ -816,7 +825,7 @@ describe("clic de légende en mode `select` (`nextLegendPick`)", () => {
|
|
|
816
825
|
it("**désélectionne** quand on reclique le nom courant (`null`)", () => {
|
|
817
826
|
expect(nextLegendPick({ name: "Jan" }, "Jan")).toBeNull()
|
|
818
827
|
// la comparaison est textuelle : 42 et « 42 » désignent la même part
|
|
819
|
-
expect(nextLegendPick({ name: 42 }, "42")).toBeNull()
|
|
828
|
+
expect(nextLegendPick({ name: 42 } as any, "42")).toBeNull()
|
|
820
829
|
})
|
|
821
830
|
|
|
822
831
|
it("une sélection sans nom (ou absente) laisse sélectionner", () => {
|