@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,445 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
import { html, svg } from "@inglorious/web"
|
|
3
|
+
import { extent } from "d3-array"
|
|
4
|
+
import { scaleBand } from "d3-scale"
|
|
5
|
+
|
|
6
|
+
import { createBrushComponent } from "../component/brush.js"
|
|
7
|
+
import { renderGrid } from "../component/grid.js"
|
|
8
|
+
import { createTooltipComponent, renderTooltip } from "../component/tooltip.js"
|
|
9
|
+
import { renderXAxis } from "../component/x-axis.js"
|
|
10
|
+
import { renderYAxis } from "../component/y-axis.js"
|
|
11
|
+
import { renderRectangle } from "../shape/rectangle.js"
|
|
12
|
+
import { renderCartesianLayout } from "../utils/cartesian-layout.js"
|
|
13
|
+
import { calculatePadding } from "../utils/padding.js"
|
|
14
|
+
import { processDeclarativeChild } from "../utils/process-declarative-child.js"
|
|
15
|
+
import { createCartesianContext } from "../utils/scales.js"
|
|
16
|
+
import { createTooltipHandlers } from "../utils/tooltip-handlers.js"
|
|
17
|
+
|
|
18
|
+
export const bar = {
|
|
19
|
+
/**
|
|
20
|
+
* Config-based rendering entry point.
|
|
21
|
+
* Builds default composition children from entity options and delegates to
|
|
22
|
+
* `renderBarChart`.
|
|
23
|
+
* @param {import('../types/charts').ChartEntity} entity
|
|
24
|
+
* @param {import('@inglorious/web').Api} api
|
|
25
|
+
* @returns {import('lit-html').TemplateResult}
|
|
26
|
+
*/
|
|
27
|
+
render(entity, api) {
|
|
28
|
+
const type = api.getType(entity.type)
|
|
29
|
+
const children = [
|
|
30
|
+
entity.showGrid !== false
|
|
31
|
+
? type.renderCartesianGrid(entity, {}, api)
|
|
32
|
+
: null,
|
|
33
|
+
type.renderXAxis(entity, {}, api),
|
|
34
|
+
type.renderYAxis(entity, {}, api),
|
|
35
|
+
type.renderBar(
|
|
36
|
+
entity,
|
|
37
|
+
{ config: { dataKey: "value", multiColor: false } },
|
|
38
|
+
api,
|
|
39
|
+
),
|
|
40
|
+
].filter(Boolean)
|
|
41
|
+
|
|
42
|
+
const chartContent = type.renderBarChart(
|
|
43
|
+
entity,
|
|
44
|
+
{
|
|
45
|
+
children,
|
|
46
|
+
config: {
|
|
47
|
+
width: entity.width,
|
|
48
|
+
height: entity.height,
|
|
49
|
+
isRawSVG: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
api,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return renderCartesianLayout(
|
|
56
|
+
entity,
|
|
57
|
+
{
|
|
58
|
+
chartType: "bar",
|
|
59
|
+
chartContent,
|
|
60
|
+
showLegend: false,
|
|
61
|
+
},
|
|
62
|
+
api,
|
|
63
|
+
)
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Composition rendering entry point for bar charts.
|
|
68
|
+
* @param {import('../types/charts').ChartEntity} entity
|
|
69
|
+
* @param {{ children: any[]|any, config?: Record<string, any> }} params
|
|
70
|
+
* @param {import('@inglorious/web').Api} api
|
|
71
|
+
* @returns {import('lit-html').TemplateResult}
|
|
72
|
+
*/
|
|
73
|
+
renderBarChart(entity, { children, config = {} }, api) {
|
|
74
|
+
if (!entity) return html`<div>Entity not found</div>`
|
|
75
|
+
if (!entity.data || !Array.isArray(entity.data)) {
|
|
76
|
+
return html`<div>Entity data is missing or invalid</div>`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const width = config.width || entity.width || 800
|
|
80
|
+
const height = config.height || entity.height || 400
|
|
81
|
+
const padding = calculatePadding(width, height)
|
|
82
|
+
|
|
83
|
+
const childrenArray = (
|
|
84
|
+
Array.isArray(children) ? children : [children]
|
|
85
|
+
).filter(Boolean)
|
|
86
|
+
|
|
87
|
+
const processedChildrenArray = childrenArray
|
|
88
|
+
.map((child) => processDeclarativeChild(child, entity, "bar", api))
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
|
|
91
|
+
// Separate components using stable flags (survives minification)
|
|
92
|
+
// This ensures correct Z-index ordering: Grid -> Bars -> Axes
|
|
93
|
+
const grid = []
|
|
94
|
+
const axes = []
|
|
95
|
+
const bars = []
|
|
96
|
+
const tooltip = []
|
|
97
|
+
const others = []
|
|
98
|
+
|
|
99
|
+
for (const child of processedChildrenArray) {
|
|
100
|
+
// Use stable flags instead of string matching (survives minification)
|
|
101
|
+
if (typeof child === "function") {
|
|
102
|
+
// If it's already marked, add to the correct bucket
|
|
103
|
+
if (child.isGrid) {
|
|
104
|
+
grid.push(child)
|
|
105
|
+
} else if (child.isAxis) {
|
|
106
|
+
axes.push(child)
|
|
107
|
+
} else if (child.isBar) {
|
|
108
|
+
bars.push(child)
|
|
109
|
+
} else if (child.isTooltip) {
|
|
110
|
+
tooltip.push(child)
|
|
111
|
+
} else {
|
|
112
|
+
// It's a lazy function from index.js - we'll identify its type during processing
|
|
113
|
+
// For now, add to others - it will be processed correctly in the final loop
|
|
114
|
+
others.push(child)
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
others.push(child)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Store barComponents for Y-axis calculation
|
|
122
|
+
const barComponents = bars
|
|
123
|
+
|
|
124
|
+
// 2. FUNDAMENTAL SCALE - Crucial for alignment
|
|
125
|
+
const categories = entity.data.map(
|
|
126
|
+
(d) => d.label || d.name || d.category || "",
|
|
127
|
+
)
|
|
128
|
+
const xScale = scaleBand()
|
|
129
|
+
.domain(categories)
|
|
130
|
+
.range([padding.left, width - padding.right])
|
|
131
|
+
.padding(0.1)
|
|
132
|
+
|
|
133
|
+
const context = createCartesianContext(
|
|
134
|
+
{ ...entity, width, height, padding },
|
|
135
|
+
"bar",
|
|
136
|
+
)
|
|
137
|
+
context.xScale = xScale
|
|
138
|
+
context.dimensions = { width, height, padding }
|
|
139
|
+
context.chartType = "bar" // Include chartType for lazy components
|
|
140
|
+
|
|
141
|
+
// 3. Identify data keys for Y-axis
|
|
142
|
+
const dataKeys =
|
|
143
|
+
config.dataKeys || barComponents.map((c) => c.dataKey || "value")
|
|
144
|
+
|
|
145
|
+
const allValues = entity.data.flatMap((d) =>
|
|
146
|
+
dataKeys.map((k) => d[k]).filter((v) => typeof v === "number"),
|
|
147
|
+
)
|
|
148
|
+
if (allValues.length > 0) {
|
|
149
|
+
const [minVal, maxVal] = extent(allValues)
|
|
150
|
+
context.yScale.domain([Math.min(0, minVal), maxVal]).nice()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 4. Process children from 'others' to identify their real types (lazy functions from index.js)
|
|
154
|
+
// This ensures grid/axes from index.js are placed in the correct buckets
|
|
155
|
+
const identifiedGrid = []
|
|
156
|
+
const identifiedAxes = []
|
|
157
|
+
const remainingOthers = []
|
|
158
|
+
|
|
159
|
+
for (const child of others) {
|
|
160
|
+
if (typeof child === "function") {
|
|
161
|
+
try {
|
|
162
|
+
const result = child(context)
|
|
163
|
+
if (typeof result === "function") {
|
|
164
|
+
if (result.isGrid) {
|
|
165
|
+
identifiedGrid.push(child) // Keep the original lazy function
|
|
166
|
+
} else if (result.isAxis) {
|
|
167
|
+
identifiedAxes.push(child)
|
|
168
|
+
} else {
|
|
169
|
+
remainingOthers.push(child)
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
remainingOthers.push(child)
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
remainingOthers.push(child)
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
remainingOthers.push(child)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Reorder children for correct Z-index: Grid -> Bars -> Axes -> Tooltip -> Others
|
|
183
|
+
// This ensures grid is behind, bars are in the middle, and axes are on top
|
|
184
|
+
const childrenToProcess = [
|
|
185
|
+
...grid,
|
|
186
|
+
...identifiedGrid, // Grids identified from others
|
|
187
|
+
...bars,
|
|
188
|
+
...axes,
|
|
189
|
+
...identifiedAxes, // Axes identified from others
|
|
190
|
+
...tooltip,
|
|
191
|
+
...remainingOthers,
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
// Process children to handle lazy functions (like renderCartesianGrid from index.js)
|
|
195
|
+
// Flow:
|
|
196
|
+
// 1. renderCartesianGrid/renderXAxis from index.js return (ctx) => { return chartType.renderCartesianGrid(...) }
|
|
197
|
+
// 2. chartType.renderCartesianGrid (from bar.js) returns gridFn which is (ctx) => { return svg... }
|
|
198
|
+
// 3. So we need: child(context) -> gridFn, then gridFn(context) -> svg
|
|
199
|
+
// Simplified deterministic approach: all functions from index.js return (ctx) => ..., so we can safely call with context
|
|
200
|
+
const processedChildren = childrenToProcess.map((child) => {
|
|
201
|
+
// Non-function children are passed through as-is
|
|
202
|
+
if (typeof child !== "function") {
|
|
203
|
+
return child
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// If it's a marked component (isGrid, isBar, isAxis, etc), it expects context directly
|
|
207
|
+
if (child.isGrid || child.isAxis || child.isBar || child.isTooltip) {
|
|
208
|
+
// For bars, also pass barIndex and totalBars
|
|
209
|
+
if (child.isBar) {
|
|
210
|
+
const barIndex = barComponents.indexOf(child)
|
|
211
|
+
return child(context, barIndex, barComponents.length)
|
|
212
|
+
}
|
|
213
|
+
return child(context)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// If it's a function from index.js (renderCartesianGrid, etc),
|
|
217
|
+
// it returns another function that also expects context
|
|
218
|
+
const result = child(context)
|
|
219
|
+
// If the result is a function (marked component), call it with context
|
|
220
|
+
if (typeof result === "function") {
|
|
221
|
+
// For bars, also pass barIndex and totalBars
|
|
222
|
+
if (result.isBar) {
|
|
223
|
+
const barIndex = barComponents.indexOf(result)
|
|
224
|
+
return result(context, barIndex, barComponents.length)
|
|
225
|
+
}
|
|
226
|
+
return result(context)
|
|
227
|
+
}
|
|
228
|
+
// Otherwise, return the result directly (already SVG or TemplateResult)
|
|
229
|
+
return result
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const svgContent = svg`
|
|
233
|
+
<svg width=${width} height=${height} viewBox="0 0 ${width} ${height}">
|
|
234
|
+
${processedChildren}
|
|
235
|
+
</svg>
|
|
236
|
+
`
|
|
237
|
+
|
|
238
|
+
if (config.isRawSVG) return svgContent
|
|
239
|
+
|
|
240
|
+
return html`
|
|
241
|
+
<div
|
|
242
|
+
class="iw-chart"
|
|
243
|
+
style="display: block; position: relative; width: 100%; box-sizing: border-box;"
|
|
244
|
+
>
|
|
245
|
+
${svgContent} ${renderTooltip(entity, {}, api)}
|
|
246
|
+
</div>
|
|
247
|
+
`
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Composition sub-render for bars.
|
|
252
|
+
* @param {import('../types/charts').ChartEntity} entity
|
|
253
|
+
* @param {{ config?: Record<string, any> }} params
|
|
254
|
+
* @param {import('@inglorious/web').Api} api
|
|
255
|
+
* @returns {(ctx: Record<string, any>, barIndex: number, totalBars: number) => import('lit-html').TemplateResult}
|
|
256
|
+
*/
|
|
257
|
+
renderBar(entity, { config = {} }, api) {
|
|
258
|
+
// Preserve config values in closure
|
|
259
|
+
const { dataKey = "value", fill, multiColor = false } = config
|
|
260
|
+
const drawFn = (ctx, barIndex, totalBars) => {
|
|
261
|
+
const entityFromContext = ctx.entity || entity
|
|
262
|
+
if (!entityFromContext) return svg``
|
|
263
|
+
const entityColors = entityFromContext.colors || [
|
|
264
|
+
"#8884d8",
|
|
265
|
+
"#82ca9d",
|
|
266
|
+
"#ffc658",
|
|
267
|
+
"#ff7300",
|
|
268
|
+
]
|
|
269
|
+
const { xScale, yScale, dimensions } = ctx
|
|
270
|
+
|
|
271
|
+
// When there's only one bar, center it in the band without using subScale
|
|
272
|
+
let barWidth, xOffset
|
|
273
|
+
if (totalBars === 1) {
|
|
274
|
+
// Single bar: occupies 80% of the band and is centered
|
|
275
|
+
const bandwidth = xScale.bandwidth()
|
|
276
|
+
barWidth = bandwidth * 0.8
|
|
277
|
+
xOffset = (bandwidth - barWidth) / 2 // Center
|
|
278
|
+
} else {
|
|
279
|
+
// Multiple bars: use subScale to group them
|
|
280
|
+
const subScale = scaleBand()
|
|
281
|
+
.domain(Array.from({ length: totalBars }, (_, i) => i))
|
|
282
|
+
.range([0, xScale.bandwidth()])
|
|
283
|
+
.padding(0.1)
|
|
284
|
+
barWidth = subScale.bandwidth()
|
|
285
|
+
xOffset = subScale(barIndex)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return svg`
|
|
289
|
+
${entityFromContext.data.map((d, i) => {
|
|
290
|
+
const category = d.label || d.name || d.category || String(i)
|
|
291
|
+
const value = d[dataKey] ?? 0
|
|
292
|
+
const bandStart = xScale(category)
|
|
293
|
+
|
|
294
|
+
// Skip if bandStart is undefined or NaN (invalid category)
|
|
295
|
+
if (bandStart == null || isNaN(bandStart)) {
|
|
296
|
+
return svg``
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const x = bandStart + xOffset
|
|
300
|
+
const y = yScale(value)
|
|
301
|
+
|
|
302
|
+
// Calculate bar height: distance from top (y) to bottom of chart area
|
|
303
|
+
const chartBottom = dimensions.height - dimensions.padding.bottom
|
|
304
|
+
const barHeight = Math.max(0, chartBottom - y)
|
|
305
|
+
|
|
306
|
+
// Skip if bar has no height or invalid dimensions
|
|
307
|
+
if (barHeight <= 0 || isNaN(barHeight) || isNaN(x) || isNaN(y)) {
|
|
308
|
+
return svg``
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const color = multiColor
|
|
312
|
+
? d.color || entityColors[i % entityColors.length]
|
|
313
|
+
: fill || d.color || entityColors[barIndex % entityColors.length]
|
|
314
|
+
|
|
315
|
+
const { onMouseEnter, onMouseLeave } = createTooltipHandlers({
|
|
316
|
+
entity: entityFromContext,
|
|
317
|
+
api,
|
|
318
|
+
tooltipData: { label: category, value, color },
|
|
319
|
+
})
|
|
320
|
+
return renderRectangle({
|
|
321
|
+
x,
|
|
322
|
+
y,
|
|
323
|
+
width: barWidth,
|
|
324
|
+
height: barHeight,
|
|
325
|
+
fill: color,
|
|
326
|
+
onMouseEnter,
|
|
327
|
+
onMouseLeave,
|
|
328
|
+
})
|
|
329
|
+
})}
|
|
330
|
+
`
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
drawFn.isBar = true
|
|
334
|
+
drawFn.dataKey = config.dataKey || "value"
|
|
335
|
+
return drawFn
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Composition sub-render for X axis.
|
|
340
|
+
* @param {import('../types/charts').ChartEntity} entity
|
|
341
|
+
* @param {{ config?: Record<string, any> }} params
|
|
342
|
+
* @param {import('@inglorious/web').Api} api
|
|
343
|
+
* @returns {(ctx: Record<string, any>) => import('lit-html').TemplateResult}
|
|
344
|
+
*/
|
|
345
|
+
renderXAxis(entity, { config = {} }, api) {
|
|
346
|
+
// Return a function that preserves the original object
|
|
347
|
+
// This prevents lit-html from evaluating the function before passing it
|
|
348
|
+
const renderFn = (ctx) => {
|
|
349
|
+
const entityFromContext = ctx.entity || entity
|
|
350
|
+
if (!entityFromContext) return svg``
|
|
351
|
+
return renderXAxis(
|
|
352
|
+
entityFromContext,
|
|
353
|
+
{
|
|
354
|
+
xScale: ctx.xScale,
|
|
355
|
+
yScale: ctx.yScale,
|
|
356
|
+
padding: ctx.dimensions.padding,
|
|
357
|
+
width: ctx.dimensions.width,
|
|
358
|
+
height: ctx.dimensions.height,
|
|
359
|
+
},
|
|
360
|
+
api,
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
// Mark as axis component for stable identification (consistent with area.js)
|
|
364
|
+
renderFn.isAxis = true
|
|
365
|
+
renderFn.config = config
|
|
366
|
+
renderFn.api = api
|
|
367
|
+
// Add a special property to prevent lit-html from rendering directly
|
|
368
|
+
// This makes the function be treated as a lit-html "directive"
|
|
369
|
+
Object.defineProperty(renderFn, "_$litType$", {
|
|
370
|
+
value: undefined,
|
|
371
|
+
writable: false,
|
|
372
|
+
enumerable: false,
|
|
373
|
+
configurable: false,
|
|
374
|
+
})
|
|
375
|
+
// Return the marked function
|
|
376
|
+
return renderFn
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Composition sub-render for Y axis.
|
|
381
|
+
* @param {import('../types/charts').ChartEntity} entity
|
|
382
|
+
* @param {{ config?: Record<string, any> }} params
|
|
383
|
+
* @param {import('@inglorious/web').Api} api
|
|
384
|
+
* @returns {(ctx: Record<string, any>) => import('lit-html').TemplateResult}
|
|
385
|
+
*/
|
|
386
|
+
renderYAxis(entity, props, api) {
|
|
387
|
+
const axisFn = (ctx) => {
|
|
388
|
+
const entityFromContext = ctx.entity || entity
|
|
389
|
+
return renderYAxis(
|
|
390
|
+
entityFromContext,
|
|
391
|
+
{
|
|
392
|
+
yScale: ctx.yScale,
|
|
393
|
+
...ctx.dimensions,
|
|
394
|
+
customTicks: ctx.yScale.ticks
|
|
395
|
+
? ctx.yScale.ticks(5)
|
|
396
|
+
: ctx.yScale.domain(),
|
|
397
|
+
},
|
|
398
|
+
api,
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
// Mark as axis component for stable identification (consistent with area.js)
|
|
402
|
+
axisFn.isAxis = true
|
|
403
|
+
return axisFn
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Composition sub-render for cartesian grid.
|
|
408
|
+
* @param {import('../types/charts').ChartEntity} entity
|
|
409
|
+
* @param {{ config?: Record<string, any> }} params
|
|
410
|
+
* @param {import('@inglorious/web').Api} api
|
|
411
|
+
* @returns {(ctx: Record<string, any>) => import('lit-html').TemplateResult}
|
|
412
|
+
*/
|
|
413
|
+
renderCartesianGrid(entity, { config = {} }, api) {
|
|
414
|
+
const gridFn = (ctx) => {
|
|
415
|
+
const entityFromContext = ctx.entity || entity
|
|
416
|
+
if (!entityFromContext) return svg``
|
|
417
|
+
return renderGrid(
|
|
418
|
+
entityFromContext,
|
|
419
|
+
{
|
|
420
|
+
...ctx.dimensions,
|
|
421
|
+
xScale: ctx.xScale,
|
|
422
|
+
yScale: ctx.yScale,
|
|
423
|
+
stroke: config.stroke || "#eee",
|
|
424
|
+
strokeDasharray: config.strokeDasharray || "5 5",
|
|
425
|
+
},
|
|
426
|
+
api,
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
// Mark as grid component for stable identification (consistent with area.js)
|
|
430
|
+
gridFn.isGrid = true
|
|
431
|
+
return gridFn
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Composition sub-render for tooltip overlay.
|
|
436
|
+
* @type {(entity: import('../types/charts').ChartEntity, params: { config?: Record<string, any> }, api: import('@inglorious/web').Api) => (ctx: Record<string, any>) => import('lit-html').TemplateResult}
|
|
437
|
+
*/
|
|
438
|
+
renderTooltip: createTooltipComponent(),
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Composition sub-render for brush control.
|
|
442
|
+
* @type {(entity: import('../types/charts').ChartEntity, params: { config?: Record<string, any> }, api: import('@inglorious/web').Api) => (ctx: Record<string, any>) => import('lit-html').TemplateResult}
|
|
443
|
+
*/
|
|
444
|
+
renderBrush: createBrushComponent(),
|
|
445
|
+
}
|