@inglorious/charts 1.0.1
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/LICENSE +9 -0
- package/README.md +554 -0
- package/package.json +64 -0
- package/src/base.css +86 -0
- package/src/cartesian/area.js +392 -0
- package/src/cartesian/area.test.js +366 -0
- package/src/cartesian/bar.js +445 -0
- package/src/cartesian/bar.test.js +346 -0
- package/src/cartesian/line.js +823 -0
- package/src/cartesian/line.test.js +177 -0
- package/src/chart.test.js +444 -0
- package/src/component/brush.js +264 -0
- package/src/component/empty-state.js +33 -0
- package/src/component/empty-state.test.js +81 -0
- package/src/component/grid.js +123 -0
- package/src/component/grid.test.js +123 -0
- package/src/component/legend.js +76 -0
- package/src/component/legend.test.js +103 -0
- package/src/component/tooltip.js +65 -0
- package/src/component/tooltip.test.js +96 -0
- package/src/component/x-axis.js +212 -0
- package/src/component/x-axis.test.js +148 -0
- package/src/component/y-axis.js +77 -0
- package/src/component/y-axis.test.js +107 -0
- package/src/handlers.js +150 -0
- package/src/index.js +264 -0
- package/src/polar/donut.js +181 -0
- package/src/polar/donut.test.js +152 -0
- package/src/polar/pie.js +758 -0
- package/src/polar/pie.test.js +268 -0
- package/src/shape/curve.js +55 -0
- package/src/shape/dot.js +104 -0
- package/src/shape/rectangle.js +46 -0
- package/src/shape/sector.js +58 -0
- package/src/template.js +25 -0
- package/src/theme.css +90 -0
- package/src/utils/cartesian-layout.js +164 -0
- package/src/utils/chart-utils.js +30 -0
- package/src/utils/colors.js +77 -0
- package/src/utils/data-utils.js +155 -0
- package/src/utils/data-utils.test.js +210 -0
- package/src/utils/extract-data-keys.js +22 -0
- package/src/utils/padding.js +16 -0
- package/src/utils/paths.js +279 -0
- package/src/utils/process-declarative-child.js +46 -0
- package/src/utils/scales.js +250 -0
- package/src/utils/shared-context.js +166 -0
- package/src/utils/shared-context.test.js +237 -0
- package/src/utils/tooltip-handlers.js +129 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { render } from "@inglorious/web/test"
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from "vitest"
|
|
6
|
+
|
|
7
|
+
import { bar } from "./bar.js"
|
|
8
|
+
|
|
9
|
+
describe("bar", () => {
|
|
10
|
+
let entity
|
|
11
|
+
let api
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
entity = {
|
|
15
|
+
id: "test-bar",
|
|
16
|
+
type: "bar",
|
|
17
|
+
data: [
|
|
18
|
+
{ label: "Jan", value: 100 },
|
|
19
|
+
{ label: "Feb", value: 150 },
|
|
20
|
+
{ label: "Mar", value: 120 },
|
|
21
|
+
],
|
|
22
|
+
width: 800,
|
|
23
|
+
height: 400,
|
|
24
|
+
padding: { top: 20, right: 50, bottom: 30, left: 50 },
|
|
25
|
+
colors: ["#3b82f6", "#ef4444", "#10b981"],
|
|
26
|
+
showGrid: true,
|
|
27
|
+
showTooltip: true,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
api = {
|
|
31
|
+
getEntity: vi.fn((id) => (id === "test-bar" ? entity : null)),
|
|
32
|
+
notify: vi.fn(),
|
|
33
|
+
getType: vi.fn((type) => (type === "bar" ? bar : null)),
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe("render()", () => {
|
|
38
|
+
it("should render bar chart with data", () => {
|
|
39
|
+
const result = bar.render(entity, api)
|
|
40
|
+
const container = document.createElement("div")
|
|
41
|
+
render(result, container)
|
|
42
|
+
|
|
43
|
+
const svg = container.querySelector("svg")
|
|
44
|
+
expect(svg).toBeTruthy()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("should handle empty data gracefully", () => {
|
|
48
|
+
entity.data = []
|
|
49
|
+
|
|
50
|
+
const result = bar.render(entity, api)
|
|
51
|
+
const container = document.createElement("div")
|
|
52
|
+
render(result, container)
|
|
53
|
+
|
|
54
|
+
// Should render empty state or handle gracefully
|
|
55
|
+
expect(result).toBeDefined()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("should render grid when showGrid is true", () => {
|
|
59
|
+
entity.showGrid = true
|
|
60
|
+
|
|
61
|
+
const result = bar.render(entity, api)
|
|
62
|
+
const container = document.createElement("div")
|
|
63
|
+
render(result, container)
|
|
64
|
+
|
|
65
|
+
const svg = container.querySelector("svg")
|
|
66
|
+
expect(svg).toBeTruthy()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("should not render grid when showGrid is false", () => {
|
|
70
|
+
entity.showGrid = false
|
|
71
|
+
|
|
72
|
+
const result = bar.render(entity, api)
|
|
73
|
+
const container = document.createElement("div")
|
|
74
|
+
render(result, container)
|
|
75
|
+
|
|
76
|
+
const svg = container.querySelector("svg")
|
|
77
|
+
expect(svg).toBeTruthy()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe("renderBarChart()", () => {
|
|
82
|
+
it("should render bar chart with children", () => {
|
|
83
|
+
const children = [
|
|
84
|
+
bar.renderCartesianGrid(entity, {}, api),
|
|
85
|
+
bar.renderXAxis(entity, {}, api),
|
|
86
|
+
bar.renderYAxis(entity, {}, api),
|
|
87
|
+
bar.renderBar(entity, { config: { dataKey: "value" } }, api),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
const result = bar.renderBarChart(
|
|
91
|
+
entity,
|
|
92
|
+
{ children, config: { width: 800, height: 400 } },
|
|
93
|
+
api,
|
|
94
|
+
)
|
|
95
|
+
const container = document.createElement("div")
|
|
96
|
+
render(result, container)
|
|
97
|
+
|
|
98
|
+
const svg = container.querySelector("svg")
|
|
99
|
+
expect(svg).toBeTruthy()
|
|
100
|
+
expect(svg.getAttribute("width")).toBe("800")
|
|
101
|
+
expect(svg.getAttribute("height")).toBe("400")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("should return error message if entity is missing", () => {
|
|
105
|
+
const result = bar.renderBarChart(null, { children: [] }, api)
|
|
106
|
+
const container = document.createElement("div")
|
|
107
|
+
render(result, container)
|
|
108
|
+
|
|
109
|
+
expect(container.textContent).toContain("Entity not found")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("should return error message if entity.data is invalid", () => {
|
|
113
|
+
entity.data = null
|
|
114
|
+
|
|
115
|
+
const result = bar.renderBarChart(entity, { children: [] }, api)
|
|
116
|
+
const container = document.createElement("div")
|
|
117
|
+
render(result, container)
|
|
118
|
+
|
|
119
|
+
expect(container.textContent).toContain("Entity data is missing")
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe("renderBar()", () => {
|
|
124
|
+
it("should return a function marked as isBar", () => {
|
|
125
|
+
const result = bar.renderBar(
|
|
126
|
+
entity,
|
|
127
|
+
{ config: { dataKey: "value" } },
|
|
128
|
+
api,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
expect(typeof result).toBe("function")
|
|
132
|
+
expect(result.isBar).toBe(true)
|
|
133
|
+
expect(result.dataKey).toBe("value")
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("should render bars when called with context", () => {
|
|
137
|
+
const barFn = bar.renderBar(entity, { config: { dataKey: "value" } }, api)
|
|
138
|
+
|
|
139
|
+
// Create mock context
|
|
140
|
+
const context = {
|
|
141
|
+
entity,
|
|
142
|
+
xScale: {
|
|
143
|
+
domain: () => ["Jan", "Feb", "Mar"],
|
|
144
|
+
range: () => [50, 750],
|
|
145
|
+
bandwidth: () => 200,
|
|
146
|
+
},
|
|
147
|
+
yScale: {
|
|
148
|
+
domain: () => [0, 200],
|
|
149
|
+
range: () => [370, 20],
|
|
150
|
+
},
|
|
151
|
+
dimensions: {
|
|
152
|
+
width: 800,
|
|
153
|
+
height: 400,
|
|
154
|
+
padding: { top: 20, right: 50, bottom: 30, left: 50 },
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Mock xScale methods
|
|
159
|
+
context.xScale = vi.fn((cat) => {
|
|
160
|
+
const map = { Jan: 100, Feb: 350, Mar: 600 }
|
|
161
|
+
return map[cat] || 0
|
|
162
|
+
})
|
|
163
|
+
context.xScale.domain = vi.fn(() => ["Jan", "Feb", "Mar"])
|
|
164
|
+
context.xScale.range = vi.fn(() => [50, 750])
|
|
165
|
+
context.xScale.bandwidth = vi.fn(() => 200)
|
|
166
|
+
|
|
167
|
+
context.yScale = vi.fn((val) => 370 - (val / 200) * 350)
|
|
168
|
+
context.yScale.domain = vi.fn(() => [0, 200])
|
|
169
|
+
context.yScale.range = vi.fn(() => [370, 20])
|
|
170
|
+
|
|
171
|
+
const result = barFn(context, 0, 1)
|
|
172
|
+
const container = document.createElement("div")
|
|
173
|
+
render(result, container)
|
|
174
|
+
|
|
175
|
+
// Should render rectangles for bars
|
|
176
|
+
const rects = container.querySelectorAll("rect")
|
|
177
|
+
expect(rects.length).toBeGreaterThan(0)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it("should use multiColor when config.multiColor is true", () => {
|
|
181
|
+
const barFn = bar.renderBar(
|
|
182
|
+
entity,
|
|
183
|
+
{
|
|
184
|
+
config: { dataKey: "value", multiColor: true },
|
|
185
|
+
},
|
|
186
|
+
api,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
expect(typeof barFn).toBe("function")
|
|
190
|
+
expect(barFn.isBar).toBe(true)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("should use fill color when provided", () => {
|
|
194
|
+
const barFn = bar.renderBar(
|
|
195
|
+
entity,
|
|
196
|
+
{
|
|
197
|
+
config: { dataKey: "value", fill: "#ff0000" },
|
|
198
|
+
},
|
|
199
|
+
api,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
expect(typeof barFn).toBe("function")
|
|
203
|
+
expect(barFn.isBar).toBe(true)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe("renderXAxis()", () => {
|
|
208
|
+
it("should return a function marked as isAxis", () => {
|
|
209
|
+
const result = bar.renderXAxis(entity, {}, api)
|
|
210
|
+
|
|
211
|
+
expect(typeof result).toBe("function")
|
|
212
|
+
expect(result.isAxis).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("should render X axis when called with context", () => {
|
|
216
|
+
const xAxisFn = bar.renderXAxis(entity, {}, api)
|
|
217
|
+
|
|
218
|
+
const context = {
|
|
219
|
+
entity,
|
|
220
|
+
xScale: {
|
|
221
|
+
domain: () => ["Jan", "Feb", "Mar"],
|
|
222
|
+
range: () => [50, 750],
|
|
223
|
+
bandwidth: () => 200,
|
|
224
|
+
},
|
|
225
|
+
yScale: {
|
|
226
|
+
domain: () => [0, 200],
|
|
227
|
+
range: () => [370, 20],
|
|
228
|
+
},
|
|
229
|
+
dimensions: {
|
|
230
|
+
width: 800,
|
|
231
|
+
height: 400,
|
|
232
|
+
padding: { top: 20, right: 50, bottom: 30, left: 50 },
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Mock xScale methods
|
|
237
|
+
context.xScale = vi.fn((cat) => {
|
|
238
|
+
const map = { Jan: 100, Feb: 350, Mar: 600 }
|
|
239
|
+
return map[cat] || 0
|
|
240
|
+
})
|
|
241
|
+
context.xScale.domain = vi.fn(() => ["Jan", "Feb", "Mar"])
|
|
242
|
+
context.xScale.range = vi.fn(() => [50, 750])
|
|
243
|
+
context.xScale.bandwidth = vi.fn(() => 200)
|
|
244
|
+
|
|
245
|
+
const result = xAxisFn(context)
|
|
246
|
+
const container = document.createElement("div")
|
|
247
|
+
render(result, container)
|
|
248
|
+
|
|
249
|
+
// Should render axis line and labels
|
|
250
|
+
const axisLine = container.querySelector("line")
|
|
251
|
+
expect(axisLine).toBeTruthy()
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe("renderYAxis()", () => {
|
|
256
|
+
it("should return a function", () => {
|
|
257
|
+
const result = bar.renderYAxis(entity, {}, api)
|
|
258
|
+
|
|
259
|
+
expect(typeof result).toBe("function")
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("should render Y axis when called with context", () => {
|
|
263
|
+
const yAxisFn = bar.renderYAxis(entity, {}, api)
|
|
264
|
+
|
|
265
|
+
const context = {
|
|
266
|
+
entity,
|
|
267
|
+
yScale: {
|
|
268
|
+
domain: () => [0, 200],
|
|
269
|
+
range: () => [370, 20],
|
|
270
|
+
ticks: () => [0, 50, 100, 150, 200],
|
|
271
|
+
},
|
|
272
|
+
dimensions: {
|
|
273
|
+
width: 800,
|
|
274
|
+
height: 400,
|
|
275
|
+
padding: { top: 20, right: 50, bottom: 30, left: 50 },
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
context.yScale = vi.fn((val) => 370 - (val / 200) * 350)
|
|
280
|
+
context.yScale.domain = vi.fn(() => [0, 200])
|
|
281
|
+
context.yScale.range = vi.fn(() => [370, 20])
|
|
282
|
+
context.yScale.ticks = vi.fn(() => [0, 50, 100, 150, 200])
|
|
283
|
+
|
|
284
|
+
const result = yAxisFn(context)
|
|
285
|
+
const container = document.createElement("div")
|
|
286
|
+
render(result, container)
|
|
287
|
+
|
|
288
|
+
// Should render axis line
|
|
289
|
+
const axisLine = container.querySelector("line")
|
|
290
|
+
expect(axisLine).toBeTruthy()
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
describe("renderCartesianGrid()", () => {
|
|
295
|
+
it("should return a function", () => {
|
|
296
|
+
const result = bar.renderCartesianGrid(entity, {}, api)
|
|
297
|
+
|
|
298
|
+
expect(typeof result).toBe("function")
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it("should render grid when called with context", () => {
|
|
302
|
+
const gridFn = bar.renderCartesianGrid(entity, {}, api)
|
|
303
|
+
|
|
304
|
+
const context = {
|
|
305
|
+
entity,
|
|
306
|
+
xScale: {
|
|
307
|
+
domain: () => ["Jan", "Feb", "Mar"],
|
|
308
|
+
range: () => [50, 750],
|
|
309
|
+
bandwidth: () => 200,
|
|
310
|
+
},
|
|
311
|
+
yScale: {
|
|
312
|
+
domain: () => [0, 200],
|
|
313
|
+
range: () => [370, 20],
|
|
314
|
+
ticks: () => [0, 50, 100, 150, 200],
|
|
315
|
+
},
|
|
316
|
+
dimensions: {
|
|
317
|
+
width: 800,
|
|
318
|
+
height: 400,
|
|
319
|
+
padding: { top: 20, right: 50, bottom: 30, left: 50 },
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Mock scales
|
|
324
|
+
context.xScale = vi.fn((cat) => {
|
|
325
|
+
const map = { Jan: 100, Feb: 350, Mar: 600 }
|
|
326
|
+
return map[cat] || 0
|
|
327
|
+
})
|
|
328
|
+
context.xScale.domain = vi.fn(() => ["Jan", "Feb", "Mar"])
|
|
329
|
+
context.xScale.range = vi.fn(() => [50, 750])
|
|
330
|
+
context.xScale.bandwidth = vi.fn(() => 200)
|
|
331
|
+
|
|
332
|
+
context.yScale = vi.fn((val) => 370 - (val / 200) * 350)
|
|
333
|
+
context.yScale.domain = vi.fn(() => [0, 200])
|
|
334
|
+
context.yScale.range = vi.fn(() => [370, 20])
|
|
335
|
+
context.yScale.ticks = vi.fn(() => [0, 50, 100, 150, 200])
|
|
336
|
+
|
|
337
|
+
const result = gridFn(context)
|
|
338
|
+
const container = document.createElement("div")
|
|
339
|
+
render(result, container)
|
|
340
|
+
|
|
341
|
+
// Should render grid lines
|
|
342
|
+
const lines = container.querySelectorAll("line")
|
|
343
|
+
expect(lines.length).toBeGreaterThan(0)
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
})
|