@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.
Files changed (49) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +554 -0
  3. package/package.json +64 -0
  4. package/src/base.css +86 -0
  5. package/src/cartesian/area.js +392 -0
  6. package/src/cartesian/area.test.js +366 -0
  7. package/src/cartesian/bar.js +445 -0
  8. package/src/cartesian/bar.test.js +346 -0
  9. package/src/cartesian/line.js +823 -0
  10. package/src/cartesian/line.test.js +177 -0
  11. package/src/chart.test.js +444 -0
  12. package/src/component/brush.js +264 -0
  13. package/src/component/empty-state.js +33 -0
  14. package/src/component/empty-state.test.js +81 -0
  15. package/src/component/grid.js +123 -0
  16. package/src/component/grid.test.js +123 -0
  17. package/src/component/legend.js +76 -0
  18. package/src/component/legend.test.js +103 -0
  19. package/src/component/tooltip.js +65 -0
  20. package/src/component/tooltip.test.js +96 -0
  21. package/src/component/x-axis.js +212 -0
  22. package/src/component/x-axis.test.js +148 -0
  23. package/src/component/y-axis.js +77 -0
  24. package/src/component/y-axis.test.js +107 -0
  25. package/src/handlers.js +150 -0
  26. package/src/index.js +264 -0
  27. package/src/polar/donut.js +181 -0
  28. package/src/polar/donut.test.js +152 -0
  29. package/src/polar/pie.js +758 -0
  30. package/src/polar/pie.test.js +268 -0
  31. package/src/shape/curve.js +55 -0
  32. package/src/shape/dot.js +104 -0
  33. package/src/shape/rectangle.js +46 -0
  34. package/src/shape/sector.js +58 -0
  35. package/src/template.js +25 -0
  36. package/src/theme.css +90 -0
  37. package/src/utils/cartesian-layout.js +164 -0
  38. package/src/utils/chart-utils.js +30 -0
  39. package/src/utils/colors.js +77 -0
  40. package/src/utils/data-utils.js +155 -0
  41. package/src/utils/data-utils.test.js +210 -0
  42. package/src/utils/extract-data-keys.js +22 -0
  43. package/src/utils/padding.js +16 -0
  44. package/src/utils/paths.js +279 -0
  45. package/src/utils/process-declarative-child.js +46 -0
  46. package/src/utils/scales.js +250 -0
  47. package/src/utils/shared-context.js +166 -0
  48. package/src/utils/shared-context.test.js +237 -0
  49. 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
+ })