@sinequa/atomic-angular 1.17.0 → 2.1.0

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 (80) hide show
  1. package/README.md +249 -244
  2. package/components/navbar-tabs/i18n/de.json +4 -2
  3. package/components/navbar-tabs/i18n/en.json +4 -2
  4. package/components/navbar-tabs/i18n/fr.json +5 -3
  5. package/components/preview/i18n/de.json +12 -0
  6. package/components/preview/i18n/en.json +12 -0
  7. package/components/preview/i18n/fr.json +12 -0
  8. package/components/sort-selector/i18n/de.json +1 -0
  9. package/components/sort-selector/i18n/en.json +1 -0
  10. package/components/sort-selector/i18n/fr.json +1 -0
  11. package/components/sponsored-results/i18n/de.json +3 -0
  12. package/components/sponsored-results/i18n/en.json +3 -0
  13. package/components/sponsored-results/i18n/fr.json +3 -0
  14. package/document-workspaces/i18n/de.json +13 -1
  15. package/document-workspaces/i18n/en.json +13 -1
  16. package/document-workspaces/i18n/fr.json +13 -1
  17. package/document-workspaces/i18n/ja.json +13 -1
  18. package/features/{drawer → advanced-search}/i18n/de.json +12 -1
  19. package/features/{drawer → advanced-search}/i18n/en.json +12 -1
  20. package/features/{drawer → advanced-search}/i18n/fr.json +13 -2
  21. package/features/alerts/i18n/de.json +7 -1
  22. package/features/alerts/i18n/en.json +7 -1
  23. package/features/alerts/i18n/fr.json +7 -1
  24. package/features/auth/i18n/de.json +6 -0
  25. package/features/auth/i18n/en.json +6 -0
  26. package/features/auth/i18n/fr.json +6 -1
  27. package/features/collections/i18n/de.json +4 -1
  28. package/features/collections/i18n/en.json +4 -1
  29. package/features/collections/i18n/fr.json +4 -1
  30. package/features/dialogs/i18n/de.json +11 -1
  31. package/features/dialogs/i18n/en.json +11 -1
  32. package/features/dialogs/i18n/fr.json +11 -1
  33. package/features/export/i18n/de.json +5 -1
  34. package/features/export/i18n/en.json +5 -1
  35. package/features/export/i18n/fr.json +5 -1
  36. package/features/feedback/i18n/de.json +2 -1
  37. package/features/feedback/i18n/en.json +2 -1
  38. package/features/feedback/i18n/fr.json +2 -1
  39. package/features/filters/i18n/de.json +10 -3
  40. package/features/filters/i18n/en.json +10 -3
  41. package/features/filters/i18n/fr.json +11 -4
  42. package/features/labels/i18n/de.json +5 -1
  43. package/features/labels/i18n/en.json +5 -1
  44. package/features/labels/i18n/fr.json +5 -1
  45. package/features/list-panel/i18n/de.json +4 -0
  46. package/features/list-panel/i18n/en.json +4 -0
  47. package/features/list-panel/i18n/fr.json +4 -0
  48. package/features/searches/i18n/de.json +2 -1
  49. package/features/searches/i18n/en.json +2 -1
  50. package/features/searches/i18n/fr.json +3 -2
  51. package/features/sheet/i18n/de.json +3 -0
  52. package/features/sheet/i18n/en.json +3 -0
  53. package/features/sheet/i18n/fr.json +3 -0
  54. package/features/user-profile/i18n/de.json +22 -1
  55. package/features/user-profile/i18n/en.json +22 -1
  56. package/features/user-profile/i18n/fr.json +22 -1
  57. package/fesm2022/sinequa-atomic-angular-charts-heatmap.mjs +294 -0
  58. package/fesm2022/sinequa-atomic-angular-charts-heatmap.mjs.map +1 -0
  59. package/fesm2022/sinequa-atomic-angular-charts.mjs +969 -0
  60. package/fesm2022/sinequa-atomic-angular-charts.mjs.map +1 -0
  61. package/fesm2022/sinequa-atomic-angular-document-workspaces.mjs +12312 -7872
  62. package/fesm2022/sinequa-atomic-angular-document-workspaces.mjs.map +1 -1
  63. package/fesm2022/sinequa-atomic-angular-workspace-elements.mjs +88 -152
  64. package/fesm2022/sinequa-atomic-angular-workspace-elements.mjs.map +1 -1
  65. package/fesm2022/sinequa-atomic-angular.mjs +30820 -17270
  66. package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
  67. package/package.json +64 -19
  68. package/providers/session-expiry/i18n/de.json +7 -0
  69. package/providers/session-expiry/i18n/en.json +7 -0
  70. package/providers/session-expiry/i18n/fr.json +7 -0
  71. package/styles/calendar.css +88 -0
  72. package/types/sinequa-atomic-angular-charts-heatmap.d.ts +154 -0
  73. package/types/sinequa-atomic-angular-charts.d.ts +462 -0
  74. package/types/sinequa-atomic-angular-document-workspaces.d.ts +3001 -0
  75. package/types/sinequa-atomic-angular-workspace-elements.d.ts +49 -0
  76. package/types/sinequa-atomic-angular.d.ts +19972 -0
  77. package/workspace-elements/styles/lexiq-workspaces.css +7310 -4010
  78. package/document-workspaces/index.d.ts +0 -2159
  79. package/index.d.ts +0 -16119
  80. package/workspace-elements/index.d.ts +0 -37
@@ -0,0 +1,969 @@
1
+ import * as i0 from "@angular/core";
2
+ import { Component, DestroyRef, Service, afterNextRender, computed, effect, inject, input, output, signal, viewChild } from "@angular/core";
3
+ import { Chart, registerables } from "chart.js";
4
+ import { cn } from "cn";
5
+ import { galactikPrimitive, galactikSemantic, paletteColor, seriesPalette, timelinePalette, toPercentages, toSeries, withAlpha } from "@sinequa/atomic-angular";
6
+ let registered = false;
7
+ function setupCharts() {
8
+ if (registered) return;
9
+ Chart.register(...registerables);
10
+ registered = true;
11
+ }
12
+ function applyChartTheme() {
13
+ Chart.defaults.color = galactikSemantic("font-neutral-muted");
14
+ Chart.defaults.borderColor = galactikSemantic("stroke-neutral-muted");
15
+ }
16
+ var ChartThemeService = class ChartThemeService {
17
+ dark = signal(hasDarkClass(), ...ngDevMode ? [{ debugName: "dark" }] : /* istanbul ignore next */ []);
18
+ isDark = this.dark.asReadonly();
19
+ constructor() {
20
+ const observer = new MutationObserver(() => this.dark.set(hasDarkClass()));
21
+ observer.observe(document.documentElement, {
22
+ attributes: true,
23
+ attributeFilter: ["class"]
24
+ });
25
+ inject(DestroyRef).onDestroy(() => observer.disconnect());
26
+ }
27
+ static ɵfac = i0.ɵɵngDeclareFactory({
28
+ minVersion: "12.0.0",
29
+ version: "22.2.1",
30
+ ngImport: i0,
31
+ type: ChartThemeService,
32
+ deps: [],
33
+ target: i0.ɵɵFactoryTarget.Service
34
+ });
35
+ static ɵprov = i0.ɵɵngDeclareService({
36
+ minVersion: "22.0.0",
37
+ version: "22.2.1",
38
+ ngImport: i0,
39
+ type: ChartThemeService
40
+ });
41
+ };
42
+ i0.ɵɵngDeclareClassMetadata({
43
+ minVersion: "12.0.0",
44
+ version: "22.2.1",
45
+ ngImport: i0,
46
+ type: ChartThemeService,
47
+ decorators: [{ type: Service }],
48
+ ctorParameters: () => []
49
+ });
50
+ function hasDarkClass() {
51
+ return document.documentElement.classList.contains("dark");
52
+ }
53
+ var ChartCanvasComponent = class ChartCanvasComponent {
54
+ cn = cn;
55
+ class = input("", ...ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []);
56
+ config = input.required(...ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []);
57
+ plugins = input([], ...ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []);
58
+ canvas = viewChild.required("canvas", ...ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []);
59
+ destroyRef = inject(DestroyRef);
60
+ theme = inject(ChartThemeService);
61
+ chart;
62
+ currentType;
63
+ currentDark;
64
+ constructor() {
65
+ setupCharts();
66
+ afterNextRender(() => {
67
+ const dark = this.theme.isDark();
68
+ applyChartTheme();
69
+ this.chart = new Chart(this.canvas().nativeElement, this.withDefaults(this.config(), this.plugins()));
70
+ this.currentType = this.config().type;
71
+ this.currentDark = dark;
72
+ });
73
+ effect(() => {
74
+ const config = this.config();
75
+ const dark = this.theme.isDark();
76
+ if (!this.chart) return;
77
+ if (this.currentType !== config.type || this.currentDark !== dark) {
78
+ applyChartTheme();
79
+ this.chart.destroy();
80
+ this.chart = new Chart(this.canvas().nativeElement, this.withDefaults(config, this.plugins()));
81
+ this.currentType = config.type;
82
+ this.currentDark = dark;
83
+ return;
84
+ }
85
+ this.chart.data = config.data;
86
+ this.chart.options = {
87
+ ...this.chart.options,
88
+ ...config.options ?? {}
89
+ };
90
+ this.chart.update();
91
+ });
92
+ this.destroyRef.onDestroy(() => this.chart?.destroy());
93
+ }
94
+ withDefaults(config, plugins) {
95
+ return {
96
+ ...config,
97
+ plugins: [...config.plugins ?? [], ...plugins],
98
+ options: {
99
+ responsive: true,
100
+ maintainAspectRatio: false,
101
+ ...config.options ?? {}
102
+ }
103
+ };
104
+ }
105
+ static ɵfac = i0.ɵɵngDeclareFactory({
106
+ minVersion: "12.0.0",
107
+ version: "22.2.1",
108
+ ngImport: i0,
109
+ type: ChartCanvasComponent,
110
+ deps: [],
111
+ target: i0.ɵɵFactoryTarget.Component
112
+ });
113
+ static ɵcmp = i0.ɵɵngDeclareComponent({
114
+ minVersion: "17.2.0",
115
+ version: "22.2.1",
116
+ type: ChartCanvasComponent,
117
+ isStandalone: true,
118
+ selector: "ChartCanvas, chart-canvas, chartcanvas",
119
+ inputs: {
120
+ class: {
121
+ classPropertyName: "class",
122
+ publicName: "class",
123
+ isSignal: true,
124
+ isRequired: false,
125
+ transformFunction: null
126
+ },
127
+ config: {
128
+ classPropertyName: "config",
129
+ publicName: "config",
130
+ isSignal: true,
131
+ isRequired: true,
132
+ transformFunction: null
133
+ },
134
+ plugins: {
135
+ classPropertyName: "plugins",
136
+ publicName: "plugins",
137
+ isSignal: true,
138
+ isRequired: false,
139
+ transformFunction: null
140
+ }
141
+ },
142
+ host: { properties: { "class": "cn(\"block h-full w-full\", class())" } },
143
+ viewQueries: [{
144
+ propertyName: "canvas",
145
+ first: true,
146
+ predicate: ["canvas"],
147
+ descendants: true,
148
+ isSignal: true
149
+ }],
150
+ ngImport: i0,
151
+ template: `<canvas #canvas class="block size-full"></canvas>`,
152
+ isInline: true
153
+ });
154
+ };
155
+ i0.ɵɵngDeclareClassMetadata({
156
+ minVersion: "12.0.0",
157
+ version: "22.2.1",
158
+ ngImport: i0,
159
+ type: ChartCanvasComponent,
160
+ decorators: [{
161
+ type: Component,
162
+ args: [{
163
+ selector: "ChartCanvas, chart-canvas, chartcanvas",
164
+ template: `<canvas #canvas class="block size-full"></canvas>`,
165
+ host: { "[class]": "cn(\"block h-full w-full\", class())" }
166
+ }]
167
+ }],
168
+ ctorParameters: () => [],
169
+ propDecorators: {
170
+ class: [{
171
+ type: i0.Input,
172
+ args: [{
173
+ isSignal: true,
174
+ alias: "class",
175
+ required: false
176
+ }]
177
+ }],
178
+ config: [{
179
+ type: i0.Input,
180
+ args: [{
181
+ isSignal: true,
182
+ alias: "config",
183
+ required: true
184
+ }]
185
+ }],
186
+ plugins: [{
187
+ type: i0.Input,
188
+ args: [{
189
+ isSignal: true,
190
+ alias: "plugins",
191
+ required: false
192
+ }]
193
+ }],
194
+ canvas: [{
195
+ type: i0.ViewChild,
196
+ args: ["canvas", { isSignal: true }]
197
+ }]
198
+ }
199
+ });
200
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
201
+ const MIN_DRAG_PX = 4;
202
+ function createTimelineBrushPlugin(onSelect) {
203
+ let startX = null;
204
+ let currentX = null;
205
+ return {
206
+ id: "timeline-brush",
207
+ afterEvent(chart, args) {
208
+ const event = args.event;
209
+ const area = chart.chartArea;
210
+ const x = event.x ?? 0;
211
+ switch (event.type) {
212
+ case "mousedown":
213
+ if (x >= area.left && x <= area.right) {
214
+ startX = x;
215
+ currentX = x;
216
+ }
217
+ break;
218
+ case "mousemove":
219
+ if (startX !== null) {
220
+ currentX = clamp(x, area.left, area.right);
221
+ args.changed = true;
222
+ }
223
+ break;
224
+ case "mouseup":
225
+ case "mouseout":
226
+ if (startX !== null && currentX !== null && Math.abs(currentX - startX) > MIN_DRAG_PX) {
227
+ const xScale = chart.scales["x"];
228
+ const labels = chart.data.labels ?? [];
229
+ if (xScale && labels.length > 0) {
230
+ const i1 = clamp(Math.round(xScale.getValueForPixel(Math.min(startX, currentX)) ?? 0), 0, labels.length - 1);
231
+ const i2 = clamp(Math.round(xScale.getValueForPixel(Math.max(startX, currentX)) ?? 0), 0, labels.length - 1);
232
+ const from = labels[i1];
233
+ const to = labels[i2];
234
+ if (from != null && to != null) onSelect(String(from), String(to));
235
+ }
236
+ }
237
+ startX = null;
238
+ currentX = null;
239
+ args.changed = true;
240
+ }
241
+ },
242
+ afterDraw(chart) {
243
+ if (startX === null || currentX === null) return;
244
+ const { ctx, chartArea: area } = chart;
245
+ const left = Math.min(startX, currentX);
246
+ const right = Math.max(startX, currentX);
247
+ ctx.save();
248
+ ctx.fillStyle = withAlpha(galactikPrimitive("blue-500"), .15);
249
+ ctx.strokeStyle = withAlpha(galactikPrimitive("blue-500"), .5);
250
+ ctx.fillRect(left, area.top, right - left, area.bottom - area.top);
251
+ ctx.strokeRect(left, area.top, right - left, area.bottom - area.top);
252
+ ctx.restore();
253
+ }
254
+ };
255
+ }
256
+ function createAxisClickPlugin(onSelect, axes = ["x", "y"]) {
257
+ let hovering = false;
258
+ const setCursor = (chart, hover) => {
259
+ if (hover === hovering) return;
260
+ hovering = hover;
261
+ chart.canvas.style.cursor = hover ? "pointer" : "";
262
+ };
263
+ return {
264
+ id: "axis-click",
265
+ afterEvent(chart, args) {
266
+ const event = args.event;
267
+ const x = event.x ?? 0;
268
+ const y = event.y ?? 0;
269
+ switch (event.type) {
270
+ case "click": {
271
+ const hit = hitTest(chart, axes, x, y);
272
+ if (hit) onSelect(hit);
273
+ break;
274
+ }
275
+ case "mousemove":
276
+ setCursor(chart, hitTest(chart, axes, x, y) !== null);
277
+ break;
278
+ case "mouseout": setCursor(chart, false);
279
+ }
280
+ },
281
+ afterDestroy(chart) {
282
+ if (hovering) {
283
+ hovering = false;
284
+ chart.canvas.style.cursor = "";
285
+ }
286
+ }
287
+ };
288
+ }
289
+ function hitTest(chart, axes, x, y) {
290
+ for (const axis of axes) {
291
+ const scale = chart.scales[axis];
292
+ if (!scale || !isInsideScaleBox(scale, x, y)) continue;
293
+ const tick = nearestTick(scale, scale.isHorizontal() ? x : y);
294
+ if (tick) return {
295
+ axis,
296
+ index: tick.index,
297
+ label: tick.label
298
+ };
299
+ }
300
+ return null;
301
+ }
302
+ function isInsideScaleBox(scale, x, y) {
303
+ return x >= scale.left && x <= scale.right && y >= scale.top && y <= scale.bottom;
304
+ }
305
+ function nearestTick(scale, position) {
306
+ const ticks = scale.ticks;
307
+ if (ticks.length === 0) return null;
308
+ let best = 0;
309
+ let bestDistance = Number.POSITIVE_INFINITY;
310
+ for (let i = 0; i < ticks.length; i++) {
311
+ const distance = Math.abs(scale.getPixelForTick(i) - position);
312
+ if (distance < bestDistance) {
313
+ bestDistance = distance;
314
+ best = i;
315
+ }
316
+ }
317
+ const span = scale.isHorizontal() ? scale.width : scale.height;
318
+ if (bestDistance > span / ticks.length / 2 + 2) return null;
319
+ const value = ticks[best]?.value ?? best;
320
+ return {
321
+ index: value,
322
+ label: scale.getLabelForValue(value)
323
+ };
324
+ }
325
+ const MASK_UNIT = {
326
+ "YYYY-MM-DD-hh-mm": "minute",
327
+ "YYYY-MM-DD-hh": "hour",
328
+ "YYYY-MM-DD": "day",
329
+ "YYYY-WW": "week",
330
+ "YYYY-MM": "month",
331
+ YYYY: "year"
332
+ };
333
+ function buildTimeline(inputs, options = {}) {
334
+ const parsed = inputs.map((input) => ({
335
+ label: input.label,
336
+ primary: input.primary ?? false,
337
+ points: toPointMap(input.aggregation, input.weightField)
338
+ }));
339
+ const axis = buildAxis(parsed, options);
340
+ const series = parsed.map((entry) => {
341
+ const range = timestampRange(entry.points);
342
+ return {
343
+ label: entry.label,
344
+ primary: entry.primary,
345
+ values: axis.timestamps.map((ts) => {
346
+ const value = entry.points.get(ts);
347
+ if (value !== void 0) return value;
348
+ if (range && ts >= range.first && ts <= range.last) return 0;
349
+ return null;
350
+ })
351
+ };
352
+ });
353
+ return {
354
+ labels: axis.labels,
355
+ series
356
+ };
357
+ }
358
+ function buildAxis(series, options) {
359
+ const extentSources = series.some((s) => s.primary) ? series.filter((s) => s.primary) : series;
360
+ const tsToKey = /* @__PURE__ */ new Map();
361
+ for (const entry of extentSources) for (const ts of entry.points.keys()) if (!tsToKey.has(ts)) tsToKey.set(ts, formatFallback(ts));
362
+ for (const entry of series) for (const ts of entry.points.keys()) if (!tsToKey.has(ts) && extentSources === series) tsToKey.set(ts, formatFallback(ts));
363
+ const sorted = [...tsToKey.keys()].sort((a, b) => a - b);
364
+ const unit = options.mask ? MASK_UNIT[options.mask] : void 0;
365
+ if (!unit || sorted.length < 2) return {
366
+ labels: sorted.map((ts) => formatKey(ts, options.mask)),
367
+ timestamps: sorted
368
+ };
369
+ const maxBuckets = options.maxBuckets ?? 5e3;
370
+ const last = sorted[sorted.length - 1] ?? 0;
371
+ const labels = [];
372
+ const timestamps = [];
373
+ let cursor = new Date(sorted[0] ?? 0);
374
+ for (let i = 0; i < maxBuckets && cursor.getTime() <= last; i++) {
375
+ const ts = cursor.getTime();
376
+ timestamps.push(ts);
377
+ labels.push(formatKey(ts, options.mask));
378
+ cursor = stepDate(cursor, unit);
379
+ }
380
+ return {
381
+ labels,
382
+ timestamps
383
+ };
384
+ }
385
+ function timestampRange(points) {
386
+ let first;
387
+ let last;
388
+ for (const ts of points.keys()) {
389
+ if (first === void 0 || ts < first) first = ts;
390
+ if (last === void 0 || ts > last) last = ts;
391
+ }
392
+ return first === void 0 || last === void 0 ? void 0 : {
393
+ first,
394
+ last
395
+ };
396
+ }
397
+ function toPointMap(aggregation, weightField) {
398
+ const points = /* @__PURE__ */ new Map();
399
+ if (!aggregation) return points;
400
+ for (const item of aggregation.items) {
401
+ const date = parseBucketDate(String(item.value ?? ""));
402
+ if (!date) continue;
403
+ points.set(date.getTime(), readValue(item, weightField));
404
+ }
405
+ return points;
406
+ }
407
+ function readValue(item, weightField) {
408
+ if (weightField) {
409
+ const raw = item.operatorResults?.[weightField];
410
+ if (raw instanceof Date) return raw.getTime();
411
+ return Number(raw ?? 0);
412
+ }
413
+ return Number(item.count ?? 0);
414
+ }
415
+ function parseBucketDate(key) {
416
+ const s = key.trim();
417
+ if (s.length === 0) return null;
418
+ if (/^\d{4}$/.test(s)) return new Date(Number(s), 0, 1);
419
+ const ym = /^(\d{4})-(\d{2})$/.exec(s);
420
+ if (ym) return new Date(Number(ym[1]), Number(ym[2]) - 1, 1);
421
+ const ymd = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
422
+ if (ymd) return new Date(Number(ymd[1]), Number(ymd[2]) - 1, Number(ymd[3]));
423
+ const parsed = new Date(s.replace(" ", "T"));
424
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
425
+ }
426
+ function stepDate(date, unit) {
427
+ const d = new Date(date.getTime());
428
+ switch (unit) {
429
+ case "minute":
430
+ d.setMinutes(d.getMinutes() + 1);
431
+ break;
432
+ case "hour":
433
+ d.setHours(d.getHours() + 1);
434
+ break;
435
+ case "day":
436
+ d.setDate(d.getDate() + 1);
437
+ break;
438
+ case "week":
439
+ d.setDate(d.getDate() + 7);
440
+ break;
441
+ case "month":
442
+ d.setMonth(d.getMonth() + 1);
443
+ break;
444
+ case "year": d.setFullYear(d.getFullYear() + 1);
445
+ }
446
+ return d;
447
+ }
448
+ const pad = (n) => String(n).padStart(2, "0");
449
+ function formatKey(timestamp, mask) {
450
+ const d = new Date(timestamp);
451
+ switch (mask) {
452
+ case "YYYY": return String(d.getFullYear());
453
+ case "YYYY-MM": return `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;
454
+ case "YYYY-WW":
455
+ case "YYYY-MM-DD": return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
456
+ case "YYYY-MM-DD-hh": return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:00`;
457
+ case "YYYY-MM-DD-hh-mm": return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
458
+ default: return formatFallback(timestamp);
459
+ }
460
+ }
461
+ function formatFallback(timestamp) {
462
+ const d = new Date(timestamp);
463
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
464
+ }
465
+ function pickResolution(candidates, rangeMonths) {
466
+ if (candidates.length === 0) return void 0;
467
+ let best;
468
+ for (const candidate of candidates) {
469
+ const unlimited = candidate.maxMonths === -1;
470
+ if (!unlimited && candidate.maxMonths < rangeMonths) continue;
471
+ if (!best) {
472
+ best = candidate;
473
+ continue;
474
+ }
475
+ const bestUnlimited = best.maxMonths === -1;
476
+ if (bestUnlimited && !unlimited) best = candidate;
477
+ else if (!bestUnlimited && !unlimited && candidate.maxMonths < best.maxMonths) best = candidate;
478
+ }
479
+ return best ?? candidates[candidates.length - 1];
480
+ }
481
+ function monthsBetween(from, to) {
482
+ return (to.getFullYear() - from.getFullYear()) * 12 + (to.getMonth() - from.getMonth());
483
+ }
484
+ var AggregationChartComponent = class AggregationChartComponent {
485
+ cn = cn;
486
+ class = input("", ...ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []);
487
+ aggregation = input(void 0, ...ngDevMode ? [{ debugName: "aggregation" }] : /* istanbul ignore next */ []);
488
+ type = input("bar", ...ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []);
489
+ label = input("", ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
490
+ limit = input(10, ...ngDevMode ? [{ debugName: "limit" }] : /* istanbul ignore next */ []);
491
+ asPercentage = input(false, ...ngDevMode ? [{ debugName: "asPercentage" }] : /* istanbul ignore next */ []);
492
+ weightField = input(void 0, ...ngDevMode ? [{ debugName: "weightField" }] : /* istanbul ignore next */ []);
493
+ emptyLabel = input("No data", ...ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []);
494
+ itemSelected = output();
495
+ palette = signal(seriesPalette(), ...ngDevMode ? [{ debugName: "palette" }] : /* istanbul ignore next */ []);
496
+ constructor() {
497
+ afterNextRender(() => this.palette.set(seriesPalette()));
498
+ }
499
+ series = computed(() => toSeries(this.aggregation(), {
500
+ limit: this.limit(),
501
+ weightField: this.weightField()
502
+ }), ...ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []);
503
+ isEmpty = computed(() => this.series().labels.length === 0, ...ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []);
504
+ config = computed(() => {
505
+ const series = this.series();
506
+ const type = this.type();
507
+ const circular = type === "pie" || type === "doughnut";
508
+ const percentages = toPercentages(series.values);
509
+ const plotted = this.asPercentage() ? percentages : series.values;
510
+ const palette = this.palette();
511
+ return {
512
+ type,
513
+ data: {
514
+ labels: series.labels,
515
+ datasets: [{
516
+ label: this.label(),
517
+ data: plotted,
518
+ backgroundColor: circular ? [...palette] : paletteColor(palette, 0)
519
+ }]
520
+ },
521
+ options: {
522
+ plugins: {
523
+ legend: {
524
+ display: circular,
525
+ position: "right"
526
+ },
527
+ tooltip: { callbacks: { label: (ctx) => {
528
+ const index = ctx.dataIndex;
529
+ const count = series.values[index] ?? 0;
530
+ return ` ${(percentages[index] ?? 0).toFixed(1)}% (${count})`;
531
+ } } }
532
+ },
533
+ scales: circular ? {} : { y: {
534
+ beginAtZero: true,
535
+ ticks: this.asPercentage() ? { callback: (value) => `${value}%` } : {}
536
+ } },
537
+ onClick: (_event, elements) => {
538
+ const first = elements[0];
539
+ if (!first) return;
540
+ const label = series.labels[first.index];
541
+ if (label != null) this.itemSelected.emit(label);
542
+ }
543
+ }
544
+ };
545
+ }, ...ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []);
546
+ static ɵfac = i0.ɵɵngDeclareFactory({
547
+ minVersion: "12.0.0",
548
+ version: "22.2.1",
549
+ ngImport: i0,
550
+ type: AggregationChartComponent,
551
+ deps: [],
552
+ target: i0.ɵɵFactoryTarget.Component
553
+ });
554
+ static ɵcmp = i0.ɵɵngDeclareComponent({
555
+ minVersion: "17.0.0",
556
+ version: "22.2.1",
557
+ type: AggregationChartComponent,
558
+ isStandalone: true,
559
+ selector: "AggregationChart, aggregation-chart, aggregationchart",
560
+ inputs: {
561
+ class: {
562
+ classPropertyName: "class",
563
+ publicName: "class",
564
+ isSignal: true,
565
+ isRequired: false,
566
+ transformFunction: null
567
+ },
568
+ aggregation: {
569
+ classPropertyName: "aggregation",
570
+ publicName: "aggregation",
571
+ isSignal: true,
572
+ isRequired: false,
573
+ transformFunction: null
574
+ },
575
+ type: {
576
+ classPropertyName: "type",
577
+ publicName: "type",
578
+ isSignal: true,
579
+ isRequired: false,
580
+ transformFunction: null
581
+ },
582
+ label: {
583
+ classPropertyName: "label",
584
+ publicName: "label",
585
+ isSignal: true,
586
+ isRequired: false,
587
+ transformFunction: null
588
+ },
589
+ limit: {
590
+ classPropertyName: "limit",
591
+ publicName: "limit",
592
+ isSignal: true,
593
+ isRequired: false,
594
+ transformFunction: null
595
+ },
596
+ asPercentage: {
597
+ classPropertyName: "asPercentage",
598
+ publicName: "asPercentage",
599
+ isSignal: true,
600
+ isRequired: false,
601
+ transformFunction: null
602
+ },
603
+ weightField: {
604
+ classPropertyName: "weightField",
605
+ publicName: "weightField",
606
+ isSignal: true,
607
+ isRequired: false,
608
+ transformFunction: null
609
+ },
610
+ emptyLabel: {
611
+ classPropertyName: "emptyLabel",
612
+ publicName: "emptyLabel",
613
+ isSignal: true,
614
+ isRequired: false,
615
+ transformFunction: null
616
+ }
617
+ },
618
+ outputs: { itemSelected: "itemSelected" },
619
+ host: { properties: { "class": "cn(\"block h-full w-full\", class())" } },
620
+ ngImport: i0,
621
+ template: `
622
+ @if (isEmpty()) {
623
+ <p class="flex h-full items-center justify-center text-sm text-(--font-neutral-muted)">{{ emptyLabel() }}</p>
624
+ } @else {
625
+ <ChartCanvas [config]="config()" />
626
+ }
627
+ `,
628
+ isInline: true,
629
+ dependencies: [{
630
+ kind: "component",
631
+ type: ChartCanvasComponent,
632
+ selector: "ChartCanvas, chart-canvas, chartcanvas",
633
+ inputs: [
634
+ "class",
635
+ "config",
636
+ "plugins"
637
+ ]
638
+ }]
639
+ });
640
+ };
641
+ i0.ɵɵngDeclareClassMetadata({
642
+ minVersion: "12.0.0",
643
+ version: "22.2.1",
644
+ ngImport: i0,
645
+ type: AggregationChartComponent,
646
+ decorators: [{
647
+ type: Component,
648
+ args: [{
649
+ selector: "AggregationChart, aggregation-chart, aggregationchart",
650
+ imports: [ChartCanvasComponent],
651
+ template: `
652
+ @if (isEmpty()) {
653
+ <p class="flex h-full items-center justify-center text-sm text-(--font-neutral-muted)">{{ emptyLabel() }}</p>
654
+ } @else {
655
+ <ChartCanvas [config]="config()" />
656
+ }
657
+ `,
658
+ host: { "[class]": "cn(\"block h-full w-full\", class())" }
659
+ }]
660
+ }],
661
+ ctorParameters: () => [],
662
+ propDecorators: {
663
+ class: [{
664
+ type: i0.Input,
665
+ args: [{
666
+ isSignal: true,
667
+ alias: "class",
668
+ required: false
669
+ }]
670
+ }],
671
+ aggregation: [{
672
+ type: i0.Input,
673
+ args: [{
674
+ isSignal: true,
675
+ alias: "aggregation",
676
+ required: false
677
+ }]
678
+ }],
679
+ type: [{
680
+ type: i0.Input,
681
+ args: [{
682
+ isSignal: true,
683
+ alias: "type",
684
+ required: false
685
+ }]
686
+ }],
687
+ label: [{
688
+ type: i0.Input,
689
+ args: [{
690
+ isSignal: true,
691
+ alias: "label",
692
+ required: false
693
+ }]
694
+ }],
695
+ limit: [{
696
+ type: i0.Input,
697
+ args: [{
698
+ isSignal: true,
699
+ alias: "limit",
700
+ required: false
701
+ }]
702
+ }],
703
+ asPercentage: [{
704
+ type: i0.Input,
705
+ args: [{
706
+ isSignal: true,
707
+ alias: "asPercentage",
708
+ required: false
709
+ }]
710
+ }],
711
+ weightField: [{
712
+ type: i0.Input,
713
+ args: [{
714
+ isSignal: true,
715
+ alias: "weightField",
716
+ required: false
717
+ }]
718
+ }],
719
+ emptyLabel: [{
720
+ type: i0.Input,
721
+ args: [{
722
+ isSignal: true,
723
+ alias: "emptyLabel",
724
+ required: false
725
+ }]
726
+ }],
727
+ itemSelected: [{
728
+ type: i0.Output,
729
+ args: ["itemSelected"]
730
+ }]
731
+ }
732
+ });
733
+ var AggregationTimelineComponent = class AggregationTimelineComponent {
734
+ cn = cn;
735
+ class = input("", ...ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []);
736
+ series = input([], ...ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []);
737
+ mask = input("YYYY-MM-DD", ...ngDevMode ? [{ debugName: "mask" }] : /* istanbul ignore next */ []);
738
+ enableSelection = input(true, ...ngDevMode ? [{ debugName: "enableSelection" }] : /* istanbul ignore next */ []);
739
+ dashSecondary = input(false, ...ngDevMode ? [{ debugName: "dashSecondary" }] : /* istanbul ignore next */ []);
740
+ emptyLabel = input("No data", ...ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []);
741
+ rangeSelected = output();
742
+ brush = createTimelineBrushPlugin((from, to) => this.rangeSelected.emit({
743
+ from,
744
+ to
745
+ }));
746
+ palette = signal(timelinePalette(), ...ngDevMode ? [{ debugName: "palette" }] : /* istanbul ignore next */ []);
747
+ constructor() {
748
+ afterNextRender(() => this.palette.set(timelinePalette()));
749
+ }
750
+ plugins = computed(() => this.enableSelection() ? [this.brush] : [], ...ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []);
751
+ timeline = computed(() => buildTimeline(this.series(), { mask: this.mask() }), ...ngDevMode ? [{ debugName: "timeline" }] : /* istanbul ignore next */ []);
752
+ isEmpty = computed(() => this.timeline().labels.length === 0, ...ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []);
753
+ config = computed(() => {
754
+ const { labels, series } = this.timeline();
755
+ const filled = series.length === 1;
756
+ const dashSecondary = this.dashSecondary();
757
+ const palette = this.palette();
758
+ const datasets = series.map((entry, index) => {
759
+ const color = paletteColor(palette, dashSecondary ? Math.floor(index / 2) : index);
760
+ const secondary = dashSecondary && index % 2 === 1;
761
+ return {
762
+ label: entry.label,
763
+ data: entry.values,
764
+ borderColor: color,
765
+ backgroundColor: filled ? withAlpha(color, .18) : color,
766
+ borderWidth: 1.5,
767
+ borderDash: secondary ? [4, 4] : void 0,
768
+ stepped: "middle",
769
+ fill: filled && !secondary,
770
+ spanGaps: true,
771
+ pointRadius: 0,
772
+ pointHoverRadius: 4
773
+ };
774
+ });
775
+ return {
776
+ type: "line",
777
+ data: {
778
+ labels,
779
+ datasets
780
+ },
781
+ options: {
782
+ events: [
783
+ "mousedown",
784
+ "mousemove",
785
+ "mouseup",
786
+ "mouseout",
787
+ "click",
788
+ "touchstart",
789
+ "touchmove",
790
+ "touchend"
791
+ ],
792
+ interaction: {
793
+ mode: "index",
794
+ intersect: false
795
+ },
796
+ scales: {
797
+ x: {
798
+ type: "category",
799
+ grid: { display: false }
800
+ },
801
+ y: { beginAtZero: true }
802
+ },
803
+ plugins: { legend: {
804
+ display: datasets.length > 1,
805
+ position: "bottom"
806
+ } }
807
+ }
808
+ };
809
+ }, ...ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []);
810
+ static ɵfac = i0.ɵɵngDeclareFactory({
811
+ minVersion: "12.0.0",
812
+ version: "22.2.1",
813
+ ngImport: i0,
814
+ type: AggregationTimelineComponent,
815
+ deps: [],
816
+ target: i0.ɵɵFactoryTarget.Component
817
+ });
818
+ static ɵcmp = i0.ɵɵngDeclareComponent({
819
+ minVersion: "17.0.0",
820
+ version: "22.2.1",
821
+ type: AggregationTimelineComponent,
822
+ isStandalone: true,
823
+ selector: "AggregationTimeline, aggregation-timeline, aggregationtimeline",
824
+ inputs: {
825
+ class: {
826
+ classPropertyName: "class",
827
+ publicName: "class",
828
+ isSignal: true,
829
+ isRequired: false,
830
+ transformFunction: null
831
+ },
832
+ series: {
833
+ classPropertyName: "series",
834
+ publicName: "series",
835
+ isSignal: true,
836
+ isRequired: false,
837
+ transformFunction: null
838
+ },
839
+ mask: {
840
+ classPropertyName: "mask",
841
+ publicName: "mask",
842
+ isSignal: true,
843
+ isRequired: false,
844
+ transformFunction: null
845
+ },
846
+ enableSelection: {
847
+ classPropertyName: "enableSelection",
848
+ publicName: "enableSelection",
849
+ isSignal: true,
850
+ isRequired: false,
851
+ transformFunction: null
852
+ },
853
+ dashSecondary: {
854
+ classPropertyName: "dashSecondary",
855
+ publicName: "dashSecondary",
856
+ isSignal: true,
857
+ isRequired: false,
858
+ transformFunction: null
859
+ },
860
+ emptyLabel: {
861
+ classPropertyName: "emptyLabel",
862
+ publicName: "emptyLabel",
863
+ isSignal: true,
864
+ isRequired: false,
865
+ transformFunction: null
866
+ }
867
+ },
868
+ outputs: { rangeSelected: "rangeSelected" },
869
+ host: { properties: { "class": "cn(\"block h-full w-full\", class())" } },
870
+ ngImport: i0,
871
+ template: `
872
+ @if (isEmpty()) {
873
+ <p class="flex h-full items-center justify-center text-sm text-(--font-neutral-muted)">{{ emptyLabel() }}</p>
874
+ } @else {
875
+ <ChartCanvas [config]="config()" [plugins]="plugins()" />
876
+ }
877
+ `,
878
+ isInline: true,
879
+ dependencies: [{
880
+ kind: "component",
881
+ type: ChartCanvasComponent,
882
+ selector: "ChartCanvas, chart-canvas, chartcanvas",
883
+ inputs: [
884
+ "class",
885
+ "config",
886
+ "plugins"
887
+ ]
888
+ }]
889
+ });
890
+ };
891
+ i0.ɵɵngDeclareClassMetadata({
892
+ minVersion: "12.0.0",
893
+ version: "22.2.1",
894
+ ngImport: i0,
895
+ type: AggregationTimelineComponent,
896
+ decorators: [{
897
+ type: Component,
898
+ args: [{
899
+ selector: "AggregationTimeline, aggregation-timeline, aggregationtimeline",
900
+ imports: [ChartCanvasComponent],
901
+ template: `
902
+ @if (isEmpty()) {
903
+ <p class="flex h-full items-center justify-center text-sm text-(--font-neutral-muted)">{{ emptyLabel() }}</p>
904
+ } @else {
905
+ <ChartCanvas [config]="config()" [plugins]="plugins()" />
906
+ }
907
+ `,
908
+ host: { "[class]": "cn(\"block h-full w-full\", class())" }
909
+ }]
910
+ }],
911
+ ctorParameters: () => [],
912
+ propDecorators: {
913
+ class: [{
914
+ type: i0.Input,
915
+ args: [{
916
+ isSignal: true,
917
+ alias: "class",
918
+ required: false
919
+ }]
920
+ }],
921
+ series: [{
922
+ type: i0.Input,
923
+ args: [{
924
+ isSignal: true,
925
+ alias: "series",
926
+ required: false
927
+ }]
928
+ }],
929
+ mask: [{
930
+ type: i0.Input,
931
+ args: [{
932
+ isSignal: true,
933
+ alias: "mask",
934
+ required: false
935
+ }]
936
+ }],
937
+ enableSelection: [{
938
+ type: i0.Input,
939
+ args: [{
940
+ isSignal: true,
941
+ alias: "enableSelection",
942
+ required: false
943
+ }]
944
+ }],
945
+ dashSecondary: [{
946
+ type: i0.Input,
947
+ args: [{
948
+ isSignal: true,
949
+ alias: "dashSecondary",
950
+ required: false
951
+ }]
952
+ }],
953
+ emptyLabel: [{
954
+ type: i0.Input,
955
+ args: [{
956
+ isSignal: true,
957
+ alias: "emptyLabel",
958
+ required: false
959
+ }]
960
+ }],
961
+ rangeSelected: [{
962
+ type: i0.Output,
963
+ args: ["rangeSelected"]
964
+ }]
965
+ }
966
+ });
967
+ export { AggregationChartComponent, AggregationTimelineComponent, ChartCanvasComponent, ChartThemeService, applyChartTheme, buildTimeline, createAxisClickPlugin, createTimelineBrushPlugin, monthsBetween, parseBucketDate, pickResolution, setupCharts };
968
+
969
+ //# sourceMappingURL=sinequa-atomic-angular-charts.mjs.map