@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,268 @@
|
|
|
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 { pie } from "./pie.js"
|
|
8
|
+
|
|
9
|
+
describe("pie", () => {
|
|
10
|
+
let entity
|
|
11
|
+
let api
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
entity = {
|
|
15
|
+
id: "test-pie",
|
|
16
|
+
type: "pie",
|
|
17
|
+
data: [
|
|
18
|
+
{ name: "Category A", value: 20 },
|
|
19
|
+
{ name: "Category B", value: 35 },
|
|
20
|
+
{ name: "Category C", value: 15 },
|
|
21
|
+
{ name: "Category D", value: 10 },
|
|
22
|
+
],
|
|
23
|
+
width: 500,
|
|
24
|
+
height: 400,
|
|
25
|
+
colors: ["#3b82f6", "#ef4444", "#10b981", "#f59e0b"],
|
|
26
|
+
showTooltip: true,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
api = {
|
|
30
|
+
getEntity: vi.fn((id) => (id === "test-pie" ? entity : null)),
|
|
31
|
+
notify: vi.fn(),
|
|
32
|
+
getType: vi.fn().mockReturnValue({
|
|
33
|
+
renderTooltip: () => "",
|
|
34
|
+
}),
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe("render()", () => {
|
|
39
|
+
it("should render pie chart with data", () => {
|
|
40
|
+
const result = pie.render(entity, api)
|
|
41
|
+
const container = document.createElement("div")
|
|
42
|
+
render(result, container)
|
|
43
|
+
|
|
44
|
+
const svg = container.querySelector("svg")
|
|
45
|
+
expect(svg).toBeTruthy()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("should handle empty data gracefully", () => {
|
|
49
|
+
entity.data = []
|
|
50
|
+
|
|
51
|
+
const result = pie.render(entity, api)
|
|
52
|
+
const container = document.createElement("div")
|
|
53
|
+
render(result, container)
|
|
54
|
+
|
|
55
|
+
expect(result).toBeDefined()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe("renderPieChart()", () => {
|
|
60
|
+
it("should render pie chart with children", () => {
|
|
61
|
+
const children = [
|
|
62
|
+
pie.renderPie(
|
|
63
|
+
entity,
|
|
64
|
+
{
|
|
65
|
+
config: { dataKey: "value", nameKey: "name" },
|
|
66
|
+
},
|
|
67
|
+
api,
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
const result = pie.renderPieChart(
|
|
72
|
+
entity,
|
|
73
|
+
{ children, config: { width: 500, height: 400 } },
|
|
74
|
+
api,
|
|
75
|
+
)
|
|
76
|
+
const container = document.createElement("div")
|
|
77
|
+
render(result, container)
|
|
78
|
+
|
|
79
|
+
const svg = container.querySelector("svg")
|
|
80
|
+
expect(svg).toBeTruthy()
|
|
81
|
+
expect(svg.getAttribute("width")).toBe("500")
|
|
82
|
+
expect(svg.getAttribute("height")).toBe("400")
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it("should return error message if entity is missing", () => {
|
|
86
|
+
const result = pie.renderPieChart(null, { children: [] }, api)
|
|
87
|
+
const container = document.createElement("div")
|
|
88
|
+
render(result, container)
|
|
89
|
+
|
|
90
|
+
expect(container.textContent).toContain("Entity not found")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("should use custom cx and cy when provided", () => {
|
|
94
|
+
const children = [
|
|
95
|
+
pie.renderPie(
|
|
96
|
+
entity,
|
|
97
|
+
{
|
|
98
|
+
config: { dataKey: "value", nameKey: "name" },
|
|
99
|
+
},
|
|
100
|
+
api,
|
|
101
|
+
),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
const result = pie.renderPieChart(
|
|
105
|
+
entity,
|
|
106
|
+
{
|
|
107
|
+
children,
|
|
108
|
+
config: { width: 500, height: 400, cx: "35%", cy: "35%" },
|
|
109
|
+
},
|
|
110
|
+
api,
|
|
111
|
+
)
|
|
112
|
+
const container = document.createElement("div")
|
|
113
|
+
render(result, container)
|
|
114
|
+
|
|
115
|
+
const svg = container.querySelector("svg")
|
|
116
|
+
expect(svg).toBeTruthy()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe("renderPie()", () => {
|
|
121
|
+
it("should return a function marked as isPie", () => {
|
|
122
|
+
const result = pie.renderPie(
|
|
123
|
+
entity,
|
|
124
|
+
{
|
|
125
|
+
config: { dataKey: "value", nameKey: "name" },
|
|
126
|
+
},
|
|
127
|
+
api,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
expect(typeof result).toBe("function")
|
|
131
|
+
expect(result.isPie).toBe(true)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("should render pie sectors when called with context", () => {
|
|
135
|
+
const pieFn = pie.renderPie(
|
|
136
|
+
entity,
|
|
137
|
+
{
|
|
138
|
+
config: { dataKey: "value", nameKey: "name" },
|
|
139
|
+
},
|
|
140
|
+
api,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const context = {
|
|
144
|
+
entity,
|
|
145
|
+
width: 500,
|
|
146
|
+
height: 400,
|
|
147
|
+
cx: 250,
|
|
148
|
+
cy: 200,
|
|
149
|
+
api,
|
|
150
|
+
colors: entity.colors,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = pieFn(context)
|
|
154
|
+
const container = document.createElement("div")
|
|
155
|
+
render(result, container)
|
|
156
|
+
|
|
157
|
+
// Should render paths for pie sectors
|
|
158
|
+
const paths = container.querySelectorAll("path")
|
|
159
|
+
expect(paths.length).toBeGreaterThan(0)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("should support innerRadius for donut charts", () => {
|
|
163
|
+
const pieFn = pie.renderPie(
|
|
164
|
+
entity,
|
|
165
|
+
{
|
|
166
|
+
config: {
|
|
167
|
+
dataKey: "value",
|
|
168
|
+
nameKey: "name",
|
|
169
|
+
innerRadius: 50,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
api,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
expect(typeof pieFn).toBe("function")
|
|
176
|
+
expect(pieFn.isPie).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("should support custom outerRadius", () => {
|
|
180
|
+
const pieFn = pie.renderPie(
|
|
181
|
+
entity,
|
|
182
|
+
{
|
|
183
|
+
config: {
|
|
184
|
+
dataKey: "value",
|
|
185
|
+
nameKey: "name",
|
|
186
|
+
outerRadius: 150,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
api,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
expect(typeof pieFn).toBe("function")
|
|
193
|
+
expect(pieFn.isPie).toBe(true)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it("should support paddingAngle", () => {
|
|
197
|
+
const pieFn = pie.renderPie(
|
|
198
|
+
entity,
|
|
199
|
+
{
|
|
200
|
+
config: {
|
|
201
|
+
dataKey: "value",
|
|
202
|
+
nameKey: "name",
|
|
203
|
+
paddingAngle: 5,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
api,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
expect(typeof pieFn).toBe("function")
|
|
210
|
+
expect(pieFn.isPie).toBe(true)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it("should support minAngle", () => {
|
|
214
|
+
const pieFn = pie.renderPie(
|
|
215
|
+
entity,
|
|
216
|
+
{
|
|
217
|
+
config: {
|
|
218
|
+
dataKey: "value",
|
|
219
|
+
nameKey: "name",
|
|
220
|
+
minAngle: 10,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
api,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
expect(typeof pieFn).toBe("function")
|
|
227
|
+
expect(pieFn.isPie).toBe(true)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it("should support cornerRadius", () => {
|
|
231
|
+
const pieFn = pie.renderPie(
|
|
232
|
+
entity,
|
|
233
|
+
{
|
|
234
|
+
config: {
|
|
235
|
+
dataKey: "value",
|
|
236
|
+
nameKey: "name",
|
|
237
|
+
cornerRadius: 10,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
api,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
expect(typeof pieFn).toBe("function")
|
|
244
|
+
expect(pieFn.isPie).toBe(true)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it("should support custom dataKey and nameKey", () => {
|
|
248
|
+
entity.data = [
|
|
249
|
+
{ product: "Laptop", sales: 150 },
|
|
250
|
+
{ product: "Phone", sales: 200 },
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
const pieFn = pie.renderPie(
|
|
254
|
+
entity,
|
|
255
|
+
{
|
|
256
|
+
config: {
|
|
257
|
+
dataKey: "sales",
|
|
258
|
+
nameKey: "product",
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
api,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(typeof pieFn).toBe("function")
|
|
265
|
+
expect(pieFn.isPie).toBe(true)
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { svg } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders a single curve/path (primitive shape)
|
|
5
|
+
* Similar to Recharts Curve component
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} params
|
|
8
|
+
* @param {string} params.d - Path data (SVG path string)
|
|
9
|
+
* @param {string} [params.stroke] - Stroke color
|
|
10
|
+
* @param {string} [params.fill] - Fill color
|
|
11
|
+
* @param {string} [params.fillOpacity] - Fill opacity
|
|
12
|
+
* @param {string} [params.strokeWidth] - Stroke width
|
|
13
|
+
* @param {string} [params.className] - CSS class
|
|
14
|
+
* @param {Function} [params.onMouseEnter] - Mouse enter handler
|
|
15
|
+
* @param {Function} [params.onMouseLeave] - Mouse leave handler
|
|
16
|
+
* @returns {import('lit-html').TemplateResult}
|
|
17
|
+
*/
|
|
18
|
+
export function renderCurve({
|
|
19
|
+
d,
|
|
20
|
+
stroke,
|
|
21
|
+
fill = "none",
|
|
22
|
+
fillOpacity,
|
|
23
|
+
strokeWidth = "0.15625em",
|
|
24
|
+
className = "iw-chart-curve",
|
|
25
|
+
entityId,
|
|
26
|
+
onMouseEnter,
|
|
27
|
+
onMouseLeave,
|
|
28
|
+
}) {
|
|
29
|
+
const wrappedOnMouseEnter = onMouseEnter
|
|
30
|
+
? (e) => {
|
|
31
|
+
onMouseEnter(e)
|
|
32
|
+
}
|
|
33
|
+
: undefined
|
|
34
|
+
|
|
35
|
+
const wrappedOnMouseLeave = onMouseLeave
|
|
36
|
+
? (e) => {
|
|
37
|
+
onMouseLeave(e)
|
|
38
|
+
}
|
|
39
|
+
: undefined
|
|
40
|
+
|
|
41
|
+
return svg`
|
|
42
|
+
<path
|
|
43
|
+
d=${d}
|
|
44
|
+
stroke=${stroke}
|
|
45
|
+
fill=${fill}
|
|
46
|
+
fill-opacity=${fillOpacity}
|
|
47
|
+
stroke-width=${strokeWidth}
|
|
48
|
+
class=${className}
|
|
49
|
+
data-entity-id=${entityId || undefined}
|
|
50
|
+
style=${stroke ? `stroke: ${stroke} !important;` : undefined}
|
|
51
|
+
@mouseenter=${wrappedOnMouseEnter}
|
|
52
|
+
@mouseleave=${wrappedOnMouseLeave}
|
|
53
|
+
/>
|
|
54
|
+
`
|
|
55
|
+
}
|
package/src/shape/dot.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
|
|
3
|
+
import { svg } from "@inglorious/web"
|
|
4
|
+
|
|
5
|
+
import { isValidNumber } from "../utils/data-utils.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Renders a single dot/circle (primitive shape)
|
|
9
|
+
* Similar to Recharts Dot component
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} params
|
|
12
|
+
* @param {number} params.cx - Center X
|
|
13
|
+
* @param {number} params.cy - Center Y
|
|
14
|
+
* @param {number|string} [params.r] - Radius
|
|
15
|
+
* @param {string} [params.fill] - Fill color
|
|
16
|
+
* @param {string} [params.stroke] - Stroke color
|
|
17
|
+
* @param {string} [params.strokeWidth] - Stroke width
|
|
18
|
+
* @param {string} [params.className] - CSS class
|
|
19
|
+
* @param {Function} [params.onMouseEnter] - Mouse enter handler
|
|
20
|
+
* @param {Function} [params.onMouseLeave] - Mouse leave handler
|
|
21
|
+
* @returns {import('lit-html').TemplateResult}
|
|
22
|
+
*/
|
|
23
|
+
export function renderDot({
|
|
24
|
+
cx,
|
|
25
|
+
cy,
|
|
26
|
+
r = "0.25em",
|
|
27
|
+
fill,
|
|
28
|
+
stroke = "white",
|
|
29
|
+
strokeWidth = "0.125em",
|
|
30
|
+
className = "iw-chart-dot",
|
|
31
|
+
onMouseEnter,
|
|
32
|
+
onMouseLeave,
|
|
33
|
+
}) {
|
|
34
|
+
if (cx == null || cy == null) {
|
|
35
|
+
return svg``
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Parse r to get numeric value and unit
|
|
39
|
+
let rValue = 0.25
|
|
40
|
+
let rUnit = ""
|
|
41
|
+
|
|
42
|
+
if (typeof r === "string") {
|
|
43
|
+
const match = r.match(/([\d.]+)([a-z%]*)/i)
|
|
44
|
+
if (match) {
|
|
45
|
+
rValue = parseFloat(match[1]) || 0.25
|
|
46
|
+
rUnit = match[2] || ""
|
|
47
|
+
} else {
|
|
48
|
+
rValue = parseFloat(r) || 0.25
|
|
49
|
+
}
|
|
50
|
+
} else if (typeof r === "number" && isValidNumber(r) && r > 0) {
|
|
51
|
+
rValue = r
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Ensure rValue is always valid
|
|
55
|
+
if (!isValidNumber(rValue) || rValue <= 0) {
|
|
56
|
+
rValue = 0.25
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const rString = rValue.toString() + rUnit
|
|
60
|
+
|
|
61
|
+
// Handle mouse enter - increase radius
|
|
62
|
+
const handleMouseEnter = (e) => {
|
|
63
|
+
const circle = e.currentTarget
|
|
64
|
+
const currentR = circle.getAttribute("r")
|
|
65
|
+
const match = currentR.match(/([\d.]+)([a-z%]*)/i)
|
|
66
|
+
const currentRValue = match ? parseFloat(match[1]) : rValue
|
|
67
|
+
const currentRUnit = match ? match[2] : rUnit
|
|
68
|
+
|
|
69
|
+
// Store original if not already stored
|
|
70
|
+
if (!circle.getAttribute("data-original-r")) {
|
|
71
|
+
circle.setAttribute("data-original-r", currentR)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Increase by 1.5x (0.25em -> 0.375em)
|
|
75
|
+
const newR = (currentRValue * 1.5).toString() + currentRUnit
|
|
76
|
+
circle.setAttribute("r", newR)
|
|
77
|
+
|
|
78
|
+
if (onMouseEnter) onMouseEnter(e)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle mouse leave - restore original radius
|
|
82
|
+
const handleMouseLeave = (e) => {
|
|
83
|
+
const circle = e.currentTarget
|
|
84
|
+
const originalR = circle.getAttribute("data-original-r") || rString
|
|
85
|
+
circle.setAttribute("r", originalR)
|
|
86
|
+
|
|
87
|
+
if (onMouseLeave) onMouseLeave(e)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return svg`
|
|
91
|
+
<circle
|
|
92
|
+
cx=${cx}
|
|
93
|
+
cy=${cy}
|
|
94
|
+
r=${rString}
|
|
95
|
+
fill=${fill}
|
|
96
|
+
stroke=${stroke}
|
|
97
|
+
stroke-width=${strokeWidth}
|
|
98
|
+
class=${className}
|
|
99
|
+
data-original-r=${rString}
|
|
100
|
+
@mouseenter=${handleMouseEnter}
|
|
101
|
+
@mouseleave=${handleMouseLeave}
|
|
102
|
+
/>
|
|
103
|
+
`
|
|
104
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { svg } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders a single rectangle (primitive shape)
|
|
5
|
+
* Similar to Recharts Rectangle component
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} params
|
|
8
|
+
* @param {number} params.x - X position
|
|
9
|
+
* @param {number} params.y - Y position
|
|
10
|
+
* @param {number} params.width - Width
|
|
11
|
+
* @param {number} params.height - Height
|
|
12
|
+
* @param {string} [params.fill] - Fill color
|
|
13
|
+
* @param {string} [params.className] - CSS class
|
|
14
|
+
* @param {number|string} [params.rx] - Border radius X
|
|
15
|
+
* @param {number|string} [params.ry] - Border radius Y
|
|
16
|
+
* @param {Function} [params.onMouseEnter] - Mouse enter handler
|
|
17
|
+
* @param {Function} [params.onMouseLeave] - Mouse leave handler
|
|
18
|
+
* @returns {import('lit-html').TemplateResult}
|
|
19
|
+
*/
|
|
20
|
+
export function renderRectangle({
|
|
21
|
+
x,
|
|
22
|
+
y,
|
|
23
|
+
width,
|
|
24
|
+
height,
|
|
25
|
+
fill,
|
|
26
|
+
className = "iw-chart-rectangle",
|
|
27
|
+
rx = "0.25em",
|
|
28
|
+
ry = "0.25em",
|
|
29
|
+
onMouseEnter,
|
|
30
|
+
onMouseLeave,
|
|
31
|
+
}) {
|
|
32
|
+
return svg`
|
|
33
|
+
<rect
|
|
34
|
+
x=${x}
|
|
35
|
+
y=${y}
|
|
36
|
+
width=${width}
|
|
37
|
+
height=${height}
|
|
38
|
+
fill=${fill}
|
|
39
|
+
class=${className}
|
|
40
|
+
rx=${rx}
|
|
41
|
+
ry=${ry}
|
|
42
|
+
@mouseenter=${onMouseEnter}
|
|
43
|
+
@mouseleave=${onMouseLeave}
|
|
44
|
+
/>
|
|
45
|
+
`
|
|
46
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
|
|
3
|
+
import { svg } from "@inglorious/web"
|
|
4
|
+
|
|
5
|
+
import { generateArcPath } from "../utils/paths.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Renders a single sector (pie slice) - primitive shape
|
|
9
|
+
* Similar to Recharts Sector component
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} params
|
|
12
|
+
* @param {number} params.innerRadius - Inner radius
|
|
13
|
+
* @param {number} params.outerRadius - Outer radius
|
|
14
|
+
* @param {number} params.startAngle - Start angle in radians
|
|
15
|
+
* @param {number} params.endAngle - End angle in radians
|
|
16
|
+
* @param {number} params.centerX - Center X
|
|
17
|
+
* @param {number} params.centerY - Center Y
|
|
18
|
+
* @param {string} [params.fill] - Fill color
|
|
19
|
+
* @param {string} [params.className] - CSS class
|
|
20
|
+
* @param {number} [params.cornerRadius] - Corner radius for rounded edges
|
|
21
|
+
* @param {Function} [params.onMouseEnter] - Mouse enter handler
|
|
22
|
+
* @param {Function} [params.onMouseLeave] - Mouse leave handler
|
|
23
|
+
* @param {number|string} [params.dataIndex] - Data index for tracking
|
|
24
|
+
* @returns {import('lit-html').TemplateResult}
|
|
25
|
+
*/
|
|
26
|
+
export function renderSector({
|
|
27
|
+
innerRadius,
|
|
28
|
+
outerRadius,
|
|
29
|
+
startAngle,
|
|
30
|
+
endAngle,
|
|
31
|
+
centerX,
|
|
32
|
+
centerY,
|
|
33
|
+
fill,
|
|
34
|
+
className = "iw-chart-sector",
|
|
35
|
+
cornerRadius = 0,
|
|
36
|
+
onMouseEnter,
|
|
37
|
+
onMouseLeave,
|
|
38
|
+
dataIndex,
|
|
39
|
+
}) {
|
|
40
|
+
return svg`
|
|
41
|
+
<g transform="translate(${centerX}, ${centerY})">
|
|
42
|
+
<path
|
|
43
|
+
d=${generateArcPath(
|
|
44
|
+
innerRadius,
|
|
45
|
+
outerRadius,
|
|
46
|
+
startAngle,
|
|
47
|
+
endAngle,
|
|
48
|
+
cornerRadius,
|
|
49
|
+
)}
|
|
50
|
+
fill=${fill}
|
|
51
|
+
class=${className}
|
|
52
|
+
data-slice-index=${dataIndex}
|
|
53
|
+
@mouseenter=${onMouseEnter}
|
|
54
|
+
@mouseleave=${onMouseLeave}
|
|
55
|
+
/>
|
|
56
|
+
</g>
|
|
57
|
+
`
|
|
58
|
+
}
|
package/src/template.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { svg } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types/charts').ChartEntity} ChartEntity
|
|
5
|
+
* @typedef {import('@inglorious/web').Api} Api
|
|
6
|
+
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders the chart component.
|
|
11
|
+
* @param {ChartEntity} entity
|
|
12
|
+
* @param {Api} api
|
|
13
|
+
* @returns {TemplateResult}
|
|
14
|
+
*/
|
|
15
|
+
export function render(entity, api) {
|
|
16
|
+
const chart = api.getType(entity.type)
|
|
17
|
+
if (!chart) {
|
|
18
|
+
return svg`<text x="50%" y="50%" text-anchor="middle" fill="#999">Unknown chart type</text>`
|
|
19
|
+
}
|
|
20
|
+
const renderType = chart.render
|
|
21
|
+
if (!renderType) {
|
|
22
|
+
return svg`<text x="50%" y="50%" text-anchor="middle" fill="#999">Chart renderer not found</text>`
|
|
23
|
+
}
|
|
24
|
+
return renderType(entity, api)
|
|
25
|
+
}
|
package/src/theme.css
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
.iw-chart {
|
|
2
|
+
.iw-chart-svg {
|
|
3
|
+
background-color: white;
|
|
4
|
+
border-radius: 0.5em;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.iw-chart-svg {
|
|
8
|
+
isolation: isolate;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Generic hover styles for lines and curves */
|
|
12
|
+
.iw-chart-line:hover,
|
|
13
|
+
.iw-chart-curve:hover {
|
|
14
|
+
stroke-width: 0.21875em;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.iw-chart-point {
|
|
18
|
+
&:hover {
|
|
19
|
+
r: 0.375em;
|
|
20
|
+
filter: brightness(1.2);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.iw-chart-bar,
|
|
25
|
+
.iw-chart-bar-rectangle {
|
|
26
|
+
&:hover {
|
|
27
|
+
opacity: 0.9;
|
|
28
|
+
filter: brightness(1.1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.iw-chart-area {
|
|
33
|
+
&:hover {
|
|
34
|
+
opacity: 0.9;
|
|
35
|
+
filter: brightness(1.1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.iw-chart-pie-slice,
|
|
40
|
+
.iw-chart-donut-slice {
|
|
41
|
+
&:hover {
|
|
42
|
+
opacity: 0.9;
|
|
43
|
+
filter: brightness(1.15);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.iw-chart-modal {
|
|
48
|
+
background: white;
|
|
49
|
+
border: 1px solid #ccc;
|
|
50
|
+
border-radius: 0.5em;
|
|
51
|
+
padding: 0.75em;
|
|
52
|
+
box-shadow: 0 0.125em 0.5em rgba(0, 0, 0, 0.15);
|
|
53
|
+
min-width: 8.75em;
|
|
54
|
+
font-size: 0.8125em;
|
|
55
|
+
|
|
56
|
+
.iw-chart-modal-header {
|
|
57
|
+
gap: 0.5em;
|
|
58
|
+
margin-bottom: 0.5em;
|
|
59
|
+
padding-bottom: 0.5em;
|
|
60
|
+
border-bottom: 1px solid #eee;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.iw-chart-modal-color {
|
|
64
|
+
width: 0.75em;
|
|
65
|
+
height: 0.75em;
|
|
66
|
+
border-radius: 0.125em;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.iw-chart-modal-label {
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
color: #333;
|
|
72
|
+
font-size: 0.8125em;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.iw-chart-modal-body {
|
|
76
|
+
gap: 0.25em;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.iw-chart-modal-value {
|
|
80
|
+
font-size: 1em;
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
color: #333;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.iw-chart-modal-percentage {
|
|
86
|
+
font-size: 0.75em;
|
|
87
|
+
color: #999;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|