@ojiepermana/angular-chart 22.0.27

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 (32) hide show
  1. package/README.md +249 -0
  2. package/fesm2022/ojiepermana-angular-chart-area.mjs +266 -0
  3. package/fesm2022/ojiepermana-angular-chart-area.mjs.map +1 -0
  4. package/fesm2022/ojiepermana-angular-chart-bar.mjs +674 -0
  5. package/fesm2022/ojiepermana-angular-chart-bar.mjs.map +1 -0
  6. package/fesm2022/ojiepermana-angular-chart-core.mjs +764 -0
  7. package/fesm2022/ojiepermana-angular-chart-core.mjs.map +1 -0
  8. package/fesm2022/ojiepermana-angular-chart-line.mjs +281 -0
  9. package/fesm2022/ojiepermana-angular-chart-line.mjs.map +1 -0
  10. package/fesm2022/ojiepermana-angular-chart-pie.mjs +248 -0
  11. package/fesm2022/ojiepermana-angular-chart-pie.mjs.map +1 -0
  12. package/fesm2022/ojiepermana-angular-chart-primitives.mjs +1186 -0
  13. package/fesm2022/ojiepermana-angular-chart-primitives.mjs.map +1 -0
  14. package/fesm2022/ojiepermana-angular-chart-radar.mjs +329 -0
  15. package/fesm2022/ojiepermana-angular-chart-radar.mjs.map +1 -0
  16. package/fesm2022/ojiepermana-angular-chart-radial.mjs +255 -0
  17. package/fesm2022/ojiepermana-angular-chart-radial.mjs.map +1 -0
  18. package/fesm2022/ojiepermana-angular-chart-scatter.mjs +253 -0
  19. package/fesm2022/ojiepermana-angular-chart-scatter.mjs.map +1 -0
  20. package/fesm2022/ojiepermana-angular-chart.mjs +20 -0
  21. package/fesm2022/ojiepermana-angular-chart.mjs.map +1 -0
  22. package/package.json +76 -0
  23. package/types/ojiepermana-angular-chart-area.d.ts +58 -0
  24. package/types/ojiepermana-angular-chart-bar.d.ts +171 -0
  25. package/types/ojiepermana-angular-chart-core.d.ts +369 -0
  26. package/types/ojiepermana-angular-chart-line.d.ts +57 -0
  27. package/types/ojiepermana-angular-chart-pie.d.ts +93 -0
  28. package/types/ojiepermana-angular-chart-primitives.d.ts +265 -0
  29. package/types/ojiepermana-angular-chart-radar.d.ts +89 -0
  30. package/types/ojiepermana-angular-chart-radial.d.ts +86 -0
  31. package/types/ojiepermana-angular-chart-scatter.d.ts +95 -0
  32. package/types/ojiepermana-angular-chart.d.ts +2 -0
@@ -0,0 +1,764 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, computed, Injectable, inject, ElementRef, Renderer2, effect, ChangeDetectionStrategy, Component, NgZone, PLATFORM_ID, DestroyRef, viewChild, input, afterNextRender } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+ import { scalePoint, scaleLinear, scaleBand } from 'd3-scale';
5
+ import { max } from 'd3-array';
6
+ import { curveLinear, curveStep, curveMonotoneX, line, area, stack, stackOffsetExpand } from 'd3-shape';
7
+
8
+ /** CSS selector under which a chart instance is scoped. */
9
+ const CHART_DATA_ATTRIBUTE = 'data-chart';
10
+ /** Default color schemes supported by the generated `<style>` block. */
11
+ const CHART_THEMES = [
12
+ { key: 'light', selector: '' },
13
+ { key: 'dark', selector: '[data-mode="dark"]' },
14
+ ];
15
+ /**
16
+ * Generate the CSS rule-set for a chart instance: one `--color-<key>` per
17
+ * series, scoped to the owning `[data-chart]` element, with optional dark
18
+ * variant via `[data-mode="dark"] [data-chart="…"]`.
19
+ *
20
+ * Series without any color are skipped (consumer can fall back to a default).
21
+ *
22
+ * @param chartId Unique chart id (used as attribute value).
23
+ * @param config Series configuration map.
24
+ */
25
+ function buildChartCss(chartId, config) {
26
+ const entries = Object.entries(config).filter(([, cfg]) => cfg.color || cfg.theme);
27
+ if (entries.length === 0) {
28
+ return '';
29
+ }
30
+ return CHART_THEMES.map(({ key, selector }) => {
31
+ const vars = entries
32
+ .map(([seriesKey, cfg]) => {
33
+ const value = cfg.theme?.[key] ?? cfg.color;
34
+ return value ? ` --color-${escapeCssIdent(seriesKey)}: ${value};` : '';
35
+ })
36
+ .filter(Boolean)
37
+ .join('\n');
38
+ if (!vars) {
39
+ return '';
40
+ }
41
+ const scope = selector
42
+ ? `${selector} [${CHART_DATA_ATTRIBUTE}="${chartId}"]`
43
+ : `[${CHART_DATA_ATTRIBUTE}="${chartId}"]`;
44
+ return `${scope} {\n${vars}\n}`;
45
+ })
46
+ .filter(Boolean)
47
+ .join('\n');
48
+ }
49
+ /**
50
+ * Escape a string so it is safe to use as a CSS custom-property identifier.
51
+ * Allows `[A-Za-z0-9_-]`; everything else becomes `_`.
52
+ */
53
+ function escapeCssIdent(input) {
54
+ return input.replace(/[^a-zA-Z0-9_-]/g, '_');
55
+ }
56
+ /** Resolve the `var(--color-<key>)` reference for a given series. */
57
+ function seriesColorVar(seriesKey) {
58
+ return `var(--color-${seriesKey.replace(/[^a-zA-Z0-9_-]/g, '_')})`;
59
+ }
60
+
61
+ /**
62
+ * Shared chart state provided by `ChartContainer` to all nested chart
63
+ * components (axes, grid, tooltip, legend, chart types).
64
+ *
65
+ * All state is exposed as signals so consumers can derive computed state
66
+ * (scales, visible series, tooltip position) without manual subscriptions.
67
+ */
68
+ class ChartContext {
69
+ /** Stable instance id — used in the `data-chart` attribute and CSS scope. */
70
+ id = signal('', /* @ts-ignore */
71
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
72
+ /** User-provided series config. */
73
+ config = signal({}, /* @ts-ignore */
74
+ ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
75
+ /** Measured render-area dimensions (ResizeObserver-driven). */
76
+ dimensions = signal({ width: 0, height: 0 }, /* @ts-ignore */
77
+ ...(ngDevMode ? [{ debugName: "dimensions" }] : /* istanbul ignore next */ []));
78
+ /** Currently highlighted data point (tooltip / crosshair). */
79
+ activePoint = signal(null, /* @ts-ignore */
80
+ ...(ngDevMode ? [{ debugName: "activePoint" }] : /* istanbul ignore next */ []));
81
+ /** Series keys the user has toggled off via legend. */
82
+ hiddenSeries = signal(new Set(), /* @ts-ignore */
83
+ ...(ngDevMode ? [{ debugName: "hiddenSeries" }] : /* istanbul ignore next */ []));
84
+ /** Ordered list of series keys (from `config`). */
85
+ seriesKeys = computed(() => Object.keys(this.config()), /* @ts-ignore */
86
+ ...(ngDevMode ? [{ debugName: "seriesKeys" }] : /* istanbul ignore next */ []));
87
+ /** Series keys currently visible (config order minus `hiddenSeries`). */
88
+ visibleSeriesKeys = computed(() => {
89
+ const hidden = this.hiddenSeries();
90
+ return this.seriesKeys().filter((k) => !hidden.has(k));
91
+ }, /* @ts-ignore */
92
+ ...(ngDevMode ? [{ debugName: "visibleSeriesKeys" }] : /* istanbul ignore next */ []));
93
+ /** Toggle visibility of a series. */
94
+ toggleSeries(key) {
95
+ this.hiddenSeries.update((set) => {
96
+ const next = new Set(set);
97
+ if (next.has(key)) {
98
+ next.delete(key);
99
+ }
100
+ else {
101
+ next.add(key);
102
+ }
103
+ return next;
104
+ });
105
+ }
106
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
107
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartContext });
108
+ }
109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartContext, decorators: [{
110
+ type: Injectable
111
+ }] });
112
+
113
+ /**
114
+ * Emits a scoped `<style>` block mapping every series key in the current
115
+ * `ChartConfig` to a `--color-<key>` CSS custom property, scoped to
116
+ * `[data-chart="<id>"]`. Dark-mode values are scoped under `[data-mode="dark"]`.
117
+ *
118
+ * Implemented as an empty component that injects a real `<style>` element
119
+ * into its host via `Renderer2`. We avoid rendering `<style>` directly in a
120
+ * template — Angular hoists those into component CSS and strips
121
+ * interpolations from them.
122
+ *
123
+ * Consumers never instantiate this directly — `ChartContainer` renders it.
124
+ */
125
+ class ChartStyle {
126
+ ctx = inject(ChartContext);
127
+ host = inject((ElementRef));
128
+ renderer = inject(Renderer2);
129
+ styleEl = null;
130
+ css = computed(() => buildChartCss(this.ctx.id(), this.ctx.config()), /* @ts-ignore */
131
+ ...(ngDevMode ? [{ debugName: "css" }] : /* istanbul ignore next */ []));
132
+ constructor() {
133
+ effect(() => {
134
+ const css = this.css();
135
+ if (!this.styleEl) {
136
+ this.styleEl = this.renderer.createElement('style');
137
+ this.renderer.appendChild(this.host.nativeElement, this.styleEl);
138
+ }
139
+ this.styleEl.textContent = css;
140
+ });
141
+ }
142
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartStyle, deps: [], target: i0.ɵɵFactoryTarget.Component });
143
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.2", type: ChartStyle, isStandalone: true, selector: "ChartStyle", ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
144
+ }
145
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartStyle, decorators: [{
146
+ type: Component,
147
+ args: [{
148
+ selector: 'ChartStyle',
149
+ changeDetection: ChangeDetectionStrategy.OnPush,
150
+ template: '',
151
+ }]
152
+ }], ctorParameters: () => [] });
153
+
154
+ let chartIdCounter = 0;
155
+ /**
156
+ * Root of every chart. Provides `ChartContext` to descendants, reflects the
157
+ * chart id via `data-chart`, injects the per-instance CSS-variable
158
+ * `<style>` block, and tracks its render-area dimensions via
159
+ * `ResizeObserver`.
160
+ *
161
+ * Usage:
162
+ * ```html
163
+ * <Chart [config]="cfg">
164
+ * <ChartBar [data]="data" />
165
+ * <ChartLegend />
166
+ * </Chart>
167
+ * ```
168
+ *
169
+ * The chart render area is laid out on top; any projected `<ChartLegend>` is
170
+ * rendered as a centered row beneath it. Render-area dimensions are measured on
171
+ * the chart area only, so the legend never shrinks the chart.
172
+ */
173
+ class ChartContainer {
174
+ ctx = inject(ChartContext);
175
+ zone = inject(NgZone);
176
+ platformId = inject(PLATFORM_ID);
177
+ destroyRef = inject(DestroyRef);
178
+ areaRef = viewChild.required('area');
179
+ /** Series configuration. Required for color / label resolution. */
180
+ config = input.required(/* @ts-ignore */
181
+ ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
182
+ /**
183
+ * Tailwind aspect-ratio utility for the chart render area. Defaults to
184
+ * `aspect-video` for cartesian charts; override with `aspect-square` for
185
+ * radial / pie layouts, or e.g. `aspect-auto h-[320px]` for a fixed height.
186
+ */
187
+ aspect = input('aspect-video', /* @ts-ignore */
188
+ ...(ngDevMode ? [{ debugName: "aspect" }] : /* istanbul ignore next */ []));
189
+ areaClass = computed(() => `relative flex ${this.aspect()} w-full justify-center`, /* @ts-ignore */
190
+ ...(ngDevMode ? [{ debugName: "areaClass" }] : /* istanbul ignore next */ []));
191
+ /**
192
+ * Optional explicit id override. When omitted, a stable auto-id is
193
+ * generated (`chart-<n>`), unique across the document.
194
+ */
195
+ chartId = input(null, /* @ts-ignore */
196
+ ...(ngDevMode ? [{ debugName: "chartId" }] : /* istanbul ignore next */ []));
197
+ constructor() {
198
+ const autoId = `chart-${++chartIdCounter}`;
199
+ // Sync id + config into the shared context.
200
+ effect(() => {
201
+ this.ctx.id.set(this.chartId() ?? autoId);
202
+ });
203
+ effect(() => {
204
+ this.ctx.config.set(this.config());
205
+ });
206
+ // Observe host size (browser only; client-only is a confirmed constraint).
207
+ if (isPlatformBrowser(this.platformId)) {
208
+ afterNextRender(() => this.observeSize());
209
+ }
210
+ }
211
+ observeSize() {
212
+ const el = this.areaRef().nativeElement;
213
+ if (typeof ResizeObserver === 'undefined') {
214
+ const rect = el.getBoundingClientRect();
215
+ this.ctx.dimensions.set({ width: rect.width, height: rect.height });
216
+ return;
217
+ }
218
+ const observer = new ResizeObserver((entries) => {
219
+ for (const entry of entries) {
220
+ const { width, height } = entry.contentRect;
221
+ // Avoid NgZone churn — dimensions signal drives CD on its own.
222
+ this.zone.run(() => this.ctx.dimensions.set({ width, height }));
223
+ }
224
+ });
225
+ observer.observe(el);
226
+ this.destroyRef.onDestroy(() => observer.disconnect());
227
+ }
228
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.2", type: ChartContainer, isStandalone: true, selector: "Chart", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, aspect: { classPropertyName: "aspect", publicName: "aspect", isSignal: true, isRequired: false, transformFunction: null }, chartId: { classPropertyName: "chartId", publicName: "chartId", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-chart": "ctx.id()" }, classAttribute: "relative flex flex-col text-xs" }, providers: [ChartContext], viewQueries: [{ propertyName: "areaRef", first: true, predicate: ["area"], descendants: true, isSignal: true }], ngImport: i0, template: `
230
+ <ChartStyle />
231
+ <div #area data-chart-area [class]="areaClass()">
232
+ <ng-content />
233
+ </div>
234
+ <ng-content select="ChartLegend" />
235
+ `, isInline: true, dependencies: [{ kind: "component", type: ChartStyle, selector: "ChartStyle" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
236
+ }
237
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartContainer, decorators: [{
238
+ type: Component,
239
+ args: [{
240
+ selector: 'Chart',
241
+ changeDetection: ChangeDetectionStrategy.OnPush,
242
+ providers: [ChartContext],
243
+ imports: [ChartStyle],
244
+ host: {
245
+ '[attr.data-chart]': 'ctx.id()',
246
+ class: 'relative flex flex-col text-xs',
247
+ },
248
+ template: `
249
+ <ChartStyle />
250
+ <div #area data-chart-area [class]="areaClass()">
251
+ <ng-content />
252
+ </div>
253
+ <ng-content select="ChartLegend" />
254
+ `,
255
+ }]
256
+ }], ctorParameters: () => [], propDecorators: { areaRef: [{ type: i0.ViewChild, args: ['area', { isSignal: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], aspect: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspect", required: false }] }], chartId: [{ type: i0.Input, args: [{ isSignal: true, alias: "chartId", required: false }] }] } });
257
+
258
+ /**
259
+ * Cartesian plotting frame shared between a chart type (Bar, Line, Area…)
260
+ * and its axis / grid primitives.
261
+ *
262
+ * The owning chart component provides an instance and publishes its scales;
263
+ * descendants read them via signals and re-render when dimensions or data
264
+ * change.
265
+ */
266
+ class CartesianContext {
267
+ /** Inner width (outer width − margin.left − margin.right). */
268
+ innerWidth = signal(0, /* @ts-ignore */
269
+ ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
270
+ /** Inner height (outer height − margin.top − margin.bottom). */
271
+ innerHeight = signal(0, /* @ts-ignore */
272
+ ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
273
+ /** Margins around the plotting area. */
274
+ margin = signal({ top: 8, right: 8, bottom: 24, left: 40 }, /* @ts-ignore */
275
+ ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
276
+ /** Layout orientation (drives which axis holds the band scale). */
277
+ orientation = signal('vertical', /* @ts-ignore */
278
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
279
+ /** Band (categorical) scale. */
280
+ categoryScale = signal(null, /* @ts-ignore */
281
+ ...(ngDevMode ? [{ debugName: "categoryScale" }] : /* istanbul ignore next */ []));
282
+ /** Linear (numeric) scale. */
283
+ valueScale = signal(null, /* @ts-ignore */
284
+ ...(ngDevMode ? [{ debugName: "valueScale" }] : /* istanbul ignore next */ []));
285
+ /** Ordered category domain (e.g. x labels for vertical, y labels for horizontal). */
286
+ categories = signal([], /* @ts-ignore */
287
+ ...(ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []));
288
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CartesianContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
289
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CartesianContext });
290
+ }
291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CartesianContext, decorators: [{
292
+ type: Injectable
293
+ }] });
294
+ /** Resolve the scale that maps to the X axis for a given orientation. */
295
+ function xScale(ctx) {
296
+ return ctx.orientation() === 'vertical' ? ctx.categoryScale : ctx.valueScale;
297
+ }
298
+ /** Resolve the scale that maps to the Y axis for a given orientation. */
299
+ function yScale(ctx) {
300
+ return ctx.orientation() === 'vertical' ? ctx.valueScale : ctx.categoryScale;
301
+ }
302
+
303
+ class CategoricalViewportContext {
304
+ dataCount = signal(0, /* @ts-ignore */
305
+ ...(ngDevMode ? [{ debugName: "dataCount" }] : /* istanbul ignore next */ []));
306
+ brushRange = signal(null, /* @ts-ignore */
307
+ ...(ngDevMode ? [{ debugName: "brushRange" }] : /* istanbul ignore next */ []));
308
+ zoomRange = signal(null, /* @ts-ignore */
309
+ ...(ngDevMode ? [{ debugName: "zoomRange" }] : /* istanbul ignore next */ []));
310
+ hasZoom = computed(() => {
311
+ const range = this.zoomRange();
312
+ const count = this.dataCount();
313
+ return !!range && count > 0 && (range.startIndex > 0 || range.endIndex < count - 1);
314
+ }, /* @ts-ignore */
315
+ ...(ngDevMode ? [{ debugName: "hasZoom" }] : /* istanbul ignore next */ []));
316
+ resetZoom() {
317
+ this.brushRange.set(null);
318
+ this.zoomRange.set(null);
319
+ }
320
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CategoricalViewportContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
321
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CategoricalViewportContext });
322
+ }
323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CategoricalViewportContext, decorators: [{
324
+ type: Injectable
325
+ }] });
326
+
327
+ class ScatterViewportContext {
328
+ innerWidth = signal(0, /* @ts-ignore */
329
+ ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
330
+ innerHeight = signal(0, /* @ts-ignore */
331
+ ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
332
+ fullXDomain = signal(null, /* @ts-ignore */
333
+ ...(ngDevMode ? [{ debugName: "fullXDomain" }] : /* istanbul ignore next */ []));
334
+ fullYDomain = signal(null, /* @ts-ignore */
335
+ ...(ngDevMode ? [{ debugName: "fullYDomain" }] : /* istanbul ignore next */ []));
336
+ zoomXDomain = signal(null, /* @ts-ignore */
337
+ ...(ngDevMode ? [{ debugName: "zoomXDomain" }] : /* istanbul ignore next */ []));
338
+ zoomYDomain = signal(null, /* @ts-ignore */
339
+ ...(ngDevMode ? [{ debugName: "zoomYDomain" }] : /* istanbul ignore next */ []));
340
+ xScale = signal(null, /* @ts-ignore */
341
+ ...(ngDevMode ? [{ debugName: "xScale" }] : /* istanbul ignore next */ []));
342
+ yScale = signal(null, /* @ts-ignore */
343
+ ...(ngDevMode ? [{ debugName: "yScale" }] : /* istanbul ignore next */ []));
344
+ hasZoom = computed(() => this.zoomXDomain() !== null || this.zoomYDomain() !== null, /* @ts-ignore */
345
+ ...(ngDevMode ? [{ debugName: "hasZoom" }] : /* istanbul ignore next */ []));
346
+ resetZoom() {
347
+ this.zoomXDomain.set(null);
348
+ this.zoomYDomain.set(null);
349
+ }
350
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ScatterViewportContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
351
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ScatterViewportContext });
352
+ }
353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ScatterViewportContext, decorators: [{
354
+ type: Injectable
355
+ }] });
356
+
357
+ /** Produce evenly-spaced ticks for a band (categorical) scale. */
358
+ function bandTicks(scale) {
359
+ const half = scale.bandwidth() / 2;
360
+ return scale.domain().map((value) => ({
361
+ value,
362
+ offset: (scale(value) ?? 0) + half,
363
+ label: value,
364
+ }));
365
+ }
366
+ /**
367
+ * Produce ticks for a linear scale.
368
+ *
369
+ * @param scale The linear scale.
370
+ * @param count Approximate number of ticks (hint, d3 may return fewer/more).
371
+ * @param formatter Label formatter.
372
+ */
373
+ function linearTicks(scale, count = 5, formatter = (v) => String(v)) {
374
+ return scale.ticks(count).map((value) => ({
375
+ value,
376
+ offset: scale(value),
377
+ label: formatter(value),
378
+ }));
379
+ }
380
+
381
+ function clamp(value, min, max) {
382
+ return Math.min(max, Math.max(min, value));
383
+ }
384
+ function normalizeIndexRange(start, end, maxCount) {
385
+ if (maxCount <= 0) {
386
+ return null;
387
+ }
388
+ const lo = clamp(Math.floor(Math.min(start, end)), 0, maxCount - 1);
389
+ const hi = clamp(Math.floor(Math.max(start, end)), 0, maxCount - 1);
390
+ return { startIndex: lo, endIndex: hi };
391
+ }
392
+ function effectiveIndexRange(range, maxCount) {
393
+ if (maxCount <= 0) {
394
+ return null;
395
+ }
396
+ return range
397
+ ? normalizeIndexRange(range.startIndex, range.endIndex, maxCount)
398
+ : { startIndex: 0, endIndex: maxCount - 1 };
399
+ }
400
+ function indexRangeSize(range, maxCount) {
401
+ const effective = effectiveIndexRange(range, maxCount);
402
+ return effective ? effective.endIndex - effective.startIndex + 1 : 0;
403
+ }
404
+ function sliceByIndexRange(data, range) {
405
+ if (!range) {
406
+ return data;
407
+ }
408
+ return data.slice(range.startIndex, range.endIndex + 1);
409
+ }
410
+ function zoomIndexRange(current, maxCount, anchorIndex, factor) {
411
+ const base = effectiveIndexRange(current, maxCount);
412
+ if (!base) {
413
+ return null;
414
+ }
415
+ const currentSize = base.endIndex - base.startIndex + 1;
416
+ const nextSize = clamp(Math.round(currentSize * factor), 2, maxCount);
417
+ if (nextSize >= maxCount) {
418
+ return null;
419
+ }
420
+ const boundedAnchor = clamp(anchorIndex, base.startIndex, base.endIndex);
421
+ const ratio = currentSize <= 1 ? 0.5 : (boundedAnchor - base.startIndex) / (currentSize - 1);
422
+ let start = Math.round(boundedAnchor - ratio * (nextSize - 1));
423
+ start = clamp(start, 0, maxCount - nextSize);
424
+ return { startIndex: start, endIndex: start + nextSize - 1 };
425
+ }
426
+ function panIndexRange(current, maxCount, deltaSteps) {
427
+ const base = effectiveIndexRange(current, maxCount);
428
+ if (!base) {
429
+ return null;
430
+ }
431
+ const size = base.endIndex - base.startIndex + 1;
432
+ if (size >= maxCount) {
433
+ return null;
434
+ }
435
+ const start = clamp(base.startIndex + deltaSteps, 0, maxCount - size);
436
+ return { startIndex: start, endIndex: start + size - 1 };
437
+ }
438
+ function normalizeNumericDomain(a, b) {
439
+ if (a === b) {
440
+ return [a - 1, b + 1];
441
+ }
442
+ return a < b ? [a, b] : [b, a];
443
+ }
444
+ function zoomNumericDomain(current, full, anchor, factor) {
445
+ const currentWidth = current[1] - current[0];
446
+ const fullWidth = full[1] - full[0];
447
+ const nextWidth = clamp(currentWidth * factor, fullWidth / 50, fullWidth);
448
+ if (nextWidth >= fullWidth) {
449
+ return full;
450
+ }
451
+ const ratio = currentWidth === 0 ? 0.5 : (anchor - current[0]) / currentWidth;
452
+ let start = anchor - ratio * nextWidth;
453
+ start = clamp(start, full[0], full[1] - nextWidth);
454
+ return [start, start + nextWidth];
455
+ }
456
+ function panNumericDomain(current, full, delta) {
457
+ const width = current[1] - current[0];
458
+ const start = clamp(current[0] + delta, full[0], full[1] - width);
459
+ return [start, start + width];
460
+ }
461
+
462
+ /**
463
+ * Given a pointer event's local (x, y) relative to the chart's inner group,
464
+ * resolve the nearest category index along the categorical axis.
465
+ *
466
+ * @returns index into `ctx.categories()` (or −1 if no scale / no data).
467
+ */
468
+ function nearestCategoryIndex(ctx, localX, localY) {
469
+ const scale = ctx.categoryScale();
470
+ const categories = ctx.categories();
471
+ if (!scale || categories.length === 0)
472
+ return -1;
473
+ const isVertical = ctx.orientation() === 'vertical';
474
+ const pointerAlong = isVertical ? localX : localY;
475
+ const bandwidth = scale.bandwidth();
476
+ // scale.step() is only defined for band scales; fall back to bandwidth.
477
+ const step = scale.step?.() ?? bandwidth;
478
+ let bestIndex = -1;
479
+ let bestDelta = Infinity;
480
+ for (let i = 0; i < categories.length; i++) {
481
+ const base = scale(categories[i]) ?? 0;
482
+ const center = base + bandwidth / 2;
483
+ const delta = Math.abs(pointerAlong - center);
484
+ if (delta < bestDelta) {
485
+ bestDelta = delta;
486
+ bestIndex = i;
487
+ }
488
+ }
489
+ // Ignore clicks far outside any band (> 1 step away).
490
+ if (bestDelta > step) {
491
+ return -1;
492
+ }
493
+ return bestIndex;
494
+ }
495
+ /** Resolve the client-space center point of a focused or clicked SVG/HTML element. */
496
+ function elementClientCenter(target) {
497
+ const el = target;
498
+ if (!el || typeof el.getBoundingClientRect !== 'function') {
499
+ return null;
500
+ }
501
+ const rect = el.getBoundingClientRect();
502
+ return {
503
+ clientX: rect.left + rect.width / 2,
504
+ clientY: rect.top + rect.height / 2,
505
+ };
506
+ }
507
+
508
+ function curveFor(curve) {
509
+ switch (curve) {
510
+ case 'monotone':
511
+ return curveMonotoneX;
512
+ case 'step':
513
+ return curveStep;
514
+ case 'linear':
515
+ default:
516
+ return curveLinear;
517
+ }
518
+ }
519
+ function readNumber(datum, key) {
520
+ const raw = datum[key];
521
+ if (typeof raw === 'number' && Number.isFinite(raw))
522
+ return raw;
523
+ if (typeof raw === 'string') {
524
+ const n = Number(raw);
525
+ return Number.isFinite(n) ? n : 0;
526
+ }
527
+ return 0;
528
+ }
529
+ /** Build category + value scales for point-based charts (line / area). */
530
+ function buildCartesianScales(input) {
531
+ const { data, xKey, seriesKeys, orientation, innerWidth, innerHeight } = input;
532
+ const isVertical = orientation === 'vertical';
533
+ const categories = data.map((d) => String(d[xKey] ?? ''));
534
+ const categoryScale = scalePoint()
535
+ .domain(categories)
536
+ .range(isVertical ? [0, innerWidth] : [0, innerHeight])
537
+ .padding(0);
538
+ const maxValue = max(data, (d) => max(seriesKeys, (k) => readNumber(d, k)) ?? 0) ?? 0;
539
+ const valueScale = scaleLinear()
540
+ .domain([0, maxValue === 0 ? 1 : maxValue])
541
+ .nice()
542
+ .range(isVertical ? [innerHeight, 0] : [0, innerWidth]);
543
+ return { categories, categoryScale, valueScale };
544
+ }
545
+ /** Compute line-chart geometry. */
546
+ function computeLineLayout(input) {
547
+ const { data, seriesKeys, orientation, curve } = input;
548
+ const { categories, categoryScale, valueScale } = buildCartesianScales(input);
549
+ const isVertical = orientation === 'vertical';
550
+ const allPoints = [];
551
+ const series = [];
552
+ for (const seriesKey of seriesKeys) {
553
+ const points = data.map((d, datumIndex) => {
554
+ const value = readNumber(d, seriesKey);
555
+ const category = categories[datumIndex];
556
+ const c = categoryScale(category) ?? 0;
557
+ const v = valueScale(value);
558
+ return {
559
+ seriesKey,
560
+ datumIndex,
561
+ category,
562
+ value,
563
+ x: isVertical ? c : v,
564
+ y: isVertical ? v : c,
565
+ };
566
+ });
567
+ allPoints.push(...points);
568
+ const generator = line()
569
+ .x((p) => p.x)
570
+ .y((p) => p.y)
571
+ .curve(curveFor(curve));
572
+ series.push({
573
+ seriesKey,
574
+ color: seriesColorVar(seriesKey),
575
+ linePath: generator(points) ?? '',
576
+ points,
577
+ });
578
+ }
579
+ return {
580
+ series,
581
+ points: allPoints,
582
+ categoryScale,
583
+ valueScale,
584
+ categories,
585
+ };
586
+ }
587
+ /** Compute area-chart geometry (single or stacked). */
588
+ function computeAreaLayout(input) {
589
+ if (input.stacked && input.seriesKeys.length > 0) {
590
+ return computeStackedArea(input);
591
+ }
592
+ return computeSingleArea(input);
593
+ }
594
+ function computeSingleArea(input) {
595
+ const { data, seriesKeys, orientation, curve } = input;
596
+ const { categories, categoryScale, valueScale } = buildCartesianScales(input);
597
+ const isVertical = orientation === 'vertical';
598
+ const baseline = isVertical ? valueScale(0) : valueScale(0);
599
+ const allPoints = [];
600
+ const series = [];
601
+ for (const seriesKey of seriesKeys) {
602
+ const points = data.map((d, datumIndex) => {
603
+ const value = readNumber(d, seriesKey);
604
+ const category = categories[datumIndex];
605
+ const c = categoryScale(category) ?? 0;
606
+ const v = valueScale(value);
607
+ return {
608
+ seriesKey,
609
+ datumIndex,
610
+ category,
611
+ value,
612
+ x: isVertical ? c : v,
613
+ y: isVertical ? v : c,
614
+ };
615
+ });
616
+ allPoints.push(...points);
617
+ const lineGen = line()
618
+ .x((p) => p.x)
619
+ .y((p) => p.y)
620
+ .curve(curveFor(curve));
621
+ const areaGen = isVertical
622
+ ? area()
623
+ .x((p) => p.x)
624
+ .y0(baseline)
625
+ .y1((p) => p.y)
626
+ .curve(curveFor(curve))
627
+ : area()
628
+ .y((p) => p.y)
629
+ .x0(baseline)
630
+ .x1((p) => p.x)
631
+ .curve(curveFor(curve));
632
+ series.push({
633
+ seriesKey,
634
+ color: seriesColorVar(seriesKey),
635
+ linePath: lineGen(points) ?? '',
636
+ areaPath: areaGen(points) ?? '',
637
+ points,
638
+ });
639
+ }
640
+ return {
641
+ series,
642
+ points: allPoints,
643
+ categoryScale,
644
+ valueScale,
645
+ categories,
646
+ stacked: false,
647
+ };
648
+ }
649
+ function computeStackedArea(input) {
650
+ const { data, seriesKeys, orientation, innerWidth, innerHeight, xKey, curve, expanded } = input;
651
+ const isVertical = orientation === 'vertical';
652
+ const categories = data.map((d) => String(d[xKey] ?? ''));
653
+ const categoryScale = scalePoint()
654
+ .domain(categories)
655
+ .range(isVertical ? [0, innerWidth] : [0, innerHeight])
656
+ .padding(0);
657
+ const normalized = data.map((d) => {
658
+ const out = {};
659
+ for (const k of seriesKeys)
660
+ out[k] = readNumber(d, k);
661
+ return out;
662
+ });
663
+ const stackGenerator = stack().keys(seriesKeys);
664
+ if (expanded) {
665
+ stackGenerator.offset(stackOffsetExpand);
666
+ }
667
+ const stackSeries = stackGenerator(normalized);
668
+ const maxTotal = expanded ? 1 : (max(stackSeries[stackSeries.length - 1] ?? [], (p) => p[1]) ?? 0);
669
+ const valueScale = scaleLinear()
670
+ .domain([0, maxTotal === 0 ? 1 : maxTotal])
671
+ .range(isVertical ? [innerHeight, 0] : [0, innerWidth]);
672
+ if (!expanded) {
673
+ valueScale.nice();
674
+ }
675
+ const allPoints = [];
676
+ const series = stackSeries.map((layer) => {
677
+ const seriesKey = layer.key;
678
+ const points = layer.map((p, datumIndex) => {
679
+ const category = categories[datumIndex];
680
+ const c = categoryScale(category) ?? 0;
681
+ const upper = valueScale(p[1]);
682
+ return {
683
+ seriesKey,
684
+ datumIndex,
685
+ category,
686
+ value: p[1] - p[0],
687
+ x: isVertical ? c : upper,
688
+ y: isVertical ? upper : c,
689
+ };
690
+ });
691
+ allPoints.push(...points);
692
+ const lineGen = line()
693
+ .x(([x]) => x)
694
+ .y(([, y]) => y)
695
+ .curve(curveFor(curve));
696
+ const areaGen = isVertical
697
+ ? area()
698
+ .x(([x]) => x)
699
+ .y0(([, , y0]) => y0)
700
+ .y1(([, y1]) => y1)
701
+ .curve(curveFor(curve))
702
+ : area()
703
+ .y(([x]) => x)
704
+ .x0(([, , x0]) => x0)
705
+ .x1(([, x1]) => x1)
706
+ .curve(curveFor(curve));
707
+ const tuples = layer.map((p, i) => {
708
+ const c = categoryScale(categories[i]) ?? 0;
709
+ return [c, valueScale(p[1]), valueScale(p[0])];
710
+ });
711
+ return {
712
+ seriesKey,
713
+ color: seriesColorVar(seriesKey),
714
+ linePath: lineGen(tuples) ?? '',
715
+ areaPath: areaGen(tuples) ?? '',
716
+ points,
717
+ };
718
+ });
719
+ return {
720
+ series,
721
+ points: allPoints,
722
+ categoryScale,
723
+ valueScale,
724
+ categories,
725
+ stacked: true,
726
+ };
727
+ }
728
+
729
+ /**
730
+ * Publish a `scalePoint` from line/area layouts to the `CartesianContext`,
731
+ * which expects a `scaleBand`. Line/area points sit on the range edges (the
732
+ * point scale has zero padding, so the first point touches the y-axis and the
733
+ * last touches the right edge). We extend the band range by half a step on
734
+ * each side so band centers — used to position axis ticks — line up exactly
735
+ * with those edge-touching points.
736
+ *
737
+ * This keeps axes/grid primitives agnostic to whether the underlying chart
738
+ * uses `scaleBand` (bar) or `scalePoint` (line/area).
739
+ */
740
+ function pointToBandAdapter(pointScale, range) {
741
+ const half = pointScale.step() / 2;
742
+ const extended = [range[0] - half, range[1] + half];
743
+ return scaleBand().domain(pointScale.domain()).range(extended).padding(0);
744
+ }
745
+ /** Recreate a linear scale with the same domain/range (handy for effects). */
746
+ function cloneLinear(scale) {
747
+ return scaleLinear().domain(scale.domain()).range(scale.range());
748
+ }
749
+ function provideCartesianFromLineLayout(ctx, layout, orientation, innerWidth, innerHeight) {
750
+ const range = orientation === 'vertical' ? [0, innerWidth] : [0, innerHeight];
751
+ ctx.orientation.set(orientation);
752
+ ctx.innerWidth.set(innerWidth);
753
+ ctx.innerHeight.set(innerHeight);
754
+ ctx.categoryScale.set(pointToBandAdapter(layout.categoryScale, range));
755
+ ctx.valueScale.set(cloneLinear(layout.valueScale));
756
+ ctx.categories.set(layout.categories);
757
+ }
758
+
759
+ /**
760
+ * Generated bundle index. Do not edit.
761
+ */
762
+
763
+ export { CHART_DATA_ATTRIBUTE, CHART_THEMES, CartesianContext, CategoricalViewportContext, ChartContainer, ChartContext, ChartStyle, ScatterViewportContext, bandTicks, buildCartesianScales, buildChartCss, cloneLinear, computeAreaLayout, computeLineLayout, effectiveIndexRange, elementClientCenter, indexRangeSize, linearTicks, nearestCategoryIndex, normalizeIndexRange, normalizeNumericDomain, panIndexRange, panNumericDomain, pointToBandAdapter, provideCartesianFromLineLayout, seriesColorVar, sliceByIndexRange, xScale, yScale, zoomIndexRange, zoomNumericDomain };
764
+ //# sourceMappingURL=ojiepermana-angular-chart-core.mjs.map