@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.
- package/README.md +249 -0
- package/fesm2022/ojiepermana-angular-chart-area.mjs +266 -0
- package/fesm2022/ojiepermana-angular-chart-area.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-bar.mjs +674 -0
- package/fesm2022/ojiepermana-angular-chart-bar.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-core.mjs +764 -0
- package/fesm2022/ojiepermana-angular-chart-core.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-line.mjs +281 -0
- package/fesm2022/ojiepermana-angular-chart-line.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-pie.mjs +248 -0
- package/fesm2022/ojiepermana-angular-chart-pie.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-primitives.mjs +1186 -0
- package/fesm2022/ojiepermana-angular-chart-primitives.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-radar.mjs +329 -0
- package/fesm2022/ojiepermana-angular-chart-radar.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-radial.mjs +255 -0
- package/fesm2022/ojiepermana-angular-chart-radial.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart-scatter.mjs +253 -0
- package/fesm2022/ojiepermana-angular-chart-scatter.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-chart.mjs +20 -0
- package/fesm2022/ojiepermana-angular-chart.mjs.map +1 -0
- package/package.json +76 -0
- package/types/ojiepermana-angular-chart-area.d.ts +58 -0
- package/types/ojiepermana-angular-chart-bar.d.ts +171 -0
- package/types/ojiepermana-angular-chart-core.d.ts +369 -0
- package/types/ojiepermana-angular-chart-line.d.ts +57 -0
- package/types/ojiepermana-angular-chart-pie.d.ts +93 -0
- package/types/ojiepermana-angular-chart-primitives.d.ts +265 -0
- package/types/ojiepermana-angular-chart-radar.d.ts +89 -0
- package/types/ojiepermana-angular-chart-radial.d.ts +86 -0
- package/types/ojiepermana-angular-chart-scatter.d.ts +95 -0
- 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
|