@praxisui/charts 3.0.0-beta.3 → 3.0.0-beta.5
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 +6 -1
- package/fesm2022/praxisui-charts.mjs +2904 -1377
- package/fesm2022/praxisui-charts.mjs.map +1 -1
- package/index.d.ts +381 -93
- package/package.json +2 -2
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import { CommonModule } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { Injectable, Inject, InjectionToken, input, output, viewChild, inject, DestroyRef, signal, computed, effect, ChangeDetectionStrategy, Component, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
4
|
-
import * as i1$
|
|
5
|
-
import { buildApiUrl, API_URL, PraxisI18nService, ComponentMetadataRegistry, createDefaultTableConfig, DynamicWidgetPageComponent, DynamicGridPageComponent } from '@praxisui/core';
|
|
3
|
+
import { Injectable, Inject, InjectionToken, input, booleanAttribute, output, viewChild, inject, ElementRef, DestroyRef, signal, computed, afterNextRender, effect, ChangeDetectionStrategy, Component, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
4
|
+
import * as i1$2 from '@praxisui/core';
|
|
5
|
+
import { buildApiUrl, API_URL, PraxisI18nService, SETTINGS_PANEL_BRIDGE, ComponentMetadataRegistry, createDefaultTableConfig, DynamicWidgetPageComponent, DynamicGridPageComponent, providePraxisI18n, SETTINGS_PANEL_DATA } from '@praxisui/core';
|
|
6
6
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import * as i1$1 from '@angular/material/button';
|
|
8
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
9
|
+
import * as i2 from '@angular/material/icon';
|
|
10
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
11
|
+
import * as i3 from '@angular/material/tooltip';
|
|
12
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
7
13
|
import { use, init } from 'echarts/core';
|
|
8
14
|
import { BarChart, LineChart, PieChart, ScatterChart } from 'echarts/charts';
|
|
9
15
|
import { AriaComponent, DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent, TransformComponent } from 'echarts/components';
|
|
10
16
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
11
17
|
import * as i1 from '@angular/common/http';
|
|
12
18
|
import { HttpErrorResponse } from '@angular/common/http';
|
|
13
|
-
import { throwError, map } from 'rxjs';
|
|
19
|
+
import { throwError, map, BehaviorSubject } from 'rxjs';
|
|
14
20
|
import { catchError } from 'rxjs/operators';
|
|
15
21
|
import { PraxisTable } from '@praxisui/table';
|
|
22
|
+
import * as i1$3 from '@angular/forms';
|
|
23
|
+
import { FormsModule } from '@angular/forms';
|
|
24
|
+
import * as i3$1 from '@angular/material/card';
|
|
25
|
+
import { MatCardModule } from '@angular/material/card';
|
|
26
|
+
import * as i4 from '@angular/material/form-field';
|
|
27
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
28
|
+
import * as i5 from '@angular/material/input';
|
|
29
|
+
import { MatInputModule } from '@angular/material/input';
|
|
30
|
+
import * as i6 from '@angular/material/select';
|
|
31
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
32
|
+
import * as i7 from '@angular/material/slide-toggle';
|
|
33
|
+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
16
34
|
|
|
17
35
|
class PraxisChartDataTransformerService {
|
|
18
36
|
transform(config, rows) {
|
|
@@ -484,1516 +502,1974 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
484
502
|
type: Injectable
|
|
485
503
|
}], ctorParameters: () => [{ type: PraxisChartOptionBuilderService }] });
|
|
486
504
|
|
|
487
|
-
class
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
505
|
+
class ChartContractNormalizerService {
|
|
506
|
+
normalize(input) {
|
|
507
|
+
let document = structuredClone(input);
|
|
508
|
+
document = this.normalizeMotion(document);
|
|
509
|
+
document = this.normalizeKindSpecificFields(document);
|
|
510
|
+
document = this.normalizeOperationSpecificFields(document);
|
|
511
|
+
document = this.normalizeSourceSpecificFields(document);
|
|
512
|
+
return document;
|
|
493
513
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (!query?.statsPath || !statsRequest) {
|
|
498
|
-
return throwError(() => new Error('PraxisChartStatsApiService requires query.statsPath and query.statsRequest for praxis.stats execution.'));
|
|
514
|
+
normalizeMotion(document) {
|
|
515
|
+
if (!document.motion) {
|
|
516
|
+
return document;
|
|
499
517
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
518
|
+
if (document.motion.enabled === false) {
|
|
519
|
+
return {
|
|
520
|
+
...document,
|
|
521
|
+
motion: {
|
|
522
|
+
enabled: false,
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
...document,
|
|
528
|
+
motion: {
|
|
529
|
+
enabled: document.motion.enabled ?? true,
|
|
530
|
+
preset: document.motion.preset ?? 'standard',
|
|
531
|
+
},
|
|
532
|
+
};
|
|
505
533
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
534
|
+
normalizeKindSpecificFields(document) {
|
|
535
|
+
let nextDocument = document;
|
|
536
|
+
if (document.kind === 'horizontal-bar') {
|
|
537
|
+
nextDocument = {
|
|
538
|
+
...nextDocument,
|
|
539
|
+
orientation: 'horizontal',
|
|
540
|
+
};
|
|
509
541
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
value: point.value ?? null,
|
|
520
|
-
count: point.count ?? null,
|
|
521
|
-
start: point.start ?? null,
|
|
522
|
-
end: point.end ?? null,
|
|
523
|
-
granularity: response.granularity ?? null,
|
|
524
|
-
};
|
|
525
|
-
});
|
|
542
|
+
if (document.kind !== 'combo' && document.metrics?.length) {
|
|
543
|
+
nextDocument = {
|
|
544
|
+
...nextDocument,
|
|
545
|
+
metrics: document.metrics.map((metric) => ({
|
|
546
|
+
...metric,
|
|
547
|
+
axis: undefined,
|
|
548
|
+
seriesKind: undefined,
|
|
549
|
+
})),
|
|
550
|
+
};
|
|
526
551
|
}
|
|
527
|
-
if ('
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
...this.projectMetricValues(metricBindings, bucket.values, bucket.value, bucket.count),
|
|
533
|
-
key: bucket.key ?? null,
|
|
534
|
-
label: bucket.label ?? category,
|
|
535
|
-
value: bucket.value ?? null,
|
|
536
|
-
count: bucket.count ?? null,
|
|
537
|
-
from: bucket.from ?? null,
|
|
538
|
-
to: bucket.to ?? null,
|
|
539
|
-
mode: 'mode' in response ? response.mode ?? null : null,
|
|
540
|
-
requestMode: 'mode' in request ? request.mode : null,
|
|
541
|
-
};
|
|
542
|
-
});
|
|
552
|
+
if ((document.kind === 'pie' || document.kind === 'donut' || this.isDistribution(document)) && document.metrics?.length) {
|
|
553
|
+
nextDocument = {
|
|
554
|
+
...nextDocument,
|
|
555
|
+
metrics: document.metrics.slice(0, 1),
|
|
556
|
+
};
|
|
543
557
|
}
|
|
544
|
-
return
|
|
558
|
+
return nextDocument;
|
|
545
559
|
}
|
|
546
|
-
|
|
547
|
-
if (
|
|
548
|
-
return
|
|
560
|
+
normalizeOperationSpecificFields(document) {
|
|
561
|
+
if (document.source.kind !== 'praxis.stats') {
|
|
562
|
+
return document;
|
|
549
563
|
}
|
|
550
|
-
|
|
551
|
-
|
|
564
|
+
const options = { ...document.source.options };
|
|
565
|
+
if (document.source.operation !== 'timeseries') {
|
|
566
|
+
options.granularity = undefined;
|
|
567
|
+
options.fillGaps = undefined;
|
|
552
568
|
}
|
|
553
|
-
if (
|
|
554
|
-
|
|
569
|
+
if (document.source.operation !== 'distribution') {
|
|
570
|
+
options.mode = undefined;
|
|
571
|
+
options.bucketSize = undefined;
|
|
572
|
+
options.bucketCount = undefined;
|
|
555
573
|
}
|
|
556
|
-
return
|
|
574
|
+
return {
|
|
575
|
+
...document,
|
|
576
|
+
source: {
|
|
577
|
+
...document.source,
|
|
578
|
+
options,
|
|
579
|
+
},
|
|
580
|
+
};
|
|
557
581
|
}
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
return
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
582
|
+
normalizeSourceSpecificFields(document) {
|
|
583
|
+
if (document.source.kind === 'derived') {
|
|
584
|
+
return {
|
|
585
|
+
...document,
|
|
586
|
+
source: {
|
|
587
|
+
kind: 'derived',
|
|
588
|
+
},
|
|
589
|
+
};
|
|
564
590
|
}
|
|
565
|
-
return
|
|
591
|
+
return document;
|
|
566
592
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return (firstSeries?.categoryField
|
|
570
|
-
|| config.axes?.x?.field
|
|
571
|
-
|| request.field
|
|
572
|
-
|| 'category');
|
|
593
|
+
isDistribution(document) {
|
|
594
|
+
return document.source.kind === 'praxis.stats' && document.source.operation === 'distribution';
|
|
573
595
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
596
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartContractNormalizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
597
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartContractNormalizerService, providedIn: 'root' });
|
|
598
|
+
}
|
|
599
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartContractNormalizerService, decorators: [{
|
|
600
|
+
type: Injectable,
|
|
601
|
+
args: [{ providedIn: 'root' }]
|
|
602
|
+
}] });
|
|
603
|
+
|
|
604
|
+
class ChartContractValidationService {
|
|
605
|
+
validate(document) {
|
|
606
|
+
const issues = [];
|
|
607
|
+
this.validateSource(document, issues);
|
|
608
|
+
this.validateTheme(document, issues);
|
|
609
|
+
this.validateMetrics(document, issues);
|
|
610
|
+
this.validateKinds(document, issues);
|
|
611
|
+
this.validateEvents(document, issues);
|
|
612
|
+
return {
|
|
613
|
+
valid: !issues.some((issue) => issue.severity === 'error'),
|
|
614
|
+
issues,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
validateSource(document, issues) {
|
|
618
|
+
if (document.source.kind !== 'praxis.stats' && document.source.kind !== 'derived') {
|
|
619
|
+
issues.push(this.error('unsupported-source-kind', 'source.kind', `x-ui.chart source.kind="${document.source.kind}" is not supported in @praxisui/charts.`));
|
|
583
620
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
return responseMetrics.map((metric, index) => ({
|
|
587
|
-
field: metric.alias || metric.field || `value${index + 1}`,
|
|
588
|
-
alias: metric.alias || metric.field || `value${index + 1}`,
|
|
589
|
-
}));
|
|
621
|
+
if (document.source.kind === 'praxis.stats' && !document.source.resource?.trim()) {
|
|
622
|
+
issues.push(this.error('missing-resource', 'source.resource', 'x-ui.chart source.resource is required for source.kind="praxis.stats".'));
|
|
590
623
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
return requestMetrics.map((metric, index) => ({
|
|
594
|
-
field: metric.alias || metric.field || `value${index + 1}`,
|
|
595
|
-
alias: metric.alias || metric.field || `value${index + 1}`,
|
|
596
|
-
}));
|
|
624
|
+
if (document.source.kind === 'praxis.stats' && !document.source.operation) {
|
|
625
|
+
issues.push(this.error('missing-operation', 'source.operation', 'x-ui.chart source.operation is required for source.kind="praxis.stats".'));
|
|
597
626
|
}
|
|
598
|
-
const metric = ('metric' in response && response.metric) || ('metric' in request ? request.metric : undefined);
|
|
599
|
-
return [
|
|
600
|
-
{
|
|
601
|
-
field: metric?.alias || metric?.field || config.series[0]?.metric?.field || 'value',
|
|
602
|
-
alias: metric?.alias || metric?.field || config.series[0]?.metric?.field || 'value',
|
|
603
|
-
},
|
|
604
|
-
];
|
|
605
627
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}, {});
|
|
614
|
-
}
|
|
615
|
-
buildStatsUrl(statsPath) {
|
|
616
|
-
const base = this.buildDefaultApiBase();
|
|
617
|
-
const normalizedStatsPath = this.normalizePath(statsPath);
|
|
618
|
-
const resolvedStatsPath = this.shouldStripLeadingApiSegment(base)
|
|
619
|
-
? this.stripLeadingApiSegment(normalizedStatsPath)
|
|
620
|
-
: normalizedStatsPath;
|
|
621
|
-
return `${base}/${resolvedStatsPath}`;
|
|
622
|
-
}
|
|
623
|
-
buildDefaultApiBase() {
|
|
624
|
-
const entry = this.apiUrl?.default ?? {};
|
|
625
|
-
return buildApiUrl(entry).replace(/\/+$/, '');
|
|
628
|
+
validateTheme(document, issues) {
|
|
629
|
+
if (document.theme?.palette && typeof document.theme.palette === 'string') {
|
|
630
|
+
issues.push(this.error('palette-token-unsupported', 'theme.palette', 'x-ui.chart theme.palette as palette token reference is not yet implemented in @praxisui/charts.'));
|
|
631
|
+
}
|
|
632
|
+
if (document.theme?.variant) {
|
|
633
|
+
issues.push(this.error('theme-variant-unsupported', 'theme.variant', 'x-ui.chart theme.variant is not yet implemented in @praxisui/charts.'));
|
|
634
|
+
}
|
|
626
635
|
}
|
|
627
|
-
|
|
628
|
-
|
|
636
|
+
validateMetrics(document, issues) {
|
|
637
|
+
if (!document.metrics?.length) {
|
|
638
|
+
issues.push(this.error('missing-metric', 'metrics', 'x-ui.chart requires at least one metric for the current @praxisui/charts runtime.'));
|
|
639
|
+
}
|
|
640
|
+
const aggregations = [
|
|
641
|
+
...(document.metrics?.map((metric) => metric.aggregation).filter(Boolean) ?? []),
|
|
642
|
+
...(document.aggregations?.map((aggregation) => aggregation.operation) ?? []),
|
|
643
|
+
];
|
|
644
|
+
if (aggregations.includes('distinct-count')) {
|
|
645
|
+
issues.push(this.error('distinct-count-unsupported', 'metrics', 'x-ui.chart aggregation "distinct-count" is not yet implemented in @praxisui/charts.'));
|
|
646
|
+
}
|
|
629
647
|
}
|
|
630
|
-
|
|
631
|
-
|
|
648
|
+
validateKinds(document, issues) {
|
|
649
|
+
const metricCount = document.metrics?.length ?? 0;
|
|
650
|
+
if (document.kind !== 'pie' && document.kind !== 'donut' && !document.dimensions?.length) {
|
|
651
|
+
issues.push(this.error('missing-dimension', 'dimensions', 'x-ui.chart cartesian charts require at least one dimension in the current @praxisui/charts runtime.'));
|
|
652
|
+
}
|
|
653
|
+
if ((document.kind === 'pie' || document.kind === 'donut') && !document.dimensions?.[0]?.field) {
|
|
654
|
+
issues.push(this.error('pie-missing-dimension', 'dimensions[0].field', 'x-ui.chart pie/donut charts require a first dimension for category mapping.'));
|
|
655
|
+
}
|
|
656
|
+
if ((document.kind === 'pie' || document.kind === 'donut') && metricCount > 1) {
|
|
657
|
+
issues.push(this.error('pie-multi-metric', 'metrics', 'x-ui.chart pie/donut charts with multiple metrics are not yet implemented in @praxisui/charts.'));
|
|
658
|
+
}
|
|
659
|
+
if (document.kind === 'combo' && metricCount < 2) {
|
|
660
|
+
issues.push(this.error('combo-min-metrics', 'metrics', 'x-ui.chart combo charts require at least two metrics.'));
|
|
661
|
+
}
|
|
662
|
+
if (document.kind !== 'combo' && document.metrics?.some((metric) => metric.axis === 'secondary')) {
|
|
663
|
+
issues.push(this.error('secondary-axis-non-combo', 'metrics', 'x-ui.chart axis="secondary" is supported only for combo charts in @praxisui/charts.'));
|
|
664
|
+
}
|
|
665
|
+
if (document.source.kind === 'praxis.stats'
|
|
666
|
+
&& document.source.operation === 'distribution'
|
|
667
|
+
&& metricCount > 1) {
|
|
668
|
+
issues.push(this.error('distribution-single-metric', 'metrics', 'x-ui.chart praxis.stats distribution currently supports only a single metric in @praxisui/charts.'));
|
|
669
|
+
}
|
|
670
|
+
if (document.kind === 'horizontal-bar' && document.orientation && document.orientation !== 'horizontal') {
|
|
671
|
+
issues.push(this.error('horizontal-bar-orientation', 'orientation', 'x-ui.chart kind="horizontal-bar" requires orientation="horizontal" when orientation is provided.'));
|
|
672
|
+
}
|
|
673
|
+
if (document.kind === 'scatter' && !document.dimensions?.[0]?.field) {
|
|
674
|
+
issues.push(this.error('scatter-missing-dimension', 'dimensions[0].field', 'x-ui.chart scatter charts require dimensions[0].field for the x axis.'));
|
|
675
|
+
}
|
|
676
|
+
if (document.kind === 'scatter' && !document.metrics?.[0]?.field) {
|
|
677
|
+
issues.push(this.error('scatter-missing-metric', 'metrics[0].field', 'x-ui.chart scatter charts require metrics[0].field for the y axis.'));
|
|
678
|
+
}
|
|
679
|
+
if (document.kind === 'combo'
|
|
680
|
+
&& document.source.kind === 'praxis.stats'
|
|
681
|
+
&& document.source.operation !== 'group-by'
|
|
682
|
+
&& document.source.operation !== 'timeseries') {
|
|
683
|
+
issues.push(this.error('combo-operation-unsupported', 'source.operation', 'x-ui.chart combo charts over praxis.stats currently support only group-by or timeseries operations in @praxisui/charts.'));
|
|
684
|
+
}
|
|
632
685
|
}
|
|
633
|
-
|
|
634
|
-
|
|
686
|
+
validateEvents(document, issues) {
|
|
687
|
+
this.validateEventAction('pointClick', document.events?.pointClick, issues);
|
|
688
|
+
this.validateEventAction('drillDown', document.events?.drillDown, issues);
|
|
689
|
+
if (document.events?.selectionChange) {
|
|
690
|
+
issues.push(this.error('selection-change-unsupported', 'events.selectionChange', 'x-ui.chart selectionChange/crossFilter declarative runtime actions are not yet implemented in @praxisui/charts.'));
|
|
691
|
+
}
|
|
692
|
+
if (document.events?.crossFilter) {
|
|
693
|
+
issues.push(this.error('cross-filter-unsupported', 'events.crossFilter', 'x-ui.chart selectionChange/crossFilter declarative runtime actions are not yet implemented in @praxisui/charts.'));
|
|
694
|
+
}
|
|
635
695
|
}
|
|
636
|
-
|
|
637
|
-
if (
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
696
|
+
validateEventAction(eventKey, eventAction, issues) {
|
|
697
|
+
if (!eventAction?.action) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (eventAction.action !== 'emit' && !eventAction.target?.trim()) {
|
|
701
|
+
issues.push(this.error(`${eventKey}-missing-target`, `events.${eventKey}.target`, `x-ui.chart events.${eventKey}.target is required when action="${eventAction.action}".`));
|
|
642
702
|
}
|
|
643
|
-
return throwError(() => error instanceof Error ? error : new Error('Unexpected failure during praxis.stats execution.'));
|
|
644
703
|
}
|
|
645
|
-
|
|
646
|
-
|
|
704
|
+
error(code, field, message) {
|
|
705
|
+
return {
|
|
706
|
+
severity: 'error',
|
|
707
|
+
code,
|
|
708
|
+
field,
|
|
709
|
+
message,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartContractValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
713
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartContractValidationService, providedIn: 'root' });
|
|
647
714
|
}
|
|
648
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type:
|
|
715
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartContractValidationService, decorators: [{
|
|
649
716
|
type: Injectable,
|
|
650
717
|
args: [{ providedIn: 'root' }]
|
|
651
|
-
}]
|
|
652
|
-
type: Inject,
|
|
653
|
-
args: [API_URL]
|
|
654
|
-
}] }] });
|
|
655
|
-
|
|
656
|
-
const PRAXIS_CHART_ENGINE = new InjectionToken('PRAXIS_CHART_ENGINE');
|
|
718
|
+
}] });
|
|
657
719
|
|
|
658
|
-
class
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
currentLoadState = signal('idle', ...(ngDevMode ? [{ debugName: "currentLoadState" }] : []));
|
|
672
|
-
remoteResolvedData = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedData" }] : []));
|
|
673
|
-
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
|
|
674
|
-
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
|
|
675
|
-
previousRemoteSignature = null;
|
|
676
|
-
resolvedData = computed(() => {
|
|
677
|
-
const inputData = this.data();
|
|
678
|
-
if (inputData !== null && inputData !== undefined) {
|
|
679
|
-
return inputData;
|
|
680
|
-
}
|
|
681
|
-
const remoteData = this.remoteResolvedData();
|
|
682
|
-
if (remoteData !== null) {
|
|
683
|
-
return remoteData;
|
|
720
|
+
class PraxisChartCanonicalContractMapperService {
|
|
721
|
+
normalizer;
|
|
722
|
+
validator;
|
|
723
|
+
constructor(normalizer = new ChartContractNormalizerService(), validator = new ChartContractValidationService()) {
|
|
724
|
+
this.normalizer = normalizer;
|
|
725
|
+
this.validator = validator;
|
|
726
|
+
}
|
|
727
|
+
toPraxisChartConfig(contract) {
|
|
728
|
+
const normalizedContract = this.normalizer.normalize(contract);
|
|
729
|
+
const validation = this.validator.validate(normalizedContract);
|
|
730
|
+
const firstError = validation.issues.find((issue) => issue.severity === 'error');
|
|
731
|
+
if (firstError) {
|
|
732
|
+
throw new Error(firstError.message);
|
|
684
733
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
734
|
+
return {
|
|
735
|
+
id: normalizedContract.chartId,
|
|
736
|
+
type: normalizedContract.kind,
|
|
737
|
+
orientation: this.resolveOrientation(normalizedContract),
|
|
738
|
+
title: this.mapTextValue(normalizedContract.title),
|
|
739
|
+
subtitle: this.mapTextValue(normalizedContract.subtitle),
|
|
740
|
+
height: normalizedContract.height,
|
|
741
|
+
axes: this.buildAxes(normalizedContract),
|
|
742
|
+
series: this.buildSeries(normalizedContract),
|
|
743
|
+
dataSource: this.buildDataSource(normalizedContract),
|
|
744
|
+
interactions: this.buildInteractions(normalizedContract),
|
|
745
|
+
theme: this.buildTheme(normalizedContract),
|
|
746
|
+
motion: this.buildMotion(normalizedContract.motion),
|
|
747
|
+
emptyState: normalizedContract.state?.empty
|
|
748
|
+
? {
|
|
749
|
+
title: this.mapTextValue(normalizedContract.state.empty.title),
|
|
750
|
+
description: this.mapTextValue(normalizedContract.state.empty.description),
|
|
751
|
+
}
|
|
752
|
+
: undefined,
|
|
753
|
+
state: {
|
|
754
|
+
loadingLabel: this.mapTextValue(normalizedContract.state?.loading?.title),
|
|
755
|
+
error: normalizedContract.state?.error
|
|
756
|
+
? {
|
|
757
|
+
title: this.mapTextValue(normalizedContract.state.error.title),
|
|
758
|
+
description: this.mapTextValue(normalizedContract.state.error.description),
|
|
759
|
+
}
|
|
760
|
+
: undefined,
|
|
761
|
+
},
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
buildAxes(contract) {
|
|
765
|
+
const firstDimension = contract.dimensions?.[0];
|
|
766
|
+
const firstMetric = contract.metrics?.[0];
|
|
767
|
+
const secondaryMetric = contract.metrics?.find((metric) => metric.axis === 'secondary');
|
|
768
|
+
const metricCount = contract.metrics?.length ?? 0;
|
|
769
|
+
if (contract.kind === 'pie' || contract.kind === 'donut') {
|
|
770
|
+
return {
|
|
771
|
+
x: {
|
|
772
|
+
field: firstDimension?.field,
|
|
773
|
+
label: this.toLabel(firstDimension?.label),
|
|
774
|
+
},
|
|
775
|
+
};
|
|
688
776
|
}
|
|
689
|
-
|
|
690
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedData" }] : []));
|
|
691
|
-
resolvedHeight = computed(() => {
|
|
692
|
-
const value = this.config().height;
|
|
693
|
-
if (typeof value === 'number')
|
|
694
|
-
return `${value}px`;
|
|
695
|
-
return value || '320px';
|
|
696
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedHeight" }] : []));
|
|
697
|
-
renderConfig = computed(() => {
|
|
698
|
-
const config = this.config();
|
|
699
|
-
const explicitData = this.data();
|
|
700
|
-
const remoteState = this.remoteRuntimeState();
|
|
701
|
-
const remoteData = this.remoteResolvedData();
|
|
702
|
-
if (explicitData !== null && explicitData !== undefined) {
|
|
703
|
-
return config;
|
|
704
|
-
}
|
|
705
|
-
if (config.dataSource?.kind !== 'remote') {
|
|
706
|
-
return config;
|
|
707
|
-
}
|
|
708
|
-
if (remoteState === 'loading') {
|
|
777
|
+
if (contract.kind === 'scatter') {
|
|
709
778
|
return {
|
|
710
|
-
|
|
711
|
-
|
|
779
|
+
x: {
|
|
780
|
+
field: firstDimension?.field,
|
|
781
|
+
label: this.toLabel(firstDimension?.label),
|
|
782
|
+
type: firstDimension?.role === 'time' ? 'time' : 'value',
|
|
783
|
+
},
|
|
784
|
+
y: {
|
|
785
|
+
field: firstMetric?.field,
|
|
786
|
+
label: this.toLabel(firstMetric?.label),
|
|
787
|
+
type: 'value',
|
|
788
|
+
},
|
|
712
789
|
};
|
|
713
790
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
:
|
|
727
|
-
|
|
791
|
+
return {
|
|
792
|
+
x: {
|
|
793
|
+
field: firstDimension?.field,
|
|
794
|
+
label: this.toLabel(firstDimension?.label),
|
|
795
|
+
type: firstDimension?.role === 'time' ? 'time' : 'category',
|
|
796
|
+
},
|
|
797
|
+
y: {
|
|
798
|
+
label: metricCount > 1 ? undefined : this.toLabel(firstMetric?.label),
|
|
799
|
+
type: 'value',
|
|
800
|
+
},
|
|
801
|
+
ySecondary: contract.kind === 'combo' && secondaryMetric
|
|
802
|
+
? {
|
|
803
|
+
label: this.toLabel(secondaryMetric.label),
|
|
804
|
+
type: 'value',
|
|
805
|
+
position: 'right',
|
|
806
|
+
}
|
|
807
|
+
: undefined,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
buildSeries(contract) {
|
|
811
|
+
const firstDimension = contract.dimensions?.[0];
|
|
812
|
+
const labelsVisible = this.resolveToggle(contract.labels);
|
|
813
|
+
return (contract.metrics ?? []).map((metric, index) => ({
|
|
814
|
+
id: `${metric.field}-${index + 1}`,
|
|
815
|
+
name: this.toLabel(metric.label) ?? metric.field,
|
|
816
|
+
type: this.resolveSeriesType(contract.kind, metric.seriesKind, index),
|
|
817
|
+
axis: metric.axis ?? 'primary',
|
|
818
|
+
categoryField: contract.kind === 'pie' || contract.kind === 'donut' ? firstDimension?.field : undefined,
|
|
819
|
+
metric: {
|
|
820
|
+
field: metric.field,
|
|
821
|
+
aggregation: this.mapAggregation(metric.aggregation),
|
|
822
|
+
label: this.toLabel(metric.label),
|
|
823
|
+
},
|
|
824
|
+
color: metric.color,
|
|
825
|
+
stackId: contract.kind === 'stacked-bar' || contract.kind === 'stacked-area' ? 'stack-1' : undefined,
|
|
826
|
+
labels: labelsVisible ? { visible: true } : undefined,
|
|
827
|
+
smooth: this.shouldSmoothSeries(contract.kind, metric.seriesKind),
|
|
828
|
+
}));
|
|
829
|
+
}
|
|
830
|
+
buildDataSource(contract) {
|
|
831
|
+
if (contract.source.kind === 'derived') {
|
|
832
|
+
return undefined;
|
|
728
833
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
},
|
|
740
|
-
})),
|
|
741
|
-
};
|
|
834
|
+
return {
|
|
835
|
+
kind: 'remote',
|
|
836
|
+
resourcePath: contract.source.resource,
|
|
837
|
+
schemaId: contract.source.resource,
|
|
838
|
+
query: this.buildQuery(contract),
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
buildQuery(contract) {
|
|
842
|
+
if (contract.source.kind !== 'praxis.stats') {
|
|
843
|
+
return undefined;
|
|
742
844
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
845
|
+
const filtersFromContract = this.toQueryFilterMap(contract.filters);
|
|
846
|
+
const combinedFilters = {
|
|
847
|
+
...filtersFromContract,
|
|
848
|
+
};
|
|
849
|
+
return {
|
|
850
|
+
sourceKind: 'praxis.stats',
|
|
851
|
+
statsOperation: contract.source.operation,
|
|
852
|
+
granularity: contract.source.options?.granularity,
|
|
853
|
+
fillGaps: contract.source.options?.fillGaps,
|
|
854
|
+
distributionMode: contract.source.options?.mode,
|
|
855
|
+
bucketSize: contract.source.options?.bucketSize,
|
|
856
|
+
bucketCount: contract.source.options?.bucketCount,
|
|
857
|
+
statsOrderBy: this.mapStatsOrderBy(contract.source.options?.orderBy),
|
|
858
|
+
statsPath: this.buildStatsPath(contract),
|
|
859
|
+
statsRequest: this.buildStatsRequest(contract, combinedFilters),
|
|
860
|
+
dimensions: contract.dimensions?.map((dimension) => dimension.field),
|
|
861
|
+
metrics: contract.metrics?.map((metric) => ({
|
|
862
|
+
field: metric.field,
|
|
863
|
+
aggregation: this.mapAggregation(metric.aggregation),
|
|
864
|
+
alias: metric.field,
|
|
865
|
+
})),
|
|
866
|
+
filters: Object.keys(combinedFilters).length ? combinedFilters : undefined,
|
|
867
|
+
sort: contract.sort?.map((item) => `${item.field}:${item.direction}`),
|
|
868
|
+
limit: contract.limit ?? contract.source.options?.limit,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
buildInteractions(contract) {
|
|
872
|
+
return {
|
|
873
|
+
pointClick: Boolean(contract.events?.pointClick || contract.events?.drillDown),
|
|
874
|
+
selection: Boolean(contract.events?.selectionChange),
|
|
875
|
+
drillDown: Boolean(contract.events?.drillDown),
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
buildTheme(contract) {
|
|
879
|
+
return {
|
|
880
|
+
palette: Array.isArray(contract.theme?.palette) ? contract.theme.palette : undefined,
|
|
881
|
+
legend: { visible: this.resolveToggle(contract.legend, true) },
|
|
882
|
+
tooltip: {
|
|
883
|
+
enabled: this.resolveToggle(contract.tooltip, true),
|
|
884
|
+
trigger: contract.kind === 'pie' || contract.kind === 'donut' || contract.kind === 'scatter' ? 'item' : 'axis',
|
|
885
|
+
},
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
buildMotion(motion) {
|
|
889
|
+
if (!motion) {
|
|
890
|
+
return undefined;
|
|
760
891
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
effect(() => {
|
|
766
|
-
const config = this.config();
|
|
767
|
-
const explicitData = this.data();
|
|
768
|
-
const nextSignature = explicitData === null || explicitData === undefined
|
|
769
|
-
? this.buildRemoteSignature(config)
|
|
770
|
-
: null;
|
|
771
|
-
if (nextSignature === this.previousRemoteSignature) {
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
this.previousRemoteSignature = nextSignature;
|
|
775
|
-
this.remoteResolvedData.set(null);
|
|
776
|
-
this.remoteRuntimeState.set('idle');
|
|
777
|
-
this.remoteTechnicalError.set(null);
|
|
778
|
-
});
|
|
779
|
-
effect(() => {
|
|
780
|
-
const nextState = this.loadState();
|
|
781
|
-
if (this.currentLoadState() !== nextState) {
|
|
782
|
-
this.currentLoadState.set(nextState);
|
|
783
|
-
this.loadStateChange.emit(nextState);
|
|
784
|
-
}
|
|
785
|
-
});
|
|
786
|
-
effect(() => {
|
|
787
|
-
const config = this.config();
|
|
788
|
-
const explicitData = this.data();
|
|
789
|
-
if (config.dataSource?.kind !== 'remote' || (explicitData !== null && explicitData !== undefined)) {
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
if (this.remoteRuntimeState() === 'loading' || this.remoteResolvedData() !== null) {
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
const event = {
|
|
796
|
-
chartId: config.id,
|
|
797
|
-
dataSource: config.dataSource,
|
|
798
|
-
query: config.dataSource.query,
|
|
799
|
-
};
|
|
800
|
-
this.queryRequest.emit(event);
|
|
801
|
-
this.remoteRuntimeState.set('loading');
|
|
802
|
-
this.remoteResolvedData.set([]);
|
|
803
|
-
this.remoteTechnicalError.set(null);
|
|
804
|
-
this.statsApi.execute(event, config)
|
|
805
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
806
|
-
.subscribe({
|
|
807
|
-
next: (rows) => {
|
|
808
|
-
this.remoteResolvedData.set(rows);
|
|
809
|
-
this.remoteRuntimeState.set('ready');
|
|
810
|
-
this.remoteTechnicalError.set(null);
|
|
811
|
-
},
|
|
812
|
-
error: (error) => {
|
|
813
|
-
this.remoteResolvedData.set([]);
|
|
814
|
-
this.remoteRuntimeState.set('error');
|
|
815
|
-
this.remoteTechnicalError.set(error instanceof Error && error.message.trim()
|
|
816
|
-
? error.message
|
|
817
|
-
: 'praxis.stats request failed');
|
|
818
|
-
},
|
|
819
|
-
});
|
|
820
|
-
});
|
|
821
|
-
effect(() => {
|
|
822
|
-
if (this.loadState() !== 'ready') {
|
|
823
|
-
this.engine.destroy();
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
const host = this.chartHost()?.nativeElement;
|
|
827
|
-
if (!host)
|
|
828
|
-
return;
|
|
829
|
-
this.engine.render(host, {
|
|
830
|
-
config: this.renderConfig(),
|
|
831
|
-
data: this.resolvedData(),
|
|
832
|
-
onPointClick: (event) => this.pointClick.emit(event),
|
|
833
|
-
});
|
|
834
|
-
this.ensureResizeObserver(host);
|
|
835
|
-
});
|
|
836
|
-
this.destroyRef.onDestroy(() => {
|
|
837
|
-
this.resizeObserver()?.disconnect();
|
|
838
|
-
this.engine.destroy();
|
|
839
|
-
});
|
|
892
|
+
return {
|
|
893
|
+
enabled: motion.enabled,
|
|
894
|
+
preset: motion.preset,
|
|
895
|
+
};
|
|
840
896
|
}
|
|
841
|
-
|
|
842
|
-
if (
|
|
843
|
-
return;
|
|
897
|
+
resolveOrientation(contract) {
|
|
898
|
+
if (contract.kind === 'horizontal-bar') {
|
|
899
|
+
return 'horizontal';
|
|
844
900
|
}
|
|
845
|
-
|
|
846
|
-
this.engine.resize();
|
|
847
|
-
});
|
|
848
|
-
observer.observe(host);
|
|
849
|
-
this.resizeObserver.set(observer);
|
|
901
|
+
return contract.orientation;
|
|
850
902
|
}
|
|
851
|
-
|
|
852
|
-
if (
|
|
853
|
-
return
|
|
903
|
+
resolveSeriesType(chartKind, seriesKind, index) {
|
|
904
|
+
if (chartKind === 'combo') {
|
|
905
|
+
return seriesKind ?? (index === 0 ? 'bar' : 'line');
|
|
854
906
|
}
|
|
855
|
-
return
|
|
856
|
-
id: config.id,
|
|
857
|
-
resourcePath: config.dataSource.resourcePath,
|
|
858
|
-
query: config.dataSource.query,
|
|
859
|
-
});
|
|
907
|
+
return chartKind === 'stacked-bar' ? 'bar' : chartKind;
|
|
860
908
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
<div class="praxis-chart-spinner" aria-hidden="true"></div>
|
|
873
|
-
@if (loadingLabel()) {
|
|
874
|
-
<div class="praxis-chart-state-title">{{ loadingLabel() }}</div>
|
|
875
|
-
}
|
|
876
|
-
</div>
|
|
877
|
-
} @else if (loadState() === 'error') {
|
|
878
|
-
<div class="praxis-chart-state praxis-chart-state-error">
|
|
879
|
-
@if (errorTitle()) {
|
|
880
|
-
<div class="praxis-chart-state-title">{{ errorTitle() }}</div>
|
|
881
|
-
}
|
|
882
|
-
@if (errorDescription()) {
|
|
883
|
-
<div class="praxis-chart-state-description">{{ errorDescription() }}</div>
|
|
884
|
-
}
|
|
885
|
-
</div>
|
|
886
|
-
} @else if (loadState() === 'empty') {
|
|
887
|
-
<div class="praxis-chart-state praxis-chart-state-empty">
|
|
888
|
-
@if (emptyTitle()) {
|
|
889
|
-
<div class="praxis-chart-state-title">{{ emptyTitle() }}</div>
|
|
890
|
-
}
|
|
891
|
-
@if (emptyDescription()) {
|
|
892
|
-
<div class="praxis-chart-state-description">{{ emptyDescription() }}</div>
|
|
893
|
-
}
|
|
894
|
-
</div>
|
|
895
|
-
} @else {
|
|
896
|
-
<div #chartHost class="praxis-chart-host"></div>
|
|
897
|
-
}
|
|
898
|
-
</section>
|
|
899
|
-
`, isInline: true, styles: [":host{display:block;min-width:0}.praxis-chart-shell{position:relative;width:100%;min-height:240px;border-radius:18px;overflow:hidden;background:radial-gradient(circle at top left,rgba(18,99,180,.12),transparent 38%),linear-gradient(180deg,#1263b408,#1263b400);border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:240px;display:grid;place-content:center;gap:10px;padding:24px;text-align:center}.praxis-chart-state-title{font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.praxis-chart-state-description{font-size:.925rem;color:var(--md-sys-color-on-surface-variant, #5a5d67);max-width:36rem}.praxis-chart-spinner{width:34px;height:34px;margin-inline:auto;border-radius:999px;border:3px solid rgba(18,99,180,.18);border-top-color:#1263b4d1;animation:praxis-chart-spin .8s linear infinite}@keyframes praxis-chart-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
900
|
-
}
|
|
901
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartComponent, decorators: [{
|
|
902
|
-
type: Component,
|
|
903
|
-
args: [{ selector: 'praxis-chart', standalone: true, imports: [CommonModule], providers: [
|
|
904
|
-
EChartsEngineAdapter,
|
|
905
|
-
{
|
|
906
|
-
provide: PRAXIS_CHART_ENGINE,
|
|
907
|
-
useExisting: EChartsEngineAdapter,
|
|
908
|
-
},
|
|
909
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
910
|
-
<section class="praxis-chart-shell" [style.height]="resolvedHeight()">
|
|
911
|
-
@if (loadState() === 'loading') {
|
|
912
|
-
<div class="praxis-chart-state praxis-chart-state-loading">
|
|
913
|
-
<div class="praxis-chart-spinner" aria-hidden="true"></div>
|
|
914
|
-
@if (loadingLabel()) {
|
|
915
|
-
<div class="praxis-chart-state-title">{{ loadingLabel() }}</div>
|
|
916
|
-
}
|
|
917
|
-
</div>
|
|
918
|
-
} @else if (loadState() === 'error') {
|
|
919
|
-
<div class="praxis-chart-state praxis-chart-state-error">
|
|
920
|
-
@if (errorTitle()) {
|
|
921
|
-
<div class="praxis-chart-state-title">{{ errorTitle() }}</div>
|
|
922
|
-
}
|
|
923
|
-
@if (errorDescription()) {
|
|
924
|
-
<div class="praxis-chart-state-description">{{ errorDescription() }}</div>
|
|
925
|
-
}
|
|
926
|
-
</div>
|
|
927
|
-
} @else if (loadState() === 'empty') {
|
|
928
|
-
<div class="praxis-chart-state praxis-chart-state-empty">
|
|
929
|
-
@if (emptyTitle()) {
|
|
930
|
-
<div class="praxis-chart-state-title">{{ emptyTitle() }}</div>
|
|
931
|
-
}
|
|
932
|
-
@if (emptyDescription()) {
|
|
933
|
-
<div class="praxis-chart-state-description">{{ emptyDescription() }}</div>
|
|
934
|
-
}
|
|
935
|
-
</div>
|
|
936
|
-
} @else {
|
|
937
|
-
<div #chartHost class="praxis-chart-host"></div>
|
|
938
|
-
}
|
|
939
|
-
</section>
|
|
940
|
-
`, styles: [":host{display:block;min-width:0}.praxis-chart-shell{position:relative;width:100%;min-height:240px;border-radius:18px;overflow:hidden;background:radial-gradient(circle at top left,rgba(18,99,180,.12),transparent 38%),linear-gradient(180deg,#1263b408,#1263b400);border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:240px;display:grid;place-content:center;gap:10px;padding:24px;text-align:center}.praxis-chart-state-title{font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.praxis-chart-state-description{font-size:.925rem;color:var(--md-sys-color-on-surface-variant, #5a5d67);max-width:36rem}.praxis-chart-spinner{width:34px;height:34px;margin-inline:auto;border-radius:999px;border:3px solid rgba(18,99,180,.18);border-top-color:#1263b4d1;animation:praxis-chart-spin .8s linear infinite}@keyframes praxis-chart-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
941
|
-
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }], chartHost: [{ type: i0.ViewChild, args: ['chartHost', { isSignal: true }] }] } });
|
|
942
|
-
|
|
943
|
-
const PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH = {
|
|
944
|
-
Jan: [
|
|
945
|
-
{ segment: 'Enterprise', total: 52000 },
|
|
946
|
-
{ segment: 'Mid-market', total: 38000 },
|
|
947
|
-
{ segment: 'SMB', total: 30000 },
|
|
948
|
-
],
|
|
949
|
-
Fev: [
|
|
950
|
-
{ segment: 'Enterprise', total: 61000 },
|
|
951
|
-
{ segment: 'Mid-market', total: 42000 },
|
|
952
|
-
{ segment: 'SMB', total: 35000 },
|
|
953
|
-
],
|
|
954
|
-
Mar: [
|
|
955
|
-
{ segment: 'Enterprise', total: 68000 },
|
|
956
|
-
{ segment: 'Mid-market', total: 47000 },
|
|
957
|
-
{ segment: 'SMB', total: 36000 },
|
|
958
|
-
],
|
|
959
|
-
Abr: [
|
|
960
|
-
{ segment: 'Enterprise', total: 64000 },
|
|
961
|
-
{ segment: 'Mid-market', total: 49500 },
|
|
962
|
-
{ segment: 'SMB', total: 36000 },
|
|
963
|
-
],
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
class PraxisChartDrilldownPanelComponent {
|
|
967
|
-
title = input('Detalhamento por segmento', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
968
|
-
selection = input(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
969
|
-
activeCategory = computed(() => {
|
|
970
|
-
const raw = this.selection()?.category;
|
|
971
|
-
return typeof raw === 'string' ? raw : null;
|
|
972
|
-
}, ...(ngDevMode ? [{ debugName: "activeCategory" }] : []));
|
|
973
|
-
detailData = computed(() => {
|
|
974
|
-
const category = this.activeCategory();
|
|
975
|
-
if (!category)
|
|
976
|
-
return [];
|
|
977
|
-
return PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH[category] ?? [];
|
|
978
|
-
}, ...(ngDevMode ? [{ debugName: "detailData" }] : []));
|
|
979
|
-
detailChartConfig = computed(() => {
|
|
980
|
-
const category = this.activeCategory();
|
|
909
|
+
shouldSmoothSeries(chartKind, seriesKind) {
|
|
910
|
+
if (chartKind === 'combo') {
|
|
911
|
+
return seriesKind === 'line' || seriesKind === 'area' ? true : undefined;
|
|
912
|
+
}
|
|
913
|
+
return chartKind === 'line' || chartKind === 'area' || chartKind === 'stacked-area' ? true : undefined;
|
|
914
|
+
}
|
|
915
|
+
mapTextValue(value) {
|
|
916
|
+
if (!value)
|
|
917
|
+
return undefined;
|
|
918
|
+
if (typeof value === 'string')
|
|
919
|
+
return value;
|
|
981
920
|
return {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
title: { text: category ? `Mix de receita em ${category}` : 'Aguardando selecao' },
|
|
985
|
-
subtitle: { text: category ? 'Drill-down local com JSON mockado' : 'Selecione um ponto no chart principal' },
|
|
986
|
-
height: 280,
|
|
987
|
-
series: [
|
|
988
|
-
{
|
|
989
|
-
id: 'segmentMix',
|
|
990
|
-
categoryField: 'segment',
|
|
991
|
-
metric: { field: 'total', aggregation: 'sum' },
|
|
992
|
-
labels: { visible: true },
|
|
993
|
-
},
|
|
994
|
-
],
|
|
995
|
-
emptyState: {
|
|
996
|
-
title: { text: 'Nenhum recorte selecionado' },
|
|
997
|
-
description: { text: 'O painel de drill-down usa mocks locais e reage ao pointClick do chart principal.' },
|
|
998
|
-
},
|
|
999
|
-
theme: {
|
|
1000
|
-
palette: ['#1263b4', '#2b8a3e', '#f08c00', '#7b61ff'],
|
|
1001
|
-
},
|
|
921
|
+
key: value.key,
|
|
922
|
+
text: value.fallback,
|
|
1002
923
|
};
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
<h3>{{ title() }}</h3>
|
|
1031
|
-
@if (activeCategory()) {
|
|
1032
|
-
<p class="drilldown-description">Recorte atual: {{ activeCategory() }}</p>
|
|
1033
|
-
} @else {
|
|
1034
|
-
<p class="drilldown-description">Clique em uma barra do chart principal para abrir o detalhamento local.</p>
|
|
924
|
+
}
|
|
925
|
+
toLabel(value) {
|
|
926
|
+
if (!value)
|
|
927
|
+
return undefined;
|
|
928
|
+
if (typeof value === 'string')
|
|
929
|
+
return value;
|
|
930
|
+
return value.fallback || value.key;
|
|
931
|
+
}
|
|
932
|
+
resolveToggle(value, defaultValue = false) {
|
|
933
|
+
if (typeof value === 'boolean')
|
|
934
|
+
return value;
|
|
935
|
+
if (typeof value === 'object' && value)
|
|
936
|
+
return value.enabled;
|
|
937
|
+
return defaultValue;
|
|
938
|
+
}
|
|
939
|
+
mapAggregation(aggregation) {
|
|
940
|
+
switch (aggregation) {
|
|
941
|
+
case 'avg':
|
|
942
|
+
case 'min':
|
|
943
|
+
case 'max':
|
|
944
|
+
case 'count':
|
|
945
|
+
case 'sum':
|
|
946
|
+
return aggregation;
|
|
947
|
+
case undefined:
|
|
948
|
+
return undefined;
|
|
949
|
+
default:
|
|
950
|
+
throw new Error(`x-ui.chart aggregation "${aggregation}" is not yet implemented in @praxisui/charts.`);
|
|
1035
951
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
{
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
}
|
|
1118
|
-
{
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
};
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
952
|
+
}
|
|
953
|
+
toQueryFilterMap(filters) {
|
|
954
|
+
if (!filters?.length)
|
|
955
|
+
return undefined;
|
|
956
|
+
return filters.reduce((acc, filter) => {
|
|
957
|
+
if (filter.value !== undefined) {
|
|
958
|
+
acc[filter.field] = filter.value;
|
|
959
|
+
return acc;
|
|
960
|
+
}
|
|
961
|
+
if (filter.values !== undefined) {
|
|
962
|
+
acc[filter.field] = filter.values;
|
|
963
|
+
}
|
|
964
|
+
return acc;
|
|
965
|
+
}, {});
|
|
966
|
+
}
|
|
967
|
+
buildStatsPath(contract) {
|
|
968
|
+
return `${contract.source.resource.replace(/\/+$/, '')}/stats/${contract.source.operation}`;
|
|
969
|
+
}
|
|
970
|
+
buildStatsRequest(contract, filters) {
|
|
971
|
+
const field = this.resolveStatsField(contract);
|
|
972
|
+
const metrics = this.buildStatsMetricRequests(contract);
|
|
973
|
+
const metric = metrics[0];
|
|
974
|
+
const limit = contract.limit ?? contract.source.options?.limit;
|
|
975
|
+
const orderBy = this.mapStatsOrderByToBackend(contract.source.options?.orderBy);
|
|
976
|
+
const requestMetrics = metrics.length > 1 ? metrics : undefined;
|
|
977
|
+
switch (contract.source.operation) {
|
|
978
|
+
case 'group-by':
|
|
979
|
+
return {
|
|
980
|
+
filter: filters,
|
|
981
|
+
field,
|
|
982
|
+
metric,
|
|
983
|
+
metrics: requestMetrics,
|
|
984
|
+
limit,
|
|
985
|
+
orderBy,
|
|
986
|
+
};
|
|
987
|
+
case 'timeseries':
|
|
988
|
+
return {
|
|
989
|
+
filter: filters,
|
|
990
|
+
field,
|
|
991
|
+
granularity: this.mapStatsGranularityToBackend(contract.source.options?.granularity),
|
|
992
|
+
metric,
|
|
993
|
+
metrics: requestMetrics,
|
|
994
|
+
fillGaps: contract.source.options?.fillGaps,
|
|
995
|
+
};
|
|
996
|
+
case 'distribution':
|
|
997
|
+
return {
|
|
998
|
+
filter: filters,
|
|
999
|
+
field,
|
|
1000
|
+
mode: this.mapDistributionModeToBackend(contract.source.options?.mode),
|
|
1001
|
+
metric,
|
|
1002
|
+
bucketSize: contract.source.options?.bucketSize,
|
|
1003
|
+
bucketCount: contract.source.options?.bucketCount,
|
|
1004
|
+
limit,
|
|
1005
|
+
orderBy,
|
|
1006
|
+
};
|
|
1007
|
+
default:
|
|
1008
|
+
throw new Error(`x-ui.chart source.operation "${contract.source.operation}" is not supported in @praxisui/charts.`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
resolveStatsField(contract) {
|
|
1012
|
+
const dimensionField = contract.dimensions?.[0]?.field?.trim();
|
|
1013
|
+
if (dimensionField) {
|
|
1014
|
+
return dimensionField;
|
|
1015
|
+
}
|
|
1016
|
+
throw new Error('x-ui.chart requires dimensions[0].field to derive the canonical praxis.stats request.');
|
|
1017
|
+
}
|
|
1018
|
+
buildStatsMetricRequest(contract) {
|
|
1019
|
+
const metric = contract.metrics?.[0];
|
|
1020
|
+
if (!metric) {
|
|
1021
|
+
throw new Error('x-ui.chart requires metrics[0] to derive the canonical praxis.stats request.');
|
|
1022
|
+
}
|
|
1023
|
+
const operation = this.mapStatsMetricOperation(metric.aggregation);
|
|
1024
|
+
return {
|
|
1025
|
+
operation,
|
|
1026
|
+
field: operation === 'COUNT' ? undefined : metric.field,
|
|
1027
|
+
alias: metric.field,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
buildStatsMetricRequests(contract) {
|
|
1031
|
+
if (!contract.metrics?.length) {
|
|
1032
|
+
throw new Error('x-ui.chart requires at least one metric to derive the canonical praxis.stats request.');
|
|
1033
|
+
}
|
|
1034
|
+
return contract.metrics.map((metric) => {
|
|
1035
|
+
const operation = this.mapStatsMetricOperation(metric.aggregation);
|
|
1036
|
+
return {
|
|
1037
|
+
operation,
|
|
1038
|
+
field: operation === 'COUNT' ? undefined : metric.field,
|
|
1039
|
+
alias: metric.field,
|
|
1040
|
+
};
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
mapStatsOrderBy(value) {
|
|
1044
|
+
if (!value)
|
|
1045
|
+
return undefined;
|
|
1046
|
+
switch (value) {
|
|
1047
|
+
case 'key-asc':
|
|
1048
|
+
case 'key-desc':
|
|
1049
|
+
case 'value-asc':
|
|
1050
|
+
case 'value-desc':
|
|
1051
|
+
return value;
|
|
1052
|
+
default:
|
|
1053
|
+
throw new Error(`x-ui.chart source.options.orderBy "${value}" is not supported in @praxisui/charts.`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
mapStatsOrderByToBackend(value) {
|
|
1057
|
+
switch (value) {
|
|
1058
|
+
case 'key-asc':
|
|
1059
|
+
return 'KEY_ASC';
|
|
1060
|
+
case 'key-desc':
|
|
1061
|
+
return 'KEY_DESC';
|
|
1062
|
+
case 'value-asc':
|
|
1063
|
+
return 'VALUE_ASC';
|
|
1064
|
+
case 'value-desc':
|
|
1065
|
+
return 'VALUE_DESC';
|
|
1066
|
+
case undefined:
|
|
1067
|
+
return undefined;
|
|
1068
|
+
default:
|
|
1069
|
+
throw new Error(`x-ui.chart source.options.orderBy "${value}" is not supported in @praxisui/charts.`);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
mapStatsGranularityToBackend(value) {
|
|
1073
|
+
switch (value) {
|
|
1074
|
+
case 'hour':
|
|
1075
|
+
return 'HOUR';
|
|
1076
|
+
case 'day':
|
|
1077
|
+
return 'DAY';
|
|
1078
|
+
case 'week':
|
|
1079
|
+
return 'WEEK';
|
|
1080
|
+
case 'month':
|
|
1081
|
+
case undefined:
|
|
1082
|
+
return 'MONTH';
|
|
1083
|
+
case 'quarter':
|
|
1084
|
+
return 'QUARTER';
|
|
1085
|
+
case 'year':
|
|
1086
|
+
return 'YEAR';
|
|
1087
|
+
default:
|
|
1088
|
+
throw new Error(`x-ui.chart source.options.granularity "${value}" is not supported in @praxisui/charts.`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
mapDistributionModeToBackend(value) {
|
|
1092
|
+
switch (value) {
|
|
1093
|
+
case 'histogram':
|
|
1094
|
+
return 'HISTOGRAM';
|
|
1095
|
+
case 'terms':
|
|
1096
|
+
case undefined:
|
|
1097
|
+
return 'TERMS';
|
|
1098
|
+
default:
|
|
1099
|
+
throw new Error(`x-ui.chart source.options.mode "${value}" is not supported in @praxisui/charts.`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
mapStatsMetricOperation(aggregation) {
|
|
1103
|
+
switch (aggregation) {
|
|
1104
|
+
case undefined:
|
|
1105
|
+
case 'count':
|
|
1106
|
+
return 'COUNT';
|
|
1107
|
+
case 'sum':
|
|
1108
|
+
return 'SUM';
|
|
1109
|
+
case 'avg':
|
|
1110
|
+
return 'AVG';
|
|
1111
|
+
case 'min':
|
|
1112
|
+
return 'MIN';
|
|
1113
|
+
case 'max':
|
|
1114
|
+
return 'MAX';
|
|
1115
|
+
default:
|
|
1116
|
+
throw new Error(`x-ui.chart aggregation "${aggregation}" is not yet implemented in @praxisui/charts.`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, deps: [{ token: ChartContractNormalizerService }, { token: ChartContractValidationService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1120
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, providedIn: 'root' });
|
|
1134
1121
|
}
|
|
1122
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, decorators: [{
|
|
1123
|
+
type: Injectable,
|
|
1124
|
+
args: [{ providedIn: 'root' }]
|
|
1125
|
+
}], ctorParameters: () => [{ type: ChartContractNormalizerService }, { type: ChartContractValidationService }] });
|
|
1135
1126
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1127
|
+
class PraxisChartStatsApiService {
|
|
1128
|
+
http;
|
|
1129
|
+
apiUrl;
|
|
1130
|
+
constructor(http, apiUrl) {
|
|
1131
|
+
this.http = http;
|
|
1132
|
+
this.apiUrl = apiUrl;
|
|
1133
|
+
}
|
|
1134
|
+
execute(event, config) {
|
|
1135
|
+
const query = event.query;
|
|
1136
|
+
const statsRequest = query?.statsRequest;
|
|
1137
|
+
if (!query?.statsPath || !statsRequest) {
|
|
1138
|
+
return throwError(() => new Error('PraxisChartStatsApiService requires query.statsPath and query.statsRequest for praxis.stats execution.'));
|
|
1139
|
+
}
|
|
1140
|
+
const categoryField = this.resolveCategoryField(config, statsRequest);
|
|
1141
|
+
const url = this.buildStatsUrl(query.statsPath);
|
|
1142
|
+
return this.http
|
|
1143
|
+
.post(url, statsRequest)
|
|
1144
|
+
.pipe(map((response) => this.toChartRows(response?.data, statsRequest, categoryField, config)), catchError((error) => this.handleHttpError(error)));
|
|
1145
|
+
}
|
|
1146
|
+
toChartRows(response, request, categoryField, config) {
|
|
1147
|
+
if (!response) {
|
|
1148
|
+
return [];
|
|
1149
|
+
}
|
|
1150
|
+
const metricBindings = this.resolveMetricBindings(config, request, response);
|
|
1151
|
+
if ('points' in response) {
|
|
1152
|
+
return response.points.map((point) => {
|
|
1153
|
+
const category = point.label ?? point.start ?? point.end ?? '';
|
|
1154
|
+
return {
|
|
1155
|
+
[categoryField]: category,
|
|
1156
|
+
...this.projectMetricValues(metricBindings, point.values, point.value, point.count),
|
|
1157
|
+
key: point.start ?? point.label ?? point.end ?? category,
|
|
1158
|
+
label: point.label ?? category,
|
|
1159
|
+
value: point.value ?? null,
|
|
1160
|
+
count: point.count ?? null,
|
|
1161
|
+
start: point.start ?? null,
|
|
1162
|
+
end: point.end ?? null,
|
|
1163
|
+
granularity: response.granularity ?? null,
|
|
1164
|
+
};
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
if ('buckets' in response) {
|
|
1168
|
+
return response.buckets.map((bucket) => {
|
|
1169
|
+
const category = this.resolveBucketCategory(bucket);
|
|
1170
|
+
return {
|
|
1171
|
+
[categoryField]: category,
|
|
1172
|
+
...this.projectMetricValues(metricBindings, bucket.values, bucket.value, bucket.count),
|
|
1173
|
+
key: bucket.key ?? null,
|
|
1174
|
+
label: bucket.label ?? category,
|
|
1175
|
+
value: bucket.value ?? null,
|
|
1176
|
+
count: bucket.count ?? null,
|
|
1177
|
+
from: bucket.from ?? null,
|
|
1178
|
+
to: bucket.to ?? null,
|
|
1179
|
+
mode: 'mode' in response ? response.mode ?? null : null,
|
|
1180
|
+
requestMode: 'mode' in request ? request.mode : null,
|
|
1181
|
+
};
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
return [];
|
|
1185
|
+
}
|
|
1186
|
+
resolveBucketCategory(bucket) {
|
|
1187
|
+
if (bucket.label !== null && bucket.label !== undefined && bucket.label !== '') {
|
|
1188
|
+
return String(bucket.label);
|
|
1189
|
+
}
|
|
1190
|
+
if (bucket.key !== null && bucket.key !== undefined && bucket.key !== '') {
|
|
1191
|
+
return String(bucket.key);
|
|
1192
|
+
}
|
|
1193
|
+
if (bucket.from !== null || bucket.to !== null) {
|
|
1194
|
+
return `${bucket.from ?? ''} - ${bucket.to ?? ''}`.trim();
|
|
1195
|
+
}
|
|
1196
|
+
return '';
|
|
1197
|
+
}
|
|
1198
|
+
resolveMetricValue(value, count) {
|
|
1199
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1200
|
+
return value;
|
|
1201
|
+
}
|
|
1202
|
+
if (typeof count === 'number' && Number.isFinite(count)) {
|
|
1203
|
+
return count;
|
|
1204
|
+
}
|
|
1205
|
+
return 0;
|
|
1206
|
+
}
|
|
1207
|
+
resolveCategoryField(config, request) {
|
|
1208
|
+
const firstSeries = config.series[0];
|
|
1209
|
+
return (firstSeries?.categoryField
|
|
1210
|
+
|| config.axes?.x?.field
|
|
1211
|
+
|| request.field
|
|
1212
|
+
|| 'category');
|
|
1213
|
+
}
|
|
1214
|
+
resolveMetricBindings(config, request, response) {
|
|
1215
|
+
const queryMetrics = config.dataSource?.kind === 'remote'
|
|
1216
|
+
? (config.dataSource.query?.metrics ?? [])
|
|
1217
|
+
: [];
|
|
1218
|
+
if (queryMetrics.length) {
|
|
1219
|
+
return queryMetrics.map((metric, index) => ({
|
|
1220
|
+
field: metric.field || `value${index + 1}`,
|
|
1221
|
+
alias: metric.alias || metric.field || `value${index + 1}`,
|
|
1222
|
+
}));
|
|
1223
|
+
}
|
|
1224
|
+
const responseMetrics = 'metrics' in response ? response.metrics : undefined;
|
|
1225
|
+
if (responseMetrics?.length) {
|
|
1226
|
+
return responseMetrics.map((metric, index) => ({
|
|
1227
|
+
field: metric.alias || metric.field || `value${index + 1}`,
|
|
1228
|
+
alias: metric.alias || metric.field || `value${index + 1}`,
|
|
1229
|
+
}));
|
|
1230
|
+
}
|
|
1231
|
+
const requestMetrics = 'metrics' in request ? request.metrics : undefined;
|
|
1232
|
+
if (requestMetrics?.length) {
|
|
1233
|
+
return requestMetrics.map((metric, index) => ({
|
|
1234
|
+
field: metric.alias || metric.field || `value${index + 1}`,
|
|
1235
|
+
alias: metric.alias || metric.field || `value${index + 1}`,
|
|
1236
|
+
}));
|
|
1237
|
+
}
|
|
1238
|
+
const metric = ('metric' in response && response.metric) || ('metric' in request ? request.metric : undefined);
|
|
1239
|
+
return [
|
|
1240
|
+
{
|
|
1241
|
+
field: metric?.alias || metric?.field || config.series[0]?.metric?.field || 'value',
|
|
1242
|
+
alias: metric?.alias || metric?.field || config.series[0]?.metric?.field || 'value',
|
|
1243
|
+
},
|
|
1244
|
+
];
|
|
1245
|
+
}
|
|
1246
|
+
projectMetricValues(bindings, values, primaryValue, count) {
|
|
1247
|
+
return bindings.reduce((acc, binding, index) => {
|
|
1248
|
+
const rawValue = values?.[binding.alias];
|
|
1249
|
+
acc[binding.field] = index === 0
|
|
1250
|
+
? this.resolveMetricValue(rawValue ?? primaryValue, count)
|
|
1251
|
+
: this.resolveMetricValue(rawValue, null);
|
|
1252
|
+
return acc;
|
|
1253
|
+
}, {});
|
|
1254
|
+
}
|
|
1255
|
+
buildStatsUrl(statsPath) {
|
|
1256
|
+
const base = this.buildDefaultApiBase();
|
|
1257
|
+
const normalizedStatsPath = this.normalizePath(statsPath);
|
|
1258
|
+
const resolvedStatsPath = this.shouldStripLeadingApiSegment(base)
|
|
1259
|
+
? this.stripLeadingApiSegment(normalizedStatsPath)
|
|
1260
|
+
: normalizedStatsPath;
|
|
1261
|
+
return `${base}/${resolvedStatsPath}`;
|
|
1262
|
+
}
|
|
1263
|
+
buildDefaultApiBase() {
|
|
1264
|
+
const entry = this.apiUrl?.default ?? {};
|
|
1265
|
+
return buildApiUrl(entry).replace(/\/+$/, '');
|
|
1266
|
+
}
|
|
1267
|
+
normalizePath(value) {
|
|
1268
|
+
return String(value || '').trim().replace(/^\/+|\/+$/g, '');
|
|
1269
|
+
}
|
|
1270
|
+
stripLeadingApiSegment(value) {
|
|
1271
|
+
return value.replace(/^api\/+/i, '');
|
|
1272
|
+
}
|
|
1273
|
+
shouldStripLeadingApiSegment(base) {
|
|
1274
|
+
return /\/api$/i.test(base);
|
|
1275
|
+
}
|
|
1276
|
+
handleHttpError(error) {
|
|
1277
|
+
if (error instanceof HttpErrorResponse) {
|
|
1278
|
+
const detail = typeof error.message === 'string' && error.message.trim()
|
|
1279
|
+
? error.message
|
|
1280
|
+
: `HTTP ${error.status || 0}`;
|
|
1281
|
+
return throwError(() => new Error(detail));
|
|
1282
|
+
}
|
|
1283
|
+
return throwError(() => error instanceof Error ? error : new Error('Unexpected failure during praxis.stats execution.'));
|
|
1284
|
+
}
|
|
1285
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStatsApiService, deps: [{ token: i1.HttpClient }, { token: API_URL }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1286
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStatsApiService, providedIn: 'root' });
|
|
1169
1287
|
}
|
|
1288
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStatsApiService, decorators: [{
|
|
1289
|
+
type: Injectable,
|
|
1290
|
+
args: [{ providedIn: 'root' }]
|
|
1291
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
|
|
1292
|
+
type: Inject,
|
|
1293
|
+
args: [API_URL]
|
|
1294
|
+
}] }] });
|
|
1170
1295
|
|
|
1171
|
-
const
|
|
1172
|
-
id: 'praxis-chart-state-probe',
|
|
1173
|
-
componentType: 'praxis-chart-state-probe',
|
|
1174
|
-
displayName: 'Praxis Chart State Probe',
|
|
1175
|
-
selector: 'praxis-chart-state-probe',
|
|
1176
|
-
component: PraxisChartStateProbeComponent,
|
|
1177
|
-
friendlyName: 'Praxis Chart State Probe',
|
|
1178
|
-
description: 'Diagnostic widget used to inspect chart events and runtime payloads during local validation.',
|
|
1179
|
-
icon: 'monitoring',
|
|
1180
|
-
tags: ['chart', 'probe', 'debug', 'widget'],
|
|
1181
|
-
lib: '@praxisui/charts',
|
|
1182
|
-
inputs: [
|
|
1183
|
-
{
|
|
1184
|
-
name: 'title',
|
|
1185
|
-
type: 'string',
|
|
1186
|
-
description: 'Probe panel title.',
|
|
1187
|
-
},
|
|
1188
|
-
{
|
|
1189
|
-
name: 'value',
|
|
1190
|
-
type: 'unknown',
|
|
1191
|
-
description: 'Payload rendered as formatted JSON.',
|
|
1192
|
-
},
|
|
1193
|
-
],
|
|
1194
|
-
};
|
|
1195
|
-
function providePraxisChartStateProbeMetadata() {
|
|
1196
|
-
return {
|
|
1197
|
-
provide: ENVIRONMENT_INITIALIZER,
|
|
1198
|
-
multi: true,
|
|
1199
|
-
useFactory: (registry) => () => {
|
|
1200
|
-
registry.register(PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA);
|
|
1201
|
-
},
|
|
1202
|
-
deps: [ComponentMetadataRegistry],
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1296
|
+
const PRAXIS_CHART_ENGINE = new InjectionToken('PRAXIS_CHART_ENGINE');
|
|
1205
1297
|
|
|
1206
|
-
class
|
|
1298
|
+
class PraxisChartComponent {
|
|
1207
1299
|
config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
1208
1300
|
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1209
|
-
|
|
1301
|
+
chartDocument = input(null, ...(ngDevMode ? [{ debugName: "chartDocument" }] : []));
|
|
1302
|
+
filterCriteria = input(null, ...(ngDevMode ? [{ debugName: "filterCriteria" }] : []));
|
|
1303
|
+
enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
1304
|
+
availableResources = input([], ...(ngDevMode ? [{ debugName: "availableResources" }] : []));
|
|
1305
|
+
availableFields = input([], ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
|
|
1306
|
+
availableTargets = input([], ...(ngDevMode ? [{ debugName: "availableTargets" }] : []));
|
|
1210
1307
|
pointClick = output();
|
|
1211
1308
|
queryRequest = output();
|
|
1212
1309
|
loadStateChange = output();
|
|
1310
|
+
chartDocumentApplied = output();
|
|
1311
|
+
chartDocumentSaved = output();
|
|
1312
|
+
chartHost = viewChild('chartHost', ...(ngDevMode ? [{ debugName: "chartHost" }] : []));
|
|
1313
|
+
hostElement = inject(ElementRef);
|
|
1314
|
+
engine = inject(PRAXIS_CHART_ENGINE);
|
|
1315
|
+
transformer = inject(PraxisChartDataTransformerService);
|
|
1316
|
+
canonicalMapper = inject(PraxisChartCanonicalContractMapperService);
|
|
1213
1317
|
statsApi = inject(PraxisChartStatsApiService);
|
|
1318
|
+
i18n = inject(PraxisI18nService);
|
|
1319
|
+
settingsPanel = inject(SETTINGS_PANEL_BRIDGE, { optional: true });
|
|
1214
1320
|
destroyRef = inject(DestroyRef);
|
|
1215
|
-
|
|
1321
|
+
resizeObserver = signal(null, ...(ngDevMode ? [{ debugName: "resizeObserver" }] : []));
|
|
1322
|
+
shellObserver = signal(null, ...(ngDevMode ? [{ debugName: "shellObserver" }] : []));
|
|
1323
|
+
currentLoadState = signal('idle', ...(ngDevMode ? [{ debugName: "currentLoadState" }] : []));
|
|
1324
|
+
remoteResolvedData = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedData" }] : []));
|
|
1216
1325
|
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
|
|
1217
1326
|
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
|
|
1327
|
+
runtimeChartDocument = signal(null, ...(ngDevMode ? [{ debugName: "runtimeChartDocument" }] : []));
|
|
1328
|
+
mappedRuntimeConfig = signal(null, ...(ngDevMode ? [{ debugName: "mappedRuntimeConfig" }] : []));
|
|
1329
|
+
chartDocumentMappingError = signal(null, ...(ngDevMode ? [{ debugName: "chartDocumentMappingError" }] : []));
|
|
1330
|
+
fillContainerHeight = signal(false, ...(ngDevMode ? [{ debugName: "fillContainerHeight" }] : []));
|
|
1218
1331
|
previousRemoteSignature = null;
|
|
1219
|
-
|
|
1332
|
+
previousDocumentSignature = null;
|
|
1333
|
+
editorSessionSubscriptions = [];
|
|
1334
|
+
effectiveConfig = computed(() => {
|
|
1335
|
+
const base = this.mappedRuntimeConfig() ?? this.config();
|
|
1336
|
+
const runtimeFilters = normalizeFilterCriteria(this.filterCriteria());
|
|
1337
|
+
if (!runtimeFilters || base.dataSource?.kind !== 'remote' || !base.dataSource.query) {
|
|
1338
|
+
return base;
|
|
1339
|
+
}
|
|
1340
|
+
return mergeRemoteFilterCriteria(base, runtimeFilters);
|
|
1341
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveConfig" }] : []));
|
|
1342
|
+
resolvedData = computed(() => {
|
|
1343
|
+
const inputData = this.data();
|
|
1344
|
+
if (inputData !== null && inputData !== undefined) {
|
|
1345
|
+
return inputData;
|
|
1346
|
+
}
|
|
1347
|
+
const remoteData = this.remoteResolvedData();
|
|
1348
|
+
if (remoteData !== null) {
|
|
1349
|
+
return remoteData;
|
|
1350
|
+
}
|
|
1351
|
+
const dataSource = this.effectiveConfig().dataSource;
|
|
1352
|
+
if (dataSource?.kind === 'local') {
|
|
1353
|
+
return dataSource.items ?? [];
|
|
1354
|
+
}
|
|
1355
|
+
return [];
|
|
1356
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedData" }] : []));
|
|
1357
|
+
resolvedHeight = computed(() => {
|
|
1358
|
+
if (this.fillContainerHeight()) {
|
|
1359
|
+
return '100%';
|
|
1360
|
+
}
|
|
1361
|
+
const value = this.effectiveConfig().height;
|
|
1362
|
+
if (typeof value === 'number')
|
|
1363
|
+
return `${value}px`;
|
|
1364
|
+
return value || 'var(--praxis-chart-runtime-height, 320px)';
|
|
1365
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedHeight" }] : []));
|
|
1366
|
+
renderConfig = computed(() => {
|
|
1367
|
+
const config = this.effectiveConfig();
|
|
1220
1368
|
const explicitData = this.data();
|
|
1369
|
+
const remoteState = this.remoteRuntimeState();
|
|
1370
|
+
const remoteData = this.remoteResolvedData();
|
|
1221
1371
|
if (explicitData !== null && explicitData !== undefined) {
|
|
1222
|
-
return
|
|
1372
|
+
return config;
|
|
1223
1373
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1374
|
+
if (config.dataSource?.kind !== 'remote') {
|
|
1375
|
+
return config;
|
|
1376
|
+
}
|
|
1377
|
+
if (remoteState === 'loading') {
|
|
1378
|
+
return {
|
|
1379
|
+
...config,
|
|
1380
|
+
preferredLoadState: 'loading',
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
if (remoteState === 'error') {
|
|
1384
|
+
return {
|
|
1385
|
+
...config,
|
|
1386
|
+
preferredLoadState: 'error',
|
|
1387
|
+
state: config.state?.error
|
|
1388
|
+
? {
|
|
1389
|
+
...config.state,
|
|
1390
|
+
error: {
|
|
1391
|
+
...config.state.error,
|
|
1392
|
+
technicalDetails: this.remoteTechnicalError() ?? config.state.error.technicalDetails,
|
|
1393
|
+
},
|
|
1394
|
+
}
|
|
1395
|
+
: config.state,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
if (remoteData !== null) {
|
|
1399
|
+
return {
|
|
1400
|
+
...config,
|
|
1401
|
+
preferredLoadState: undefined,
|
|
1402
|
+
series: config.series.map((series) => ({
|
|
1403
|
+
...series,
|
|
1404
|
+
metric: {
|
|
1405
|
+
...series.metric,
|
|
1406
|
+
aggregation: 'sum',
|
|
1407
|
+
field: series.metric?.field || 'value',
|
|
1408
|
+
},
|
|
1409
|
+
})),
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
return config;
|
|
1413
|
+
}, ...(ngDevMode ? [{ debugName: "renderConfig" }] : []));
|
|
1414
|
+
loadingLabel = computed(() => this.i18n.resolve(this.renderConfig().state?.loadingLabel), ...(ngDevMode ? [{ debugName: "loadingLabel" }] : []));
|
|
1415
|
+
emptyTitle = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.title), ...(ngDevMode ? [{ debugName: "emptyTitle" }] : []));
|
|
1416
|
+
emptyDescription = computed(() => this.i18n.resolve(this.renderConfig().emptyState?.description), ...(ngDevMode ? [{ debugName: "emptyDescription" }] : []));
|
|
1417
|
+
errorTitle = computed(() => this.chartDocumentMappingError()
|
|
1418
|
+
? this.i18n.resolve({
|
|
1419
|
+
key: 'praxis.charts.runtime.invalidDocumentTitle',
|
|
1420
|
+
text: 'Chart configuration is invalid',
|
|
1421
|
+
})
|
|
1422
|
+
: this.i18n.resolve(this.renderConfig().state?.error?.title), ...(ngDevMode ? [{ debugName: "errorTitle" }] : []));
|
|
1423
|
+
errorDescription = computed(() => this.chartDocumentMappingError()
|
|
1424
|
+
? this.i18n.resolve({
|
|
1425
|
+
key: 'praxis.charts.runtime.invalidDocumentDescription',
|
|
1426
|
+
text: 'The canonical chart document could not be mapped to the current Praxis chart runtime. Review the chart contract before continuing.',
|
|
1427
|
+
})
|
|
1428
|
+
: this.i18n.resolve(this.renderConfig().state?.error?.description), ...(ngDevMode ? [{ debugName: "errorDescription" }] : []));
|
|
1429
|
+
editChartLabel = computed(() => this.i18n.resolve({ key: 'praxis.charts.runtime.editChart', text: 'Edit chart settings' }), ...(ngDevMode ? [{ debugName: "editChartLabel" }] : []));
|
|
1430
|
+
loadState = computed(() => {
|
|
1431
|
+
if (this.chartDocumentMappingError()) {
|
|
1432
|
+
return 'error';
|
|
1433
|
+
}
|
|
1434
|
+
const config = this.renderConfig();
|
|
1435
|
+
if (config.preferredLoadState === 'loading')
|
|
1436
|
+
return 'loading';
|
|
1437
|
+
if (config.preferredLoadState === 'error')
|
|
1438
|
+
return 'error';
|
|
1439
|
+
const dataSource = config.dataSource;
|
|
1440
|
+
const explicitData = this.data();
|
|
1441
|
+
if (dataSource?.kind === 'remote' && (explicitData === null || explicitData === undefined)) {
|
|
1442
|
+
return this.remoteRuntimeState() === 'ready' ? 'ready' : 'loading';
|
|
1443
|
+
}
|
|
1444
|
+
const transformed = this.transformer.transform(config, this.resolvedData());
|
|
1445
|
+
return transformed.hasData ? 'ready' : 'empty';
|
|
1446
|
+
}, ...(ngDevMode ? [{ debugName: "loadState" }] : []));
|
|
1236
1447
|
constructor() {
|
|
1448
|
+
afterNextRender(() => {
|
|
1449
|
+
this.observeShellSizingContext();
|
|
1450
|
+
});
|
|
1237
1451
|
effect(() => {
|
|
1238
|
-
const
|
|
1452
|
+
const document = this.chartDocument();
|
|
1453
|
+
const signature = document ? JSON.stringify(document) : null;
|
|
1454
|
+
if (signature === this.previousDocumentSignature) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
this.previousDocumentSignature = signature;
|
|
1458
|
+
this.runtimeChartDocument.set(document ? structuredClone(document) : null);
|
|
1459
|
+
});
|
|
1460
|
+
effect(() => {
|
|
1461
|
+
const runtimeDocument = this.runtimeChartDocument();
|
|
1462
|
+
if (!runtimeDocument) {
|
|
1463
|
+
this.mappedRuntimeConfig.set(null);
|
|
1464
|
+
this.chartDocumentMappingError.set(null);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
try {
|
|
1468
|
+
this.mappedRuntimeConfig.set(this.canonicalMapper.toPraxisChartConfig(runtimeDocument));
|
|
1469
|
+
this.chartDocumentMappingError.set(null);
|
|
1470
|
+
}
|
|
1471
|
+
catch (error) {
|
|
1472
|
+
this.mappedRuntimeConfig.set(null);
|
|
1473
|
+
this.chartDocumentMappingError.set(error instanceof Error && error.message.trim()
|
|
1474
|
+
? error.message
|
|
1475
|
+
: 'Invalid x-ui.chart document for Praxis chart runtime.');
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
effect(() => {
|
|
1479
|
+
const config = this.effectiveConfig();
|
|
1239
1480
|
const explicitData = this.data();
|
|
1240
1481
|
const nextSignature = explicitData === null || explicitData === undefined
|
|
1241
|
-
? buildRemoteSignature(config)
|
|
1482
|
+
? this.buildRemoteSignature(config)
|
|
1242
1483
|
: null;
|
|
1243
1484
|
if (nextSignature === this.previousRemoteSignature) {
|
|
1244
1485
|
return;
|
|
1245
1486
|
}
|
|
1246
1487
|
this.previousRemoteSignature = nextSignature;
|
|
1247
|
-
this.
|
|
1488
|
+
this.remoteResolvedData.set(null);
|
|
1248
1489
|
this.remoteRuntimeState.set('idle');
|
|
1249
1490
|
this.remoteTechnicalError.set(null);
|
|
1250
1491
|
});
|
|
1251
1492
|
effect(() => {
|
|
1252
|
-
const
|
|
1493
|
+
const nextState = this.loadState();
|
|
1494
|
+
if (this.currentLoadState() !== nextState) {
|
|
1495
|
+
this.currentLoadState.set(nextState);
|
|
1496
|
+
this.loadStateChange.emit(nextState);
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
effect(() => {
|
|
1500
|
+
const effectiveConfig = this.effectiveConfig();
|
|
1253
1501
|
const explicitData = this.data();
|
|
1254
|
-
if (
|
|
1502
|
+
if (effectiveConfig.dataSource?.kind !== 'remote' || (explicitData !== null && explicitData !== undefined)) {
|
|
1255
1503
|
return;
|
|
1256
1504
|
}
|
|
1257
|
-
if (
|
|
1505
|
+
if (this.remoteRuntimeState() === 'loading' || this.remoteResolvedData() !== null) {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
const event = {
|
|
1509
|
+
chartId: effectiveConfig.id,
|
|
1510
|
+
dataSource: effectiveConfig.dataSource,
|
|
1511
|
+
query: effectiveConfig.dataSource.query,
|
|
1512
|
+
};
|
|
1513
|
+
this.queryRequest.emit(event);
|
|
1514
|
+
this.remoteRuntimeState.set('loading');
|
|
1515
|
+
this.remoteResolvedData.set([]);
|
|
1516
|
+
this.remoteTechnicalError.set(null);
|
|
1517
|
+
this.statsApi.execute(event, effectiveConfig)
|
|
1518
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1519
|
+
.subscribe({
|
|
1520
|
+
next: (rows) => {
|
|
1521
|
+
this.remoteResolvedData.set(rows);
|
|
1522
|
+
this.remoteRuntimeState.set('ready');
|
|
1523
|
+
this.remoteTechnicalError.set(null);
|
|
1524
|
+
},
|
|
1525
|
+
error: (error) => {
|
|
1526
|
+
this.remoteResolvedData.set([]);
|
|
1527
|
+
this.remoteRuntimeState.set('error');
|
|
1528
|
+
this.remoteTechnicalError.set(error instanceof Error && error.message.trim()
|
|
1529
|
+
? error.message
|
|
1530
|
+
: 'praxis.stats request failed');
|
|
1531
|
+
},
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
effect(() => {
|
|
1535
|
+
if (this.loadState() !== 'ready') {
|
|
1536
|
+
this.engine.destroy();
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const host = this.chartHost()?.nativeElement;
|
|
1540
|
+
if (!host)
|
|
1541
|
+
return;
|
|
1542
|
+
this.engine.render(host, {
|
|
1543
|
+
config: this.renderConfig(),
|
|
1544
|
+
data: this.resolvedData(),
|
|
1545
|
+
onPointClick: (event) => this.pointClick.emit(event),
|
|
1546
|
+
});
|
|
1547
|
+
this.ensureResizeObserver(host);
|
|
1548
|
+
});
|
|
1549
|
+
this.destroyRef.onDestroy(() => {
|
|
1550
|
+
this.clearEditorSessionSubscriptions();
|
|
1551
|
+
this.resizeObserver()?.disconnect();
|
|
1552
|
+
this.shellObserver()?.disconnect();
|
|
1553
|
+
this.engine.destroy();
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
canOpenConfigEditor() {
|
|
1557
|
+
return this.enableCustomization() && !!this.settingsPanel && !!this.runtimeChartDocument();
|
|
1558
|
+
}
|
|
1559
|
+
async openConfigEditor() {
|
|
1560
|
+
if (!this.settingsPanel) {
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const document = this.runtimeChartDocument();
|
|
1564
|
+
if (!document) {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
const { PraxisChartConfigEditor } = await Promise.resolve().then(function () { return praxisChartConfigEditor; });
|
|
1568
|
+
this.clearEditorSessionSubscriptions();
|
|
1569
|
+
const ref = this.settingsPanel.open({
|
|
1570
|
+
id: `chart.${document.chartId || this.effectiveConfig().id || 'praxis-chart'}`,
|
|
1571
|
+
title: this.editChartLabel(),
|
|
1572
|
+
content: {
|
|
1573
|
+
component: PraxisChartConfigEditor,
|
|
1574
|
+
inputs: {
|
|
1575
|
+
chartDocument: document,
|
|
1576
|
+
document,
|
|
1577
|
+
mode: 'edit',
|
|
1578
|
+
readonly: false,
|
|
1579
|
+
availableResources: this.availableResources(),
|
|
1580
|
+
availableFields: this.availableFields(),
|
|
1581
|
+
availableTargets: this.availableTargets(),
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
});
|
|
1585
|
+
const appliedSubscription = ref.applied$
|
|
1586
|
+
.subscribe((nextDocument) => {
|
|
1587
|
+
if (!nextDocument) {
|
|
1258
1588
|
return;
|
|
1259
1589
|
}
|
|
1260
|
-
|
|
1590
|
+
const clone = structuredClone(nextDocument);
|
|
1591
|
+
this.runtimeChartDocument.set(clone);
|
|
1592
|
+
this.chartDocumentApplied.emit(clone);
|
|
1593
|
+
});
|
|
1594
|
+
const savedSubscription = ref.saved$
|
|
1595
|
+
.subscribe((nextDocument) => {
|
|
1596
|
+
if (!nextDocument) {
|
|
1261
1597
|
return;
|
|
1262
1598
|
}
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
query: config.dataSource.query,
|
|
1267
|
-
};
|
|
1268
|
-
this.queryRequest.emit(event);
|
|
1269
|
-
this.remoteRuntimeState.set('loading');
|
|
1270
|
-
this.remoteResolvedRows.set([]);
|
|
1271
|
-
this.remoteTechnicalError.set(null);
|
|
1272
|
-
this.statsApi.execute(event, config)
|
|
1273
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1274
|
-
.subscribe({
|
|
1275
|
-
next: (rows) => {
|
|
1276
|
-
this.remoteResolvedRows.set(rows);
|
|
1277
|
-
this.remoteRuntimeState.set('ready');
|
|
1278
|
-
this.remoteTechnicalError.set(null);
|
|
1279
|
-
},
|
|
1280
|
-
error: (error) => {
|
|
1281
|
-
this.remoteResolvedRows.set([]);
|
|
1282
|
-
this.remoteRuntimeState.set('error');
|
|
1283
|
-
this.remoteTechnicalError.set(error instanceof Error && error.message.trim()
|
|
1284
|
-
? error.message
|
|
1285
|
-
: 'praxis.stats request failed');
|
|
1286
|
-
},
|
|
1287
|
-
});
|
|
1599
|
+
const clone = structuredClone(nextDocument);
|
|
1600
|
+
this.runtimeChartDocument.set(clone);
|
|
1601
|
+
this.chartDocumentSaved.emit(clone);
|
|
1288
1602
|
});
|
|
1603
|
+
this.editorSessionSubscriptions = [appliedSubscription, savedSubscription];
|
|
1289
1604
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
if (!row) {
|
|
1605
|
+
ensureResizeObserver(host) {
|
|
1606
|
+
if (this.resizeObserver()) {
|
|
1293
1607
|
return;
|
|
1294
1608
|
}
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
const categoryField = primarySeries?.categoryField || config.axes?.x?.field;
|
|
1298
|
-
const metricField = primarySeries?.metric?.field;
|
|
1299
|
-
this.pointClick.emit({
|
|
1300
|
-
chartId: config.id,
|
|
1301
|
-
seriesId: primarySeries?.id,
|
|
1302
|
-
seriesName: primarySeries?.name,
|
|
1303
|
-
category: categoryField ? stringifyCell(row[categoryField]) : undefined,
|
|
1304
|
-
value: metricField ? row[metricField] : undefined,
|
|
1305
|
-
data: row,
|
|
1609
|
+
const observer = new ResizeObserver(() => {
|
|
1610
|
+
this.engine.resize();
|
|
1306
1611
|
});
|
|
1612
|
+
observer.observe(host);
|
|
1613
|
+
this.resizeObserver.set(observer);
|
|
1307
1614
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
<praxis-chart
|
|
1312
|
-
[config]="config()"
|
|
1313
|
-
[data]="data()"
|
|
1314
|
-
(pointClick)="pointClick.emit($event)"
|
|
1315
|
-
(queryRequest)="queryRequest.emit($event)"
|
|
1316
|
-
(loadStateChange)="loadStateChange.emit($event)"
|
|
1317
|
-
></praxis-chart>
|
|
1318
|
-
} @else if (stateMode() === 'loading') {
|
|
1319
|
-
<section class="showcase-state-card">
|
|
1320
|
-
<h4>{{ loadingLabel() }}</h4>
|
|
1321
|
-
</section>
|
|
1322
|
-
} @else if (stateMode() === 'error') {
|
|
1323
|
-
<section class="showcase-state-card showcase-state-card-error">
|
|
1324
|
-
<h4>{{ errorTitle() }}</h4>
|
|
1325
|
-
@if (errorDescription()) {
|
|
1326
|
-
<p>{{ errorDescription() }}</p>
|
|
1615
|
+
buildRemoteSignature(config) {
|
|
1616
|
+
if (config.dataSource?.kind !== 'remote') {
|
|
1617
|
+
return null;
|
|
1327
1618
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1619
|
+
return JSON.stringify({
|
|
1620
|
+
id: config.id,
|
|
1621
|
+
resourcePath: config.dataSource.resourcePath,
|
|
1622
|
+
query: config.dataSource.query,
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
clearEditorSessionSubscriptions() {
|
|
1626
|
+
for (const subscription of this.editorSessionSubscriptions) {
|
|
1627
|
+
subscription.unsubscribe();
|
|
1628
|
+
}
|
|
1629
|
+
this.editorSessionSubscriptions = [];
|
|
1630
|
+
}
|
|
1631
|
+
observeShellSizingContext() {
|
|
1632
|
+
const host = this.hostElement.nativeElement;
|
|
1633
|
+
const shell = host.closest('.pdx-shell');
|
|
1634
|
+
if (!shell) {
|
|
1635
|
+
this.fillContainerHeight.set(false);
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
const syncShellMode = () => {
|
|
1639
|
+
this.fillContainerHeight.set(shell.classList.contains('expanded') || shell.classList.contains('fullscreen'));
|
|
1640
|
+
this.scheduleResizeAfterShellModeChange();
|
|
1641
|
+
};
|
|
1642
|
+
syncShellMode();
|
|
1643
|
+
const observer = new MutationObserver(() => {
|
|
1644
|
+
syncShellMode();
|
|
1645
|
+
});
|
|
1646
|
+
observer.observe(shell, {
|
|
1647
|
+
attributes: true,
|
|
1648
|
+
attributeFilter: ['class'],
|
|
1649
|
+
});
|
|
1650
|
+
this.shellObserver.set(observer);
|
|
1651
|
+
}
|
|
1652
|
+
scheduleResizeAfterShellModeChange() {
|
|
1653
|
+
const runResize = () => {
|
|
1654
|
+
this.engine.resize();
|
|
1655
|
+
};
|
|
1656
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1657
|
+
requestAnimationFrame(() => {
|
|
1658
|
+
runResize();
|
|
1659
|
+
requestAnimationFrame(() => {
|
|
1660
|
+
runResize();
|
|
1661
|
+
});
|
|
1662
|
+
});
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
setTimeout(() => {
|
|
1666
|
+
runResize();
|
|
1667
|
+
}, 0);
|
|
1339
1668
|
}
|
|
1340
|
-
|
|
1669
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1670
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartComponent, isStandalone: true, selector: "praxis-chart", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, chartDocument: { classPropertyName: "chartDocument", publicName: "chartDocument", isSignal: true, isRequired: false, transformFunction: null }, filterCriteria: { classPropertyName: "filterCriteria", publicName: "filterCriteria", isSignal: true, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null }, availableResources: { classPropertyName: "availableResources", publicName: "availableResources", isSignal: true, isRequired: false, transformFunction: null }, availableFields: { classPropertyName: "availableFields", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null }, availableTargets: { classPropertyName: "availableTargets", publicName: "availableTargets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick", queryRequest: "queryRequest", loadStateChange: "loadStateChange", chartDocumentApplied: "chartDocumentApplied", chartDocumentSaved: "chartDocumentSaved" }, providers: [
|
|
1671
|
+
EChartsEngineAdapter,
|
|
1672
|
+
{
|
|
1673
|
+
provide: PRAXIS_CHART_ENGINE,
|
|
1674
|
+
useExisting: EChartsEngineAdapter,
|
|
1675
|
+
},
|
|
1676
|
+
], viewQueries: [{ propertyName: "chartHost", first: true, predicate: ["chartHost"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1677
|
+
<section class="praxis-chart-shell" [style.height]="resolvedHeight()">
|
|
1678
|
+
@if (canOpenConfigEditor()) {
|
|
1679
|
+
<button
|
|
1680
|
+
class="praxis-chart-settings-trigger"
|
|
1681
|
+
mat-icon-button
|
|
1682
|
+
type="button"
|
|
1683
|
+
[attr.aria-label]="editChartLabel()"
|
|
1684
|
+
[matTooltip]="editChartLabel()"
|
|
1685
|
+
(click)="openConfigEditor()"
|
|
1686
|
+
>
|
|
1687
|
+
<mat-icon>tune</mat-icon>
|
|
1688
|
+
</button>
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
@if (loadState() === 'loading') {
|
|
1692
|
+
<div class="praxis-chart-state praxis-chart-state-loading">
|
|
1693
|
+
<div class="praxis-chart-spinner" aria-hidden="true"></div>
|
|
1694
|
+
@if (loadingLabel()) {
|
|
1695
|
+
<div class="praxis-chart-state-title">{{ loadingLabel() }}</div>
|
|
1696
|
+
}
|
|
1697
|
+
</div>
|
|
1698
|
+
} @else if (loadState() === 'error') {
|
|
1699
|
+
<div class="praxis-chart-state praxis-chart-state-error">
|
|
1700
|
+
@if (errorTitle()) {
|
|
1701
|
+
<div class="praxis-chart-state-title">{{ errorTitle() }}</div>
|
|
1702
|
+
}
|
|
1703
|
+
@if (errorDescription()) {
|
|
1704
|
+
<div class="praxis-chart-state-description">{{ errorDescription() }}</div>
|
|
1705
|
+
}
|
|
1706
|
+
</div>
|
|
1707
|
+
} @else if (loadState() === 'empty') {
|
|
1708
|
+
<div class="praxis-chart-state praxis-chart-state-empty">
|
|
1709
|
+
@if (emptyTitle()) {
|
|
1710
|
+
<div class="praxis-chart-state-title">{{ emptyTitle() }}</div>
|
|
1711
|
+
}
|
|
1712
|
+
@if (emptyDescription()) {
|
|
1713
|
+
<div class="praxis-chart-state-description">{{ emptyDescription() }}</div>
|
|
1714
|
+
}
|
|
1715
|
+
</div>
|
|
1716
|
+
} @else {
|
|
1717
|
+
<div #chartHost class="praxis-chart-host"></div>
|
|
1718
|
+
}
|
|
1719
|
+
</section>
|
|
1720
|
+
`, isInline: true, styles: [":host{display:block;height:100%;min-width:0}:host-context(.pdx-shell.expanded) .praxis-chart-shell,:host-context(.pdx-shell.fullscreen) .praxis-chart-shell{height:100%!important}.praxis-chart-shell{position:relative;width:100%;height:100%;min-height:240px;border-radius:18px;overflow:hidden;background:radial-gradient(circle at top left,rgba(18,99,180,.12),transparent 38%),linear-gradient(180deg,#1263b408,#1263b400);border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent)}.praxis-chart-settings-trigger{position:absolute;top:10px;right:10px;z-index:3;background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 88%,rgba(18,99,180,.12));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:240px;display:grid;place-content:center;gap:10px;padding:24px;text-align:center}.praxis-chart-state-title{font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.praxis-chart-state-description{font-size:.925rem;color:var(--md-sys-color-on-surface-variant, #5a5d67);max-width:36rem}.praxis-chart-spinner{width:34px;height:34px;margin-inline:auto;border-radius:999px;border:3px solid rgba(18,99,180,.18);border-top-color:#1263b4d1;animation:praxis-chart-spin .8s linear infinite}@keyframes praxis-chart-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1341
1721
|
}
|
|
1342
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type:
|
|
1722
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartComponent, decorators: [{
|
|
1343
1723
|
type: Component,
|
|
1344
|
-
args: [{ selector: 'praxis-chart
|
|
1345
|
-
|
|
1724
|
+
args: [{ selector: 'praxis-chart', standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatTooltipModule], providers: [
|
|
1725
|
+
EChartsEngineAdapter,
|
|
1726
|
+
{
|
|
1727
|
+
provide: PRAXIS_CHART_ENGINE,
|
|
1728
|
+
useExisting: EChartsEngineAdapter,
|
|
1729
|
+
},
|
|
1730
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1731
|
+
<section class="praxis-chart-shell" [style.height]="resolvedHeight()">
|
|
1732
|
+
@if (canOpenConfigEditor()) {
|
|
1733
|
+
<button
|
|
1734
|
+
class="praxis-chart-settings-trigger"
|
|
1735
|
+
mat-icon-button
|
|
1736
|
+
type="button"
|
|
1737
|
+
[attr.aria-label]="editChartLabel()"
|
|
1738
|
+
[matTooltip]="editChartLabel()"
|
|
1739
|
+
(click)="openConfigEditor()"
|
|
1740
|
+
>
|
|
1741
|
+
<mat-icon>tune</mat-icon>
|
|
1742
|
+
</button>
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
@if (loadState() === 'loading') {
|
|
1746
|
+
<div class="praxis-chart-state praxis-chart-state-loading">
|
|
1747
|
+
<div class="praxis-chart-spinner" aria-hidden="true"></div>
|
|
1748
|
+
@if (loadingLabel()) {
|
|
1749
|
+
<div class="praxis-chart-state-title">{{ loadingLabel() }}</div>
|
|
1750
|
+
}
|
|
1751
|
+
</div>
|
|
1752
|
+
} @else if (loadState() === 'error') {
|
|
1753
|
+
<div class="praxis-chart-state praxis-chart-state-error">
|
|
1754
|
+
@if (errorTitle()) {
|
|
1755
|
+
<div class="praxis-chart-state-title">{{ errorTitle() }}</div>
|
|
1756
|
+
}
|
|
1757
|
+
@if (errorDescription()) {
|
|
1758
|
+
<div class="praxis-chart-state-description">{{ errorDescription() }}</div>
|
|
1759
|
+
}
|
|
1760
|
+
</div>
|
|
1761
|
+
} @else if (loadState() === 'empty') {
|
|
1762
|
+
<div class="praxis-chart-state praxis-chart-state-empty">
|
|
1763
|
+
@if (emptyTitle()) {
|
|
1764
|
+
<div class="praxis-chart-state-title">{{ emptyTitle() }}</div>
|
|
1765
|
+
}
|
|
1766
|
+
@if (emptyDescription()) {
|
|
1767
|
+
<div class="praxis-chart-state-description">{{ emptyDescription() }}</div>
|
|
1768
|
+
}
|
|
1769
|
+
</div>
|
|
1770
|
+
} @else {
|
|
1771
|
+
<div #chartHost class="praxis-chart-host"></div>
|
|
1772
|
+
}
|
|
1773
|
+
</section>
|
|
1774
|
+
`, styles: [":host{display:block;height:100%;min-width:0}:host-context(.pdx-shell.expanded) .praxis-chart-shell,:host-context(.pdx-shell.fullscreen) .praxis-chart-shell{height:100%!important}.praxis-chart-shell{position:relative;width:100%;height:100%;min-height:240px;border-radius:18px;overflow:hidden;background:radial-gradient(circle at top left,rgba(18,99,180,.12),transparent 38%),linear-gradient(180deg,#1263b408,#1263b400);border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent)}.praxis-chart-settings-trigger{position:absolute;top:10px;right:10px;z-index:3;background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 88%,rgba(18,99,180,.12));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.praxis-chart-host{width:100%;height:100%}.praxis-chart-state{height:100%;min-height:240px;display:grid;place-content:center;gap:10px;padding:24px;text-align:center}.praxis-chart-state-title{font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.praxis-chart-state-description{font-size:.925rem;color:var(--md-sys-color-on-surface-variant, #5a5d67);max-width:36rem}.praxis-chart-spinner{width:34px;height:34px;margin-inline:auto;border-radius:999px;border:3px solid rgba(18,99,180,.18);border-top-color:#1263b4d1;animation:praxis-chart-spin .8s linear infinite}@keyframes praxis-chart-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
1775
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], chartDocument: [{ type: i0.Input, args: [{ isSignal: true, alias: "chartDocument", required: false }] }], filterCriteria: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterCriteria", required: false }] }], enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }], availableResources: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableResources", required: false }] }], availableFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], availableTargets: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableTargets", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }], chartDocumentApplied: [{ type: i0.Output, args: ["chartDocumentApplied"] }], chartDocumentSaved: [{ type: i0.Output, args: ["chartDocumentSaved"] }], chartHost: [{ type: i0.ViewChild, args: ['chartHost', { isSignal: true }] }] } });
|
|
1776
|
+
function normalizeFilterCriteria(criteria) {
|
|
1777
|
+
if (!criteria)
|
|
1778
|
+
return null;
|
|
1779
|
+
const next = Object.entries(criteria).reduce((acc, [key, value]) => {
|
|
1780
|
+
if (value === null || value === undefined)
|
|
1781
|
+
return acc;
|
|
1782
|
+
if (Array.isArray(value) && value.length === 0)
|
|
1783
|
+
return acc;
|
|
1784
|
+
if (typeof value === 'string' && value.trim() === '')
|
|
1785
|
+
return acc;
|
|
1786
|
+
acc[key] = value;
|
|
1787
|
+
return acc;
|
|
1788
|
+
}, {});
|
|
1789
|
+
return Object.keys(next).length ? next : null;
|
|
1790
|
+
}
|
|
1791
|
+
function mergeRemoteFilterCriteria(config, runtimeFilters) {
|
|
1792
|
+
const dataSource = config.dataSource;
|
|
1793
|
+
if (dataSource?.kind !== 'remote' || !dataSource.query)
|
|
1794
|
+
return config;
|
|
1795
|
+
const query = dataSource.query;
|
|
1796
|
+
const nextFilters = {
|
|
1797
|
+
...(query.filters || {}),
|
|
1798
|
+
...runtimeFilters,
|
|
1799
|
+
};
|
|
1800
|
+
const statsRequest = query.statsRequest && typeof query.statsRequest === 'object'
|
|
1801
|
+
? {
|
|
1802
|
+
...query.statsRequest,
|
|
1803
|
+
filter: {
|
|
1804
|
+
...((query.statsRequest.filter) || {}),
|
|
1805
|
+
...runtimeFilters,
|
|
1806
|
+
},
|
|
1807
|
+
}
|
|
1808
|
+
: query.statsRequest;
|
|
1809
|
+
return {
|
|
1810
|
+
...config,
|
|
1811
|
+
dataSource: {
|
|
1812
|
+
...dataSource,
|
|
1813
|
+
query: {
|
|
1814
|
+
...query,
|
|
1815
|
+
filters: nextFilters,
|
|
1816
|
+
statsRequest,
|
|
1817
|
+
},
|
|
1818
|
+
},
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
const PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH = {
|
|
1823
|
+
Jan: [
|
|
1824
|
+
{ segment: 'Enterprise', total: 52000 },
|
|
1825
|
+
{ segment: 'Mid-market', total: 38000 },
|
|
1826
|
+
{ segment: 'SMB', total: 30000 },
|
|
1827
|
+
],
|
|
1828
|
+
Fev: [
|
|
1829
|
+
{ segment: 'Enterprise', total: 61000 },
|
|
1830
|
+
{ segment: 'Mid-market', total: 42000 },
|
|
1831
|
+
{ segment: 'SMB', total: 35000 },
|
|
1832
|
+
],
|
|
1833
|
+
Mar: [
|
|
1834
|
+
{ segment: 'Enterprise', total: 68000 },
|
|
1835
|
+
{ segment: 'Mid-market', total: 47000 },
|
|
1836
|
+
{ segment: 'SMB', total: 36000 },
|
|
1837
|
+
],
|
|
1838
|
+
Abr: [
|
|
1839
|
+
{ segment: 'Enterprise', total: 64000 },
|
|
1840
|
+
{ segment: 'Mid-market', total: 49500 },
|
|
1841
|
+
{ segment: 'SMB', total: 36000 },
|
|
1842
|
+
],
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
class PraxisChartDrilldownPanelComponent {
|
|
1846
|
+
title = input('Detalhamento por segmento', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1847
|
+
selection = input(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
1848
|
+
activeCategory = computed(() => {
|
|
1849
|
+
const raw = this.selection()?.category;
|
|
1850
|
+
return typeof raw === 'string' ? raw : null;
|
|
1851
|
+
}, ...(ngDevMode ? [{ debugName: "activeCategory" }] : []));
|
|
1852
|
+
detailData = computed(() => {
|
|
1853
|
+
const category = this.activeCategory();
|
|
1854
|
+
if (!category)
|
|
1855
|
+
return [];
|
|
1856
|
+
return PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH[category] ?? [];
|
|
1857
|
+
}, ...(ngDevMode ? [{ debugName: "detailData" }] : []));
|
|
1858
|
+
detailChartConfig = computed(() => {
|
|
1859
|
+
const category = this.activeCategory();
|
|
1860
|
+
return {
|
|
1861
|
+
id: 'chart-drilldown-detail',
|
|
1862
|
+
type: 'donut',
|
|
1863
|
+
title: { text: category ? `Mix de receita em ${category}` : 'Aguardando selecao' },
|
|
1864
|
+
subtitle: { text: category ? 'Drill-down local com JSON mockado' : 'Selecione um ponto no chart principal' },
|
|
1865
|
+
height: 280,
|
|
1866
|
+
series: [
|
|
1867
|
+
{
|
|
1868
|
+
id: 'segmentMix',
|
|
1869
|
+
categoryField: 'segment',
|
|
1870
|
+
metric: { field: 'total', aggregation: 'sum' },
|
|
1871
|
+
labels: { visible: true },
|
|
1872
|
+
},
|
|
1873
|
+
],
|
|
1874
|
+
emptyState: {
|
|
1875
|
+
title: { text: 'Nenhum recorte selecionado' },
|
|
1876
|
+
description: { text: 'O painel de drill-down usa mocks locais e reage ao pointClick do chart principal.' },
|
|
1877
|
+
},
|
|
1878
|
+
theme: {
|
|
1879
|
+
palette: ['#1263b4', '#2b8a3e', '#f08c00', '#7b61ff'],
|
|
1880
|
+
},
|
|
1881
|
+
};
|
|
1882
|
+
}, ...(ngDevMode ? [{ debugName: "detailChartConfig" }] : []));
|
|
1883
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDrilldownPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1884
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartDrilldownPanelComponent, isStandalone: true, selector: "praxis-chart-drilldown-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1885
|
+
<section class="drilldown-shell">
|
|
1886
|
+
<header class="drilldown-header">
|
|
1887
|
+
<p class="drilldown-eyebrow">Drill-down local</p>
|
|
1888
|
+
<h3>{{ title() }}</h3>
|
|
1889
|
+
@if (activeCategory()) {
|
|
1890
|
+
<p class="drilldown-description">Recorte atual: {{ activeCategory() }}</p>
|
|
1891
|
+
} @else {
|
|
1892
|
+
<p class="drilldown-description">Clique em uma barra do chart principal para abrir o detalhamento local.</p>
|
|
1893
|
+
}
|
|
1894
|
+
</header>
|
|
1895
|
+
|
|
1346
1896
|
<praxis-chart
|
|
1347
|
-
[config]="
|
|
1348
|
-
[data]="
|
|
1349
|
-
(pointClick)="pointClick.emit($event)"
|
|
1350
|
-
(queryRequest)="queryRequest.emit($event)"
|
|
1351
|
-
(loadStateChange)="loadStateChange.emit($event)"
|
|
1897
|
+
[config]="detailChartConfig()"
|
|
1898
|
+
[data]="detailData()"
|
|
1352
1899
|
></praxis-chart>
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
<h4>{{ loadingLabel() }}</h4>
|
|
1356
|
-
</section>
|
|
1357
|
-
} @else if (stateMode() === 'error') {
|
|
1358
|
-
<section class="showcase-state-card showcase-state-card-error">
|
|
1359
|
-
<h4>{{ errorTitle() }}</h4>
|
|
1360
|
-
@if (errorDescription()) {
|
|
1361
|
-
<p>{{ errorDescription() }}</p>
|
|
1362
|
-
}
|
|
1363
|
-
</section>
|
|
1364
|
-
} @else {
|
|
1365
|
-
<praxis-table
|
|
1366
|
-
[config]="tableConfig()"
|
|
1367
|
-
[data]="resolvedRows()"
|
|
1368
|
-
[tableId]="tableId()"
|
|
1369
|
-
[title]="resolvedTitle()"
|
|
1370
|
-
[subtitle]="resolvedSubtitle()"
|
|
1371
|
-
[icon]="'table_view'"
|
|
1372
|
-
(rowClick)="handleRowClick($event)"
|
|
1373
|
-
></praxis-table>
|
|
1374
|
-
}
|
|
1375
|
-
`, styles: [":host{display:block;min-width:0}.showcase-state-card{min-height:240px;display:grid;place-content:center;gap:8px;padding:24px;border-radius:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#fffffff5,#f4f7fbfa);text-align:center}.showcase-state-card h4{margin:0;font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.showcase-state-card p{margin:0;color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"] }]
|
|
1376
|
-
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], viewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewMode", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }] } });
|
|
1377
|
-
function resolveShowcaseStateMode(config, remoteRuntimeState = 'idle') {
|
|
1378
|
-
if (config.preferredLoadState === 'loading') {
|
|
1379
|
-
return 'loading';
|
|
1380
|
-
}
|
|
1381
|
-
if (config.preferredLoadState === 'error') {
|
|
1382
|
-
return 'error';
|
|
1383
|
-
}
|
|
1384
|
-
if (config.dataSource?.kind === 'remote' && remoteRuntimeState === 'error') {
|
|
1385
|
-
return 'error';
|
|
1386
|
-
}
|
|
1387
|
-
if (config.dataSource?.kind === 'remote' && remoteRuntimeState !== 'ready') {
|
|
1388
|
-
return 'loading';
|
|
1389
|
-
}
|
|
1390
|
-
return 'ready';
|
|
1900
|
+
</section>
|
|
1901
|
+
`, isInline: true, styles: [":host{display:block}.drilldown-shell{display:grid;gap:14px}.drilldown-header h3,.drilldown-eyebrow,.drilldown-description{margin:0}.drilldown-eyebrow{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #5a5d67)}.drilldown-header h3{font-size:1.2rem;color:var(--md-sys-color-on-surface, #1a1b20)}.drilldown-description{color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1391
1902
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const bulkActions = actions.bulk;
|
|
1404
|
-
const appearance = tableConfig.appearance;
|
|
1405
|
-
const categoryField = config.axes?.x?.field || config.series[0]?.categoryField;
|
|
1406
|
-
const categoryLabel = config.axes?.x?.label || categoryField;
|
|
1407
|
-
const sampleRow = rows[0] || {};
|
|
1408
|
-
const seriesColumns = config.series
|
|
1409
|
-
.map((series) => {
|
|
1410
|
-
const field = series.metric?.field || series.categoryField;
|
|
1411
|
-
if (!field) {
|
|
1412
|
-
return null;
|
|
1903
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartDrilldownPanelComponent, decorators: [{
|
|
1904
|
+
type: Component,
|
|
1905
|
+
args: [{ selector: 'praxis-chart-drilldown-panel', standalone: true, imports: [CommonModule, PraxisChartComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1906
|
+
<section class="drilldown-shell">
|
|
1907
|
+
<header class="drilldown-header">
|
|
1908
|
+
<p class="drilldown-eyebrow">Drill-down local</p>
|
|
1909
|
+
<h3>{{ title() }}</h3>
|
|
1910
|
+
@if (activeCategory()) {
|
|
1911
|
+
<p class="drilldown-description">Recorte atual: {{ activeCategory() }}</p>
|
|
1912
|
+
} @else {
|
|
1913
|
+
<p class="drilldown-description">Clique em uma barra do chart principal para abrir o detalhamento local.</p>
|
|
1413
1914
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
return tableConfig;
|
|
1444
|
-
}
|
|
1445
|
-
function resolveText(value) {
|
|
1446
|
-
if (typeof value === 'string') {
|
|
1447
|
-
return value;
|
|
1448
|
-
}
|
|
1449
|
-
if (value && typeof value === 'object' && 'text' in value) {
|
|
1450
|
-
const text = value.text;
|
|
1451
|
-
return typeof text === 'string' ? text : '';
|
|
1452
|
-
}
|
|
1453
|
-
return '';
|
|
1454
|
-
}
|
|
1455
|
-
function stringifyCell(value) {
|
|
1456
|
-
if (value === null || value === undefined) {
|
|
1457
|
-
return undefined;
|
|
1458
|
-
}
|
|
1459
|
-
return String(value);
|
|
1460
|
-
}
|
|
1461
|
-
function buildRemoteSignature(config) {
|
|
1462
|
-
if (config.dataSource?.kind !== 'remote') {
|
|
1463
|
-
return null;
|
|
1464
|
-
}
|
|
1465
|
-
return JSON.stringify({
|
|
1466
|
-
id: config.id,
|
|
1467
|
-
resourcePath: config.dataSource.resourcePath,
|
|
1468
|
-
query: config.dataSource.query,
|
|
1469
|
-
});
|
|
1915
|
+
</header>
|
|
1916
|
+
|
|
1917
|
+
<praxis-chart
|
|
1918
|
+
[config]="detailChartConfig()"
|
|
1919
|
+
[data]="detailData()"
|
|
1920
|
+
></praxis-chart>
|
|
1921
|
+
</section>
|
|
1922
|
+
`, styles: [":host{display:block}.drilldown-shell{display:grid;gap:14px}.drilldown-header h3,.drilldown-eyebrow,.drilldown-description{margin:0}.drilldown-eyebrow{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #5a5d67)}.drilldown-header h3{font-size:1.2rem;color:var(--md-sys-color-on-surface, #1a1b20)}.drilldown-description{color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"] }]
|
|
1923
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }] } });
|
|
1924
|
+
|
|
1925
|
+
class PraxisChartStateProbeComponent {
|
|
1926
|
+
title = input('Chart Runtime Probe', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1927
|
+
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1928
|
+
serializedValue = computed(() => JSON.stringify(this.value(), null, 2), ...(ngDevMode ? [{ debugName: "serializedValue" }] : []));
|
|
1929
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStateProbeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1930
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartStateProbeComponent, isStandalone: true, selector: "praxis-chart-state-probe", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1931
|
+
<section class="probe-shell">
|
|
1932
|
+
<header class="probe-header">
|
|
1933
|
+
<p class="probe-eyebrow">Probe</p>
|
|
1934
|
+
<h3>{{ title() }}</h3>
|
|
1935
|
+
</header>
|
|
1936
|
+
|
|
1937
|
+
@if (value() === null || value() === undefined || value() === '') {
|
|
1938
|
+
<div class="probe-empty">Aguardando eventos do runtime.</div>
|
|
1939
|
+
} @else {
|
|
1940
|
+
<pre>{{ serializedValue() }}</pre>
|
|
1941
|
+
}
|
|
1942
|
+
</section>
|
|
1943
|
+
`, isInline: true, styles: [":host{display:block}.probe-shell{display:grid;gap:12px;min-height:220px;padding:18px;border-radius:20px;background:linear-gradient(180deg,#0b111ff5,#0b111fe0),radial-gradient(circle at top right,rgba(18,99,180,.32),transparent 35%);color:#d7e6ff}.probe-header h3,.probe-eyebrow{margin:0}.probe-eyebrow{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:#d7e6ffb3}.probe-empty{color:#d7e6ffc7}pre{margin:0;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:.84rem;line-height:1.45}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1470
1944
|
}
|
|
1945
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartStateProbeComponent, decorators: [{
|
|
1946
|
+
type: Component,
|
|
1947
|
+
args: [{ selector: 'praxis-chart-state-probe', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1948
|
+
<section class="probe-shell">
|
|
1949
|
+
<header class="probe-header">
|
|
1950
|
+
<p class="probe-eyebrow">Probe</p>
|
|
1951
|
+
<h3>{{ title() }}</h3>
|
|
1952
|
+
</header>
|
|
1471
1953
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1954
|
+
@if (value() === null || value() === undefined || value() === '') {
|
|
1955
|
+
<div class="probe-empty">Aguardando eventos do runtime.</div>
|
|
1956
|
+
} @else {
|
|
1957
|
+
<pre>{{ serializedValue() }}</pre>
|
|
1958
|
+
}
|
|
1959
|
+
</section>
|
|
1960
|
+
`, styles: [":host{display:block}.probe-shell{display:grid;gap:12px;min-height:220px;padding:18px;border-radius:20px;background:linear-gradient(180deg,#0b111ff5,#0b111fe0),radial-gradient(circle at top right,rgba(18,99,180,.32),transparent 35%);color:#d7e6ff}.probe-header h3,.probe-eyebrow{margin:0}.probe-eyebrow{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:#d7e6ffb3}.probe-empty{color:#d7e6ffc7}pre{margin:0;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:.84rem;line-height:1.45}\n"] }]
|
|
1961
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
1962
|
+
|
|
1963
|
+
const PRAXIS_CHART_COMPONENT_METADATA = {
|
|
1964
|
+
id: 'praxis-chart',
|
|
1965
|
+
componentType: 'praxis-chart',
|
|
1966
|
+
displayName: 'Praxis Chart',
|
|
1967
|
+
selector: 'praxis-chart',
|
|
1968
|
+
component: PraxisChartComponent,
|
|
1969
|
+
friendlyName: 'Praxis Chart',
|
|
1970
|
+
description: 'Chart component for analytics and metadata-driven visualizations in Praxis UI.',
|
|
1971
|
+
icon: 'bar_chart',
|
|
1972
|
+
tags: ['chart', 'analytics', 'widget', 'visualization'],
|
|
1482
1973
|
lib: '@praxisui/charts',
|
|
1483
1974
|
inputs: [
|
|
1484
1975
|
{
|
|
1485
1976
|
name: 'config',
|
|
1486
1977
|
type: 'PraxisChartConfig',
|
|
1487
|
-
description: '
|
|
1978
|
+
description: 'Declarative chart configuration with metadata-oriented semantics.',
|
|
1488
1979
|
},
|
|
1489
1980
|
{
|
|
1490
1981
|
name: 'data',
|
|
1491
1982
|
type: 'PraxisChartDataRow[]',
|
|
1492
|
-
description: '
|
|
1983
|
+
description: 'Optional local dataset that takes precedence over local datasource items.',
|
|
1493
1984
|
},
|
|
1494
1985
|
{
|
|
1495
|
-
name: '
|
|
1496
|
-
type:
|
|
1497
|
-
description: '
|
|
1986
|
+
name: 'chartDocument',
|
|
1987
|
+
type: 'PraxisXUiChartContract | null',
|
|
1988
|
+
description: 'Optional canonical x-ui.chart document used as the authoring source of truth for settings-panel integration.',
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
name: 'filterCriteria',
|
|
1992
|
+
type: 'Record<string, unknown> | null',
|
|
1993
|
+
description: 'Declarative filter criteria merged into remote datasource queries, enabling dynamic-page connections without host orchestration.',
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
name: 'enableCustomization',
|
|
1997
|
+
type: 'boolean',
|
|
1998
|
+
description: 'When true and a settings-panel bridge is available, exposes the chart config editor from the runtime.',
|
|
1999
|
+
default: false,
|
|
1498
2000
|
},
|
|
1499
2001
|
],
|
|
1500
2002
|
outputs: [
|
|
1501
2003
|
{
|
|
1502
2004
|
name: 'pointClick',
|
|
1503
2005
|
type: 'PraxisChartPointEvent',
|
|
1504
|
-
description: '
|
|
2006
|
+
description: 'Emitted when the host wants to react to a point/series click.',
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
name: 'queryRequest',
|
|
2010
|
+
type: 'PraxisChartQueryRequestEvent',
|
|
2011
|
+
description: 'Emitted before a remote praxis.stats datasource is resolved, allowing host-side observability of the outgoing request.',
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
name: 'loadStateChange',
|
|
2015
|
+
type: 'PraxisChartLoadState',
|
|
2016
|
+
description: 'Emitted when the chart state changes.',
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
name: 'chartDocumentApplied',
|
|
2020
|
+
type: 'PraxisXUiChartContract',
|
|
2021
|
+
description: 'Emitted when the runtime chart editor applies a canonical x-ui.chart document through the settings panel bridge.',
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
name: 'chartDocumentSaved',
|
|
2025
|
+
type: 'PraxisXUiChartContract',
|
|
2026
|
+
description: 'Emitted when the runtime chart editor saves a canonical x-ui.chart document through the settings panel bridge.',
|
|
2027
|
+
},
|
|
2028
|
+
],
|
|
2029
|
+
actions: [
|
|
2030
|
+
{
|
|
2031
|
+
id: 'open-chart-editor',
|
|
2032
|
+
label: 'Open Chart Editor',
|
|
2033
|
+
icon: 'tune',
|
|
2034
|
+
description: 'Opens the canonical chart config editor when the host provides a settings panel bridge and chartDocument.',
|
|
2035
|
+
command: 'openConfigEditor',
|
|
2036
|
+
scope: 'toolbar',
|
|
1505
2037
|
},
|
|
2038
|
+
],
|
|
2039
|
+
};
|
|
2040
|
+
function providePraxisChartsMetadata() {
|
|
2041
|
+
return {
|
|
2042
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
2043
|
+
multi: true,
|
|
2044
|
+
useFactory: (registry) => () => {
|
|
2045
|
+
registry.register(PRAXIS_CHART_COMPONENT_METADATA);
|
|
2046
|
+
},
|
|
2047
|
+
deps: [ComponentMetadataRegistry],
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
const PRAXIS_CHART_DRILLDOWN_PANEL_METADATA = {
|
|
2052
|
+
id: 'praxis-chart-drilldown-panel',
|
|
2053
|
+
componentType: 'praxis-chart-drilldown-panel',
|
|
2054
|
+
displayName: 'Praxis Chart Drilldown Panel',
|
|
2055
|
+
selector: 'praxis-chart-drilldown-panel',
|
|
2056
|
+
component: PraxisChartDrilldownPanelComponent,
|
|
2057
|
+
friendlyName: 'Praxis Chart Drilldown Panel',
|
|
2058
|
+
description: 'Local drill-down panel that consumes chart point events and renders a derived detail chart.',
|
|
2059
|
+
icon: 'query_stats',
|
|
2060
|
+
tags: ['chart', 'drilldown', 'widget', 'analytics'],
|
|
2061
|
+
lib: '@praxisui/charts',
|
|
2062
|
+
inputs: [
|
|
1506
2063
|
{
|
|
1507
|
-
name: '
|
|
1508
|
-
type: '
|
|
1509
|
-
description: '
|
|
2064
|
+
name: 'title',
|
|
2065
|
+
type: 'string',
|
|
2066
|
+
description: 'Panel title for the drill-down view.',
|
|
1510
2067
|
},
|
|
1511
2068
|
{
|
|
1512
|
-
name: '
|
|
1513
|
-
type: '
|
|
1514
|
-
description: '
|
|
2069
|
+
name: 'selection',
|
|
2070
|
+
type: 'PraxisChartPointEvent',
|
|
2071
|
+
description: 'Event emitted by a source chart and used to resolve local detail datasets.',
|
|
1515
2072
|
},
|
|
1516
2073
|
],
|
|
1517
2074
|
};
|
|
1518
|
-
function
|
|
2075
|
+
function providePraxisChartDrilldownPanelMetadata() {
|
|
1519
2076
|
return {
|
|
1520
2077
|
provide: ENVIRONMENT_INITIALIZER,
|
|
1521
2078
|
multi: true,
|
|
1522
2079
|
useFactory: (registry) => () => {
|
|
1523
|
-
registry.register(
|
|
2080
|
+
registry.register(PRAXIS_CHART_DRILLDOWN_PANEL_METADATA);
|
|
1524
2081
|
},
|
|
1525
2082
|
deps: [ComponentMetadataRegistry],
|
|
1526
2083
|
};
|
|
1527
2084
|
}
|
|
1528
2085
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
type:
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
throw new Error('x-ui.chart source.operation is required for source.kind="praxis.stats".');
|
|
1584
|
-
}
|
|
1585
|
-
if (contract.theme?.palette && typeof contract.theme.palette === 'string') {
|
|
1586
|
-
throw new Error('x-ui.chart theme.palette as palette token reference is not yet implemented in @praxisui/charts.');
|
|
1587
|
-
}
|
|
1588
|
-
if (contract.theme?.variant) {
|
|
1589
|
-
throw new Error('x-ui.chart theme.variant is not yet implemented in @praxisui/charts.');
|
|
1590
|
-
}
|
|
1591
|
-
const aggregations = [
|
|
1592
|
-
...(contract.metrics?.map((metric) => metric.aggregation).filter(Boolean) ?? []),
|
|
1593
|
-
...(contract.aggregations?.map((aggregation) => aggregation.operation) ?? []),
|
|
1594
|
-
];
|
|
1595
|
-
if (aggregations.includes('distinct-count')) {
|
|
1596
|
-
throw new Error('x-ui.chart aggregation "distinct-count" is not yet implemented in @praxisui/charts.');
|
|
1597
|
-
}
|
|
1598
|
-
if (!contract.metrics?.length) {
|
|
1599
|
-
throw new Error('x-ui.chart requires at least one metric for the current @praxisui/charts runtime.');
|
|
1600
|
-
}
|
|
1601
|
-
if (contract.kind !== 'pie' && contract.kind !== 'donut' && !contract.dimensions?.length) {
|
|
1602
|
-
throw new Error('x-ui.chart cartesian charts require at least one dimension in the current @praxisui/charts runtime.');
|
|
1603
|
-
}
|
|
1604
|
-
if ((contract.kind === 'pie' || contract.kind === 'donut') && !contract.dimensions?.[0]?.field) {
|
|
1605
|
-
throw new Error('x-ui.chart pie/donut charts require a first dimension for category mapping.');
|
|
1606
|
-
}
|
|
1607
|
-
if (contract.metrics.length > 1 && (contract.kind === 'pie' || contract.kind === 'donut')) {
|
|
1608
|
-
throw new Error('x-ui.chart pie/donut charts with multiple metrics are not yet implemented in @praxisui/charts.');
|
|
1609
|
-
}
|
|
1610
|
-
if (contract.kind === 'combo') {
|
|
1611
|
-
if (contract.metrics.length < 2) {
|
|
1612
|
-
throw new Error('x-ui.chart combo charts require at least two metrics.');
|
|
1613
|
-
}
|
|
1614
|
-
if (contract.source.kind === 'praxis.stats'
|
|
1615
|
-
&& contract.source.operation !== 'group-by'
|
|
1616
|
-
&& contract.source.operation !== 'timeseries') {
|
|
1617
|
-
throw new Error('x-ui.chart combo charts over praxis.stats currently support only group-by or timeseries operations in @praxisui/charts.');
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
if (contract.kind !== 'combo' && contract.metrics.some((metric) => metric.axis === 'secondary')) {
|
|
1621
|
-
throw new Error('x-ui.chart axis="secondary" is supported only for combo charts in @praxisui/charts.');
|
|
1622
|
-
}
|
|
1623
|
-
if (contract.source.kind === 'praxis.stats'
|
|
1624
|
-
&& contract.source.operation === 'distribution'
|
|
1625
|
-
&& contract.metrics.length > 1) {
|
|
1626
|
-
throw new Error('x-ui.chart praxis.stats distribution currently supports only a single metric in @praxisui/charts.');
|
|
1627
|
-
}
|
|
1628
|
-
if (contract.kind === 'horizontal-bar' && contract.orientation && contract.orientation !== 'horizontal') {
|
|
1629
|
-
throw new Error('x-ui.chart kind="horizontal-bar" requires orientation="horizontal" when orientation is provided.');
|
|
1630
|
-
}
|
|
1631
|
-
if (contract.kind === 'scatter') {
|
|
1632
|
-
const firstDimension = contract.dimensions?.[0];
|
|
1633
|
-
if (!firstDimension?.field) {
|
|
1634
|
-
throw new Error('x-ui.chart scatter charts require dimensions[0].field for the x axis.');
|
|
1635
|
-
}
|
|
1636
|
-
if (!contract.metrics?.[0]?.field) {
|
|
1637
|
-
throw new Error('x-ui.chart scatter charts require metrics[0].field for the y axis.');
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
if (contract.events?.selectionChange || contract.events?.crossFilter) {
|
|
1641
|
-
throw new Error('x-ui.chart selectionChange/crossFilter declarative runtime actions are not yet implemented in @praxisui/charts.');
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
buildAxes(contract) {
|
|
1645
|
-
const firstDimension = contract.dimensions?.[0];
|
|
1646
|
-
const firstMetric = contract.metrics?.[0];
|
|
1647
|
-
const secondaryMetric = contract.metrics?.find((metric) => metric.axis === 'secondary');
|
|
1648
|
-
const metricCount = contract.metrics?.length ?? 0;
|
|
1649
|
-
if (contract.kind === 'pie' || contract.kind === 'donut') {
|
|
1650
|
-
return {
|
|
1651
|
-
x: {
|
|
1652
|
-
field: firstDimension?.field,
|
|
1653
|
-
label: this.toLabel(firstDimension?.label),
|
|
1654
|
-
},
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
if (contract.kind === 'scatter') {
|
|
1658
|
-
return {
|
|
1659
|
-
x: {
|
|
1660
|
-
field: firstDimension?.field,
|
|
1661
|
-
label: this.toLabel(firstDimension?.label),
|
|
1662
|
-
type: firstDimension?.role === 'time' ? 'time' : 'value',
|
|
1663
|
-
},
|
|
1664
|
-
y: {
|
|
1665
|
-
field: firstMetric?.field,
|
|
1666
|
-
label: this.toLabel(firstMetric?.label),
|
|
1667
|
-
type: 'value',
|
|
1668
|
-
},
|
|
1669
|
-
};
|
|
1670
|
-
}
|
|
1671
|
-
return {
|
|
1672
|
-
x: {
|
|
1673
|
-
field: firstDimension?.field,
|
|
1674
|
-
label: this.toLabel(firstDimension?.label),
|
|
1675
|
-
type: firstDimension?.role === 'time' ? 'time' : 'category',
|
|
1676
|
-
},
|
|
1677
|
-
y: {
|
|
1678
|
-
label: metricCount > 1 ? undefined : this.toLabel(firstMetric?.label),
|
|
1679
|
-
type: 'value',
|
|
1680
|
-
},
|
|
1681
|
-
ySecondary: contract.kind === 'combo' && secondaryMetric
|
|
1682
|
-
? {
|
|
1683
|
-
label: this.toLabel(secondaryMetric.label),
|
|
1684
|
-
type: 'value',
|
|
1685
|
-
position: 'right',
|
|
1686
|
-
}
|
|
1687
|
-
: undefined,
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
buildSeries(contract) {
|
|
1691
|
-
const firstDimension = contract.dimensions?.[0];
|
|
1692
|
-
const labelsVisible = this.resolveToggle(contract.labels);
|
|
1693
|
-
return (contract.metrics ?? []).map((metric, index) => ({
|
|
1694
|
-
id: `${metric.field}-${index + 1}`,
|
|
1695
|
-
name: this.toLabel(metric.label) ?? metric.field,
|
|
1696
|
-
type: this.resolveSeriesType(contract.kind, metric.seriesKind, index),
|
|
1697
|
-
axis: metric.axis ?? 'primary',
|
|
1698
|
-
categoryField: contract.kind === 'pie' || contract.kind === 'donut' ? firstDimension?.field : undefined,
|
|
1699
|
-
metric: {
|
|
1700
|
-
field: metric.field,
|
|
1701
|
-
aggregation: this.mapAggregation(metric.aggregation),
|
|
1702
|
-
label: this.toLabel(metric.label),
|
|
1703
|
-
},
|
|
1704
|
-
color: metric.color,
|
|
1705
|
-
stackId: contract.kind === 'stacked-bar' || contract.kind === 'stacked-area' ? 'stack-1' : undefined,
|
|
1706
|
-
labels: labelsVisible ? { visible: true } : undefined,
|
|
1707
|
-
smooth: this.shouldSmoothSeries(contract.kind, metric.seriesKind),
|
|
1708
|
-
}));
|
|
1709
|
-
}
|
|
1710
|
-
buildDataSource(contract) {
|
|
1711
|
-
if (contract.source.kind === 'derived') {
|
|
1712
|
-
return undefined;
|
|
1713
|
-
}
|
|
1714
|
-
return {
|
|
1715
|
-
kind: 'remote',
|
|
1716
|
-
resourcePath: contract.source.resource,
|
|
1717
|
-
schemaId: contract.source.resource,
|
|
1718
|
-
query: this.buildQuery(contract),
|
|
1719
|
-
};
|
|
1720
|
-
}
|
|
1721
|
-
buildQuery(contract) {
|
|
1722
|
-
if (contract.source.kind !== 'praxis.stats') {
|
|
1723
|
-
return undefined;
|
|
1724
|
-
}
|
|
1725
|
-
const filtersFromContract = this.toQueryFilterMap(contract.filters);
|
|
1726
|
-
const combinedFilters = {
|
|
1727
|
-
...filtersFromContract,
|
|
1728
|
-
};
|
|
1729
|
-
return {
|
|
1730
|
-
sourceKind: 'praxis.stats',
|
|
1731
|
-
statsOperation: contract.source.operation,
|
|
1732
|
-
granularity: contract.source.options?.granularity,
|
|
1733
|
-
fillGaps: contract.source.options?.fillGaps,
|
|
1734
|
-
distributionMode: contract.source.options?.mode,
|
|
1735
|
-
bucketSize: contract.source.options?.bucketSize,
|
|
1736
|
-
bucketCount: contract.source.options?.bucketCount,
|
|
1737
|
-
statsOrderBy: this.mapStatsOrderBy(contract.source.options?.orderBy),
|
|
1738
|
-
statsPath: this.buildStatsPath(contract),
|
|
1739
|
-
statsRequest: this.buildStatsRequest(contract, combinedFilters),
|
|
1740
|
-
dimensions: contract.dimensions?.map((dimension) => dimension.field),
|
|
1741
|
-
metrics: contract.metrics?.map((metric) => ({
|
|
1742
|
-
field: metric.field,
|
|
1743
|
-
aggregation: this.mapAggregation(metric.aggregation),
|
|
1744
|
-
alias: metric.field,
|
|
1745
|
-
})),
|
|
1746
|
-
filters: Object.keys(combinedFilters).length ? combinedFilters : undefined,
|
|
1747
|
-
sort: contract.sort?.map((item) => `${item.field}:${item.direction}`),
|
|
1748
|
-
limit: contract.limit ?? contract.source.options?.limit,
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
buildInteractions(contract) {
|
|
1752
|
-
return {
|
|
1753
|
-
pointClick: Boolean(contract.events?.pointClick || contract.events?.drillDown),
|
|
1754
|
-
selection: Boolean(contract.events?.selectionChange),
|
|
1755
|
-
drillDown: Boolean(contract.events?.drillDown),
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
buildTheme(contract) {
|
|
1759
|
-
return {
|
|
1760
|
-
palette: Array.isArray(contract.theme?.palette) ? contract.theme.palette : undefined,
|
|
1761
|
-
legend: { visible: this.resolveToggle(contract.legend, true) },
|
|
1762
|
-
tooltip: {
|
|
1763
|
-
enabled: this.resolveToggle(contract.tooltip, true),
|
|
1764
|
-
trigger: contract.kind === 'pie' || contract.kind === 'donut' || contract.kind === 'scatter' ? 'item' : 'axis',
|
|
1765
|
-
},
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
resolveOrientation(contract) {
|
|
1769
|
-
if (contract.kind === 'horizontal-bar') {
|
|
1770
|
-
return 'horizontal';
|
|
1771
|
-
}
|
|
1772
|
-
return contract.orientation;
|
|
1773
|
-
}
|
|
1774
|
-
resolveSeriesType(chartKind, seriesKind, index) {
|
|
1775
|
-
if (chartKind === 'combo') {
|
|
1776
|
-
return seriesKind ?? (index === 0 ? 'bar' : 'line');
|
|
1777
|
-
}
|
|
1778
|
-
return chartKind === 'stacked-bar' ? 'bar' : chartKind;
|
|
1779
|
-
}
|
|
1780
|
-
shouldSmoothSeries(chartKind, seriesKind) {
|
|
1781
|
-
if (chartKind === 'combo') {
|
|
1782
|
-
return seriesKind === 'line' || seriesKind === 'area' ? true : undefined;
|
|
1783
|
-
}
|
|
1784
|
-
return chartKind === 'line' || chartKind === 'area' || chartKind === 'stacked-area' ? true : undefined;
|
|
1785
|
-
}
|
|
1786
|
-
mapTextValue(value) {
|
|
1787
|
-
if (!value)
|
|
1788
|
-
return undefined;
|
|
1789
|
-
if (typeof value === 'string')
|
|
1790
|
-
return value;
|
|
1791
|
-
return {
|
|
1792
|
-
key: value.key,
|
|
1793
|
-
text: value.fallback,
|
|
1794
|
-
};
|
|
1795
|
-
}
|
|
1796
|
-
toLabel(value) {
|
|
1797
|
-
if (!value)
|
|
1798
|
-
return undefined;
|
|
1799
|
-
if (typeof value === 'string')
|
|
1800
|
-
return value;
|
|
1801
|
-
return value.fallback || value.key;
|
|
1802
|
-
}
|
|
1803
|
-
resolveToggle(value, defaultValue = false) {
|
|
1804
|
-
if (typeof value === 'boolean')
|
|
1805
|
-
return value;
|
|
1806
|
-
if (typeof value === 'object' && value)
|
|
1807
|
-
return value.enabled;
|
|
1808
|
-
return defaultValue;
|
|
1809
|
-
}
|
|
1810
|
-
mapAggregation(aggregation) {
|
|
1811
|
-
switch (aggregation) {
|
|
1812
|
-
case 'avg':
|
|
1813
|
-
case 'min':
|
|
1814
|
-
case 'max':
|
|
1815
|
-
case 'count':
|
|
1816
|
-
case 'sum':
|
|
1817
|
-
return aggregation;
|
|
1818
|
-
case undefined:
|
|
1819
|
-
return undefined;
|
|
1820
|
-
default:
|
|
1821
|
-
throw new Error(`x-ui.chart aggregation "${aggregation}" is not yet implemented in @praxisui/charts.`);
|
|
2086
|
+
const PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA = {
|
|
2087
|
+
id: 'praxis-chart-state-probe',
|
|
2088
|
+
componentType: 'praxis-chart-state-probe',
|
|
2089
|
+
displayName: 'Praxis Chart State Probe',
|
|
2090
|
+
selector: 'praxis-chart-state-probe',
|
|
2091
|
+
component: PraxisChartStateProbeComponent,
|
|
2092
|
+
friendlyName: 'Praxis Chart State Probe',
|
|
2093
|
+
description: 'Diagnostic widget used to inspect chart events and runtime payloads during local validation.',
|
|
2094
|
+
icon: 'monitoring',
|
|
2095
|
+
tags: ['chart', 'probe', 'debug', 'widget'],
|
|
2096
|
+
lib: '@praxisui/charts',
|
|
2097
|
+
inputs: [
|
|
2098
|
+
{
|
|
2099
|
+
name: 'title',
|
|
2100
|
+
type: 'string',
|
|
2101
|
+
description: 'Probe panel title.',
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
name: 'value',
|
|
2105
|
+
type: 'unknown',
|
|
2106
|
+
description: 'Payload rendered as formatted JSON.',
|
|
2107
|
+
},
|
|
2108
|
+
],
|
|
2109
|
+
};
|
|
2110
|
+
function providePraxisChartStateProbeMetadata() {
|
|
2111
|
+
return {
|
|
2112
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
2113
|
+
multi: true,
|
|
2114
|
+
useFactory: (registry) => () => {
|
|
2115
|
+
registry.register(PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA);
|
|
2116
|
+
},
|
|
2117
|
+
deps: [ComponentMetadataRegistry],
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
class PraxisChartShowcaseWidgetComponent {
|
|
2122
|
+
config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
2123
|
+
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2124
|
+
chartDocument = input(null, ...(ngDevMode ? [{ debugName: "chartDocument" }] : []));
|
|
2125
|
+
enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization" }] : []));
|
|
2126
|
+
viewMode = input('chart', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
|
|
2127
|
+
pointClick = output();
|
|
2128
|
+
queryRequest = output();
|
|
2129
|
+
loadStateChange = output();
|
|
2130
|
+
statsApi = inject(PraxisChartStatsApiService);
|
|
2131
|
+
destroyRef = inject(DestroyRef);
|
|
2132
|
+
remoteResolvedRows = signal(null, ...(ngDevMode ? [{ debugName: "remoteResolvedRows" }] : []));
|
|
2133
|
+
remoteRuntimeState = signal('idle', ...(ngDevMode ? [{ debugName: "remoteRuntimeState" }] : []));
|
|
2134
|
+
remoteTechnicalError = signal(null, ...(ngDevMode ? [{ debugName: "remoteTechnicalError" }] : []));
|
|
2135
|
+
previousRemoteSignature = null;
|
|
2136
|
+
resolvedRows = computed(() => {
|
|
2137
|
+
const explicitData = this.data();
|
|
2138
|
+
if (explicitData !== null && explicitData !== undefined) {
|
|
2139
|
+
return explicitData;
|
|
1822
2140
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
2141
|
+
return this.remoteResolvedRows() ?? [];
|
|
2142
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedRows" }] : []));
|
|
2143
|
+
tableId = computed(() => `${this.config().id || 'praxis-chart'}-showcase-table`, ...(ngDevMode ? [{ debugName: "tableId" }] : []));
|
|
2144
|
+
resolvedTitle = computed(() => resolveText(this.config().title) || 'Tabela analitica', ...(ngDevMode ? [{ debugName: "resolvedTitle" }] : []));
|
|
2145
|
+
resolvedSubtitle = computed(() => resolveText(this.config().subtitle) || 'Mesmo dataset em visualizacao tabular', ...(ngDevMode ? [{ debugName: "resolvedSubtitle" }] : []));
|
|
2146
|
+
loadingLabel = computed(() => resolveText(this.config().state?.loadingLabel) || 'Carregando analytics...', ...(ngDevMode ? [{ debugName: "loadingLabel" }] : []));
|
|
2147
|
+
errorTitle = computed(() => this.remoteTechnicalError()
|
|
2148
|
+
|| resolveText(this.config().state?.error?.title)
|
|
2149
|
+
|| 'Falha no contrato analitico', ...(ngDevMode ? [{ debugName: "errorTitle" }] : []));
|
|
2150
|
+
errorDescription = computed(() => resolveText(this.config().state?.error?.description), ...(ngDevMode ? [{ debugName: "errorDescription" }] : []));
|
|
2151
|
+
stateMode = computed(() => resolveShowcaseStateMode(this.config(), this.remoteRuntimeState()), ...(ngDevMode ? [{ debugName: "stateMode" }] : []));
|
|
2152
|
+
tableConfig = computed(() => buildTableConfig(this.config(), this.resolvedRows()), ...(ngDevMode ? [{ debugName: "tableConfig" }] : []));
|
|
2153
|
+
constructor() {
|
|
2154
|
+
effect(() => {
|
|
2155
|
+
const config = this.config();
|
|
2156
|
+
const explicitData = this.data();
|
|
2157
|
+
const nextSignature = explicitData === null || explicitData === undefined
|
|
2158
|
+
? buildRemoteSignature(config)
|
|
2159
|
+
: null;
|
|
2160
|
+
if (nextSignature === this.previousRemoteSignature) {
|
|
2161
|
+
return;
|
|
1831
2162
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
2163
|
+
this.previousRemoteSignature = nextSignature;
|
|
2164
|
+
this.remoteResolvedRows.set(null);
|
|
2165
|
+
this.remoteRuntimeState.set('idle');
|
|
2166
|
+
this.remoteTechnicalError.set(null);
|
|
2167
|
+
});
|
|
2168
|
+
effect(() => {
|
|
2169
|
+
const config = this.config();
|
|
2170
|
+
const explicitData = this.data();
|
|
2171
|
+
if (this.viewMode() !== 'table') {
|
|
2172
|
+
return;
|
|
1834
2173
|
}
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2174
|
+
if (config.dataSource?.kind !== 'remote' || (explicitData !== null && explicitData !== undefined)) {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
if (this.remoteRuntimeState() === 'loading' || this.remoteResolvedRows() !== null) {
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const event = {
|
|
2181
|
+
chartId: config.id,
|
|
2182
|
+
dataSource: config.dataSource,
|
|
2183
|
+
query: config.dataSource.query,
|
|
2184
|
+
};
|
|
2185
|
+
this.queryRequest.emit(event);
|
|
2186
|
+
this.remoteRuntimeState.set('loading');
|
|
2187
|
+
this.remoteResolvedRows.set([]);
|
|
2188
|
+
this.remoteTechnicalError.set(null);
|
|
2189
|
+
this.statsApi.execute(event, config)
|
|
2190
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2191
|
+
.subscribe({
|
|
2192
|
+
next: (rows) => {
|
|
2193
|
+
this.remoteResolvedRows.set(rows);
|
|
2194
|
+
this.remoteRuntimeState.set('ready');
|
|
2195
|
+
this.remoteTechnicalError.set(null);
|
|
2196
|
+
},
|
|
2197
|
+
error: (error) => {
|
|
2198
|
+
this.remoteResolvedRows.set([]);
|
|
2199
|
+
this.remoteRuntimeState.set('error');
|
|
2200
|
+
this.remoteTechnicalError.set(error instanceof Error && error.message.trim()
|
|
2201
|
+
? error.message
|
|
2202
|
+
: 'praxis.stats request failed');
|
|
2203
|
+
},
|
|
2204
|
+
});
|
|
2205
|
+
});
|
|
1840
2206
|
}
|
|
1841
|
-
|
|
1842
|
-
const
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
const limit = contract.limit ?? contract.source.options?.limit;
|
|
1846
|
-
const orderBy = this.mapStatsOrderByToBackend(contract.source.options?.orderBy);
|
|
1847
|
-
const requestMetrics = metrics.length > 1 ? metrics : undefined;
|
|
1848
|
-
switch (contract.source.operation) {
|
|
1849
|
-
case 'group-by':
|
|
1850
|
-
return {
|
|
1851
|
-
filter: filters,
|
|
1852
|
-
field,
|
|
1853
|
-
metric,
|
|
1854
|
-
metrics: requestMetrics,
|
|
1855
|
-
limit,
|
|
1856
|
-
orderBy,
|
|
1857
|
-
};
|
|
1858
|
-
case 'timeseries':
|
|
1859
|
-
return {
|
|
1860
|
-
filter: filters,
|
|
1861
|
-
field,
|
|
1862
|
-
granularity: this.mapStatsGranularityToBackend(contract.source.options?.granularity),
|
|
1863
|
-
metric,
|
|
1864
|
-
metrics: requestMetrics,
|
|
1865
|
-
fillGaps: contract.source.options?.fillGaps,
|
|
1866
|
-
};
|
|
1867
|
-
case 'distribution':
|
|
1868
|
-
return {
|
|
1869
|
-
filter: filters,
|
|
1870
|
-
field,
|
|
1871
|
-
mode: this.mapDistributionModeToBackend(contract.source.options?.mode),
|
|
1872
|
-
metric,
|
|
1873
|
-
bucketSize: contract.source.options?.bucketSize,
|
|
1874
|
-
bucketCount: contract.source.options?.bucketCount,
|
|
1875
|
-
limit,
|
|
1876
|
-
orderBy,
|
|
1877
|
-
};
|
|
1878
|
-
default:
|
|
1879
|
-
throw new Error(`x-ui.chart source.operation "${contract.source.operation}" is not supported in @praxisui/charts.`);
|
|
2207
|
+
handleRowClick(event) {
|
|
2208
|
+
const row = event?.row;
|
|
2209
|
+
if (!row) {
|
|
2210
|
+
return;
|
|
1880
2211
|
}
|
|
2212
|
+
const config = this.config();
|
|
2213
|
+
const primarySeries = config.series[0];
|
|
2214
|
+
const categoryField = primarySeries?.categoryField || config.axes?.x?.field;
|
|
2215
|
+
const metricField = primarySeries?.metric?.field;
|
|
2216
|
+
this.pointClick.emit({
|
|
2217
|
+
chartId: config.id,
|
|
2218
|
+
seriesId: primarySeries?.id,
|
|
2219
|
+
seriesName: primarySeries?.name,
|
|
2220
|
+
category: categoryField ? stringifyCell(row[categoryField]) : undefined,
|
|
2221
|
+
value: metricField ? row[metricField] : undefined,
|
|
2222
|
+
data: row,
|
|
2223
|
+
});
|
|
1881
2224
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2225
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartShowcaseWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2226
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartShowcaseWidgetComponent, isStandalone: true, selector: "praxis-chart-showcase-widget", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, chartDocument: { classPropertyName: "chartDocument", publicName: "chartDocument", isSignal: true, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null }, viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick", queryRequest: "queryRequest", loadStateChange: "loadStateChange" }, ngImport: i0, template: `
|
|
2227
|
+
@if (viewMode() === 'chart') {
|
|
2228
|
+
<praxis-chart
|
|
2229
|
+
[config]="config()"
|
|
2230
|
+
[data]="data()"
|
|
2231
|
+
[chartDocument]="chartDocument()"
|
|
2232
|
+
[enableCustomization]="enableCustomization()"
|
|
2233
|
+
(pointClick)="pointClick.emit($event)"
|
|
2234
|
+
(queryRequest)="queryRequest.emit($event)"
|
|
2235
|
+
(loadStateChange)="loadStateChange.emit($event)"
|
|
2236
|
+
></praxis-chart>
|
|
2237
|
+
} @else if (stateMode() === 'loading') {
|
|
2238
|
+
<section class="showcase-state-card">
|
|
2239
|
+
<h4>{{ loadingLabel() }}</h4>
|
|
2240
|
+
</section>
|
|
2241
|
+
} @else if (stateMode() === 'error') {
|
|
2242
|
+
<section class="showcase-state-card showcase-state-card-error">
|
|
2243
|
+
<h4>{{ errorTitle() }}</h4>
|
|
2244
|
+
@if (errorDescription()) {
|
|
2245
|
+
<p>{{ errorDescription() }}</p>
|
|
1886
2246
|
}
|
|
1887
|
-
|
|
2247
|
+
</section>
|
|
2248
|
+
} @else {
|
|
2249
|
+
<praxis-table
|
|
2250
|
+
[config]="tableConfig()"
|
|
2251
|
+
[data]="resolvedRows()"
|
|
2252
|
+
[tableId]="tableId()"
|
|
2253
|
+
[title]="resolvedTitle()"
|
|
2254
|
+
[subtitle]="resolvedSubtitle()"
|
|
2255
|
+
[icon]="'table_view'"
|
|
2256
|
+
(rowClick)="handleRowClick($event)"
|
|
2257
|
+
></praxis-table>
|
|
1888
2258
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2259
|
+
`, isInline: true, styles: [":host{display:block;height:100%;min-width:0}.showcase-state-card{min-height:240px;display:grid;place-content:center;gap:8px;padding:24px;border-radius:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#fffffff5,#f4f7fbfa);text-align:center}.showcase-state-card h4{margin:0;font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.showcase-state-card p{margin:0;color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }, { kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "dslFunctionRegistry", "editModeEnabled", "dense"], outputs: ["rowClick", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "columnReorder", "columnReorderAttempt", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "metadataChange", "loadingStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2260
|
+
}
|
|
2261
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartShowcaseWidgetComponent, decorators: [{
|
|
2262
|
+
type: Component,
|
|
2263
|
+
args: [{ selector: 'praxis-chart-showcase-widget', standalone: true, imports: [CommonModule, PraxisChartComponent, PraxisTable], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2264
|
+
@if (viewMode() === 'chart') {
|
|
2265
|
+
<praxis-chart
|
|
2266
|
+
[config]="config()"
|
|
2267
|
+
[data]="data()"
|
|
2268
|
+
[chartDocument]="chartDocument()"
|
|
2269
|
+
[enableCustomization]="enableCustomization()"
|
|
2270
|
+
(pointClick)="pointClick.emit($event)"
|
|
2271
|
+
(queryRequest)="queryRequest.emit($event)"
|
|
2272
|
+
(loadStateChange)="loadStateChange.emit($event)"
|
|
2273
|
+
></praxis-chart>
|
|
2274
|
+
} @else if (stateMode() === 'loading') {
|
|
2275
|
+
<section class="showcase-state-card">
|
|
2276
|
+
<h4>{{ loadingLabel() }}</h4>
|
|
2277
|
+
</section>
|
|
2278
|
+
} @else if (stateMode() === 'error') {
|
|
2279
|
+
<section class="showcase-state-card showcase-state-card-error">
|
|
2280
|
+
<h4>{{ errorTitle() }}</h4>
|
|
2281
|
+
@if (errorDescription()) {
|
|
2282
|
+
<p>{{ errorDescription() }}</p>
|
|
1893
2283
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2284
|
+
</section>
|
|
2285
|
+
} @else {
|
|
2286
|
+
<praxis-table
|
|
2287
|
+
[config]="tableConfig()"
|
|
2288
|
+
[data]="resolvedRows()"
|
|
2289
|
+
[tableId]="tableId()"
|
|
2290
|
+
[title]="resolvedTitle()"
|
|
2291
|
+
[subtitle]="resolvedSubtitle()"
|
|
2292
|
+
[icon]="'table_view'"
|
|
2293
|
+
(rowClick)="handleRowClick($event)"
|
|
2294
|
+
></praxis-table>
|
|
1900
2295
|
}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
return
|
|
1906
|
-
const operation = this.mapStatsMetricOperation(metric.aggregation);
|
|
1907
|
-
return {
|
|
1908
|
-
operation,
|
|
1909
|
-
field: operation === 'COUNT' ? undefined : metric.field,
|
|
1910
|
-
alias: metric.field,
|
|
1911
|
-
};
|
|
1912
|
-
});
|
|
2296
|
+
`, styles: [":host{display:block;height:100%;min-width:0}.showcase-state-card{min-height:240px;display:grid;place-content:center;gap:8px;padding:24px;border-radius:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#fffffff5,#f4f7fbfa);text-align:center}.showcase-state-card h4{margin:0;font-size:1rem;font-weight:600;color:var(--md-sys-color-on-surface, #1a1b20)}.showcase-state-card p{margin:0;color:var(--md-sys-color-on-surface-variant, #5a5d67)}\n"] }]
|
|
2297
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], chartDocument: [{ type: i0.Input, args: [{ isSignal: true, alias: "chartDocument", required: false }] }], enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }], viewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewMode", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], queryRequest: [{ type: i0.Output, args: ["queryRequest"] }], loadStateChange: [{ type: i0.Output, args: ["loadStateChange"] }] } });
|
|
2298
|
+
function resolveShowcaseStateMode(config, remoteRuntimeState = 'idle') {
|
|
2299
|
+
if (config.preferredLoadState === 'loading') {
|
|
2300
|
+
return 'loading';
|
|
1913
2301
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
return undefined;
|
|
1917
|
-
switch (value) {
|
|
1918
|
-
case 'key-asc':
|
|
1919
|
-
case 'key-desc':
|
|
1920
|
-
case 'value-asc':
|
|
1921
|
-
case 'value-desc':
|
|
1922
|
-
return value;
|
|
1923
|
-
default:
|
|
1924
|
-
throw new Error(`x-ui.chart source.options.orderBy "${value}" is not supported in @praxisui/charts.`);
|
|
1925
|
-
}
|
|
2302
|
+
if (config.preferredLoadState === 'error') {
|
|
2303
|
+
return 'error';
|
|
1926
2304
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
case 'key-asc':
|
|
1930
|
-
return 'KEY_ASC';
|
|
1931
|
-
case 'key-desc':
|
|
1932
|
-
return 'KEY_DESC';
|
|
1933
|
-
case 'value-asc':
|
|
1934
|
-
return 'VALUE_ASC';
|
|
1935
|
-
case 'value-desc':
|
|
1936
|
-
return 'VALUE_DESC';
|
|
1937
|
-
case undefined:
|
|
1938
|
-
return undefined;
|
|
1939
|
-
default:
|
|
1940
|
-
throw new Error(`x-ui.chart source.options.orderBy "${value}" is not supported in @praxisui/charts.`);
|
|
1941
|
-
}
|
|
2305
|
+
if (config.dataSource?.kind === 'remote' && remoteRuntimeState === 'error') {
|
|
2306
|
+
return 'error';
|
|
1942
2307
|
}
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
case 'hour':
|
|
1946
|
-
return 'HOUR';
|
|
1947
|
-
case 'day':
|
|
1948
|
-
return 'DAY';
|
|
1949
|
-
case 'week':
|
|
1950
|
-
return 'WEEK';
|
|
1951
|
-
case 'month':
|
|
1952
|
-
case undefined:
|
|
1953
|
-
return 'MONTH';
|
|
1954
|
-
case 'quarter':
|
|
1955
|
-
return 'QUARTER';
|
|
1956
|
-
case 'year':
|
|
1957
|
-
return 'YEAR';
|
|
1958
|
-
default:
|
|
1959
|
-
throw new Error(`x-ui.chart source.options.granularity "${value}" is not supported in @praxisui/charts.`);
|
|
1960
|
-
}
|
|
2308
|
+
if (config.dataSource?.kind === 'remote' && remoteRuntimeState !== 'ready') {
|
|
2309
|
+
return 'loading';
|
|
1961
2310
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2311
|
+
return 'ready';
|
|
2312
|
+
}
|
|
2313
|
+
function buildTableConfig(config, rows) {
|
|
2314
|
+
const tableConfig = createDefaultTableConfig();
|
|
2315
|
+
const toolbar = tableConfig.toolbar;
|
|
2316
|
+
const behavior = tableConfig.behavior;
|
|
2317
|
+
const pagination = behavior.pagination;
|
|
2318
|
+
const sorting = behavior.sorting;
|
|
2319
|
+
const filtering = behavior.filtering;
|
|
2320
|
+
const selection = behavior.selection;
|
|
2321
|
+
const localDataMode = behavior.localDataMode;
|
|
2322
|
+
const actions = tableConfig.actions;
|
|
2323
|
+
const rowActions = actions.row;
|
|
2324
|
+
const bulkActions = actions.bulk;
|
|
2325
|
+
const appearance = tableConfig.appearance;
|
|
2326
|
+
const categoryField = config.axes?.x?.field || config.series[0]?.categoryField;
|
|
2327
|
+
const categoryLabel = config.axes?.x?.label || categoryField;
|
|
2328
|
+
const sampleRow = rows[0] || {};
|
|
2329
|
+
const seriesColumns = config.series
|
|
2330
|
+
.map((series) => {
|
|
2331
|
+
const field = series.metric?.field || series.categoryField;
|
|
2332
|
+
if (!field) {
|
|
2333
|
+
return null;
|
|
1971
2334
|
}
|
|
2335
|
+
return {
|
|
2336
|
+
field,
|
|
2337
|
+
header: series.name || series.metric?.label || field,
|
|
2338
|
+
};
|
|
2339
|
+
})
|
|
2340
|
+
.filter((column) => column !== null);
|
|
2341
|
+
const fallbackColumns = Object.keys(sampleRow).map((field) => ({
|
|
2342
|
+
field,
|
|
2343
|
+
header: field,
|
|
2344
|
+
}));
|
|
2345
|
+
const categoryColumn = categoryField
|
|
2346
|
+
? [{
|
|
2347
|
+
field: categoryField,
|
|
2348
|
+
header: categoryLabel || categoryField,
|
|
2349
|
+
}]
|
|
2350
|
+
: [];
|
|
2351
|
+
const dedupedColumns = [...categoryColumn, ...seriesColumns, ...fallbackColumns].filter((column, index, source) => source.findIndex((candidate) => candidate.field === column.field) === index);
|
|
2352
|
+
tableConfig.columns = dedupedColumns;
|
|
2353
|
+
toolbar.visible = false;
|
|
2354
|
+
pagination.enabled = rows.length > 10;
|
|
2355
|
+
sorting.enabled = true;
|
|
2356
|
+
filtering.enabled = false;
|
|
2357
|
+
selection.enabled = false;
|
|
2358
|
+
localDataMode.enabled = true;
|
|
2359
|
+
rowActions.enabled = false;
|
|
2360
|
+
rowActions.actions = [];
|
|
2361
|
+
bulkActions.enabled = false;
|
|
2362
|
+
bulkActions.actions = [];
|
|
2363
|
+
appearance.density = 'comfortable';
|
|
2364
|
+
return tableConfig;
|
|
2365
|
+
}
|
|
2366
|
+
function resolveText(value) {
|
|
2367
|
+
if (typeof value === 'string') {
|
|
2368
|
+
return value;
|
|
1972
2369
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
case 'count':
|
|
1977
|
-
return 'COUNT';
|
|
1978
|
-
case 'sum':
|
|
1979
|
-
return 'SUM';
|
|
1980
|
-
case 'avg':
|
|
1981
|
-
return 'AVG';
|
|
1982
|
-
case 'min':
|
|
1983
|
-
return 'MIN';
|
|
1984
|
-
case 'max':
|
|
1985
|
-
return 'MAX';
|
|
1986
|
-
default:
|
|
1987
|
-
throw new Error(`x-ui.chart aggregation "${aggregation}" is not yet implemented in @praxisui/charts.`);
|
|
1988
|
-
}
|
|
2370
|
+
if (value && typeof value === 'object' && 'text' in value) {
|
|
2371
|
+
const text = value.text;
|
|
2372
|
+
return typeof text === 'string' ? text : '';
|
|
1989
2373
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
2374
|
+
return '';
|
|
2375
|
+
}
|
|
2376
|
+
function stringifyCell(value) {
|
|
2377
|
+
if (value === null || value === undefined) {
|
|
2378
|
+
return undefined;
|
|
2379
|
+
}
|
|
2380
|
+
return String(value);
|
|
2381
|
+
}
|
|
2382
|
+
function buildRemoteSignature(config) {
|
|
2383
|
+
if (config.dataSource?.kind !== 'remote') {
|
|
2384
|
+
return null;
|
|
2385
|
+
}
|
|
2386
|
+
return JSON.stringify({
|
|
2387
|
+
id: config.id,
|
|
2388
|
+
resourcePath: config.dataSource.resourcePath,
|
|
2389
|
+
query: config.dataSource.query,
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
const PRAXIS_CHART_SHOWCASE_WIDGET_METADATA = {
|
|
2394
|
+
id: 'praxis-chart-showcase-widget',
|
|
2395
|
+
componentType: 'praxis-chart-showcase-widget',
|
|
2396
|
+
displayName: 'Praxis Chart Showcase Widget',
|
|
2397
|
+
selector: 'praxis-chart-showcase-widget',
|
|
2398
|
+
component: PraxisChartShowcaseWidgetComponent,
|
|
2399
|
+
friendlyName: 'Praxis Chart Showcase Widget',
|
|
2400
|
+
description: 'Showcase-only widget that toggles between Praxis chart and Praxis table using the same dataset.',
|
|
2401
|
+
icon: 'swap_horiz',
|
|
2402
|
+
tags: ['chart', 'table', 'showcase', 'analytics'],
|
|
2403
|
+
lib: '@praxisui/charts',
|
|
2404
|
+
inputs: [
|
|
2405
|
+
{
|
|
2406
|
+
name: 'config',
|
|
2407
|
+
type: 'PraxisChartConfig',
|
|
2408
|
+
description: 'Chart configuration preserved across chart and table views.',
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
name: 'data',
|
|
2412
|
+
type: 'PraxisChartDataRow[]',
|
|
2413
|
+
description: 'Local dataset shared by the chart and table renderers.',
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
name: 'chartDocument',
|
|
2417
|
+
type: 'PraxisXUiChartContract | null',
|
|
2418
|
+
description: 'Canonical x-ui.chart contract forwarded to the runtime chart editor affordance.',
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
name: 'enableCustomization',
|
|
2422
|
+
type: 'boolean',
|
|
2423
|
+
description: 'Enables runtime chart editor affordances when the host is in customization mode.',
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
name: 'viewMode',
|
|
2427
|
+
type: "'chart' | 'table'",
|
|
2428
|
+
description: 'Current showcase view mode.',
|
|
2429
|
+
},
|
|
2430
|
+
],
|
|
2431
|
+
outputs: [
|
|
2432
|
+
{
|
|
2433
|
+
name: 'pointClick',
|
|
2434
|
+
type: 'PraxisChartPointEvent',
|
|
2435
|
+
description: 'Forwards point selection from the chart or mapped row selection from the table.',
|
|
2436
|
+
},
|
|
2437
|
+
{
|
|
2438
|
+
name: 'queryRequest',
|
|
2439
|
+
type: 'PraxisChartQueryRequestEvent',
|
|
2440
|
+
description: 'Forwards remote chart data requests.',
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
name: 'loadStateChange',
|
|
2444
|
+
type: 'PraxisChartLoadState',
|
|
2445
|
+
description: 'Forwards chart runtime state transitions.',
|
|
2446
|
+
},
|
|
2447
|
+
],
|
|
2448
|
+
};
|
|
2449
|
+
function providePraxisChartShowcaseWidgetMetadata() {
|
|
2450
|
+
return {
|
|
2451
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
2452
|
+
multi: true,
|
|
2453
|
+
useFactory: (registry) => () => {
|
|
2454
|
+
registry.register(PRAXIS_CHART_SHOWCASE_WIDGET_METADATA);
|
|
2455
|
+
},
|
|
2456
|
+
deps: [ComponentMetadataRegistry],
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
function providePraxisCharts() {
|
|
2461
|
+
return [
|
|
2462
|
+
EChartsEngineAdapter,
|
|
2463
|
+
{
|
|
2464
|
+
provide: PRAXIS_CHART_ENGINE,
|
|
2465
|
+
useExisting: EChartsEngineAdapter,
|
|
2466
|
+
},
|
|
2467
|
+
providePraxisChartsMetadata(),
|
|
2468
|
+
providePraxisChartDrilldownPanelMetadata(),
|
|
2469
|
+
providePraxisChartStateProbeMetadata(),
|
|
2470
|
+
providePraxisChartShowcaseWidgetMetadata(),
|
|
2471
|
+
];
|
|
1992
2472
|
}
|
|
1993
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCanonicalContractMapperService, decorators: [{
|
|
1994
|
-
type: Injectable,
|
|
1995
|
-
args: [{ providedIn: 'root' }]
|
|
1996
|
-
}] });
|
|
1997
2473
|
|
|
1998
2474
|
class PraxisChartMetadataRegistrationService {
|
|
1999
2475
|
registry;
|
|
@@ -2012,13 +2488,13 @@ class PraxisChartMetadataRegistrationService {
|
|
|
2012
2488
|
this.registry.register(PRAXIS_CHART_SHOWCASE_WIDGET_METADATA);
|
|
2013
2489
|
this.registered = true;
|
|
2014
2490
|
}
|
|
2015
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, deps: [{ token: i1$
|
|
2491
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, deps: [{ token: i1$2.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2016
2492
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, providedIn: 'root' });
|
|
2017
2493
|
}
|
|
2018
2494
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartMetadataRegistrationService, decorators: [{
|
|
2019
2495
|
type: Injectable,
|
|
2020
2496
|
args: [{ providedIn: 'root' }]
|
|
2021
|
-
}], ctorParameters: () => [{ type: i1$
|
|
2497
|
+
}], ctorParameters: () => [{ type: i1$2.ComponentMetadataRegistry }] });
|
|
2022
2498
|
|
|
2023
2499
|
class PraxisChartSchemaMapperService {
|
|
2024
2500
|
metadataRegistration;
|
|
@@ -2173,6 +2649,7 @@ class PraxisChartBackendPayloadAdapterService {
|
|
|
2173
2649
|
inputs: {
|
|
2174
2650
|
...(widget.definition.inputs || {}),
|
|
2175
2651
|
config: this.toChartConfig(payload),
|
|
2652
|
+
chartDocument: payload.widget.chart ?? null,
|
|
2176
2653
|
},
|
|
2177
2654
|
},
|
|
2178
2655
|
};
|
|
@@ -2200,6 +2677,7 @@ class PraxisChartBackendPayloadAdapterService {
|
|
|
2200
2677
|
inputs: {
|
|
2201
2678
|
...(widget.definition.inputs || {}),
|
|
2202
2679
|
config: this.toChartConfig(payload),
|
|
2680
|
+
chartDocument: payload.widget.chart ?? null,
|
|
2203
2681
|
},
|
|
2204
2682
|
},
|
|
2205
2683
|
};
|
|
@@ -2924,9 +3402,10 @@ function withShowcaseViewToggle(widget, initialViewMode = 'chart') {
|
|
|
2924
3402
|
id: 'praxis-chart-showcase-widget',
|
|
2925
3403
|
inputs: {
|
|
2926
3404
|
...(widget.definition.inputs || {}),
|
|
3405
|
+
enableCustomization: '${enableCustomization}',
|
|
2927
3406
|
viewMode: initialViewMode,
|
|
2928
3407
|
},
|
|
2929
|
-
bindingOrder: ['config', 'data', 'viewMode'],
|
|
3408
|
+
bindingOrder: ['config', 'chartDocument', 'data', 'enableCustomization', 'viewMode'],
|
|
2930
3409
|
},
|
|
2931
3410
|
shell: {
|
|
2932
3411
|
...(widget.shell || {}),
|
|
@@ -2962,15 +3441,17 @@ function buildShowcaseToggleActions() {
|
|
|
2962
3441
|
}
|
|
2963
3442
|
|
|
2964
3443
|
class PraxisChartCompositionShowcaseComponent {
|
|
3444
|
+
enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
2965
3445
|
layoutMode = signal('widget', ...(ngDevMode ? [{ debugName: "layoutMode" }] : []));
|
|
2966
3446
|
payloadMode = signal('group-by', ...(ngDevMode ? [{ debugName: "payloadMode" }] : []));
|
|
2967
3447
|
scenarioMode = signal('baseline', ...(ngDevMode ? [{ debugName: "scenarioMode" }] : []));
|
|
2968
|
-
|
|
3448
|
+
backendPayloadAdapter = inject(PraxisChartBackendPayloadAdapterService);
|
|
3449
|
+
runtimeContext = computed(() => ({
|
|
2969
3450
|
tenantId: 'demo-enterprise',
|
|
2970
3451
|
locale: 'pt-BR',
|
|
2971
3452
|
environment: 'published-api',
|
|
2972
|
-
|
|
2973
|
-
|
|
3453
|
+
enableCustomization: this.enableCustomization(),
|
|
3454
|
+
}), ...(ngDevMode ? [{ debugName: "runtimeContext" }] : []));
|
|
2974
3455
|
widgetPage = computed(() => {
|
|
2975
3456
|
switch (this.scenarioMode()) {
|
|
2976
3457
|
case 'interactive':
|
|
@@ -3123,7 +3604,7 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
3123
3604
|
}
|
|
3124
3605
|
}, ...(ngDevMode ? [{ debugName: "selectedPayload" }] : []));
|
|
3125
3606
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartCompositionShowcaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3126
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartCompositionShowcaseComponent, isStandalone: true, selector: "praxis-chart-composition-showcase", providers: [providePraxisCharts()], ngImport: i0, template: `
|
|
3607
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartCompositionShowcaseComponent, isStandalone: true, selector: "praxis-chart-composition-showcase", inputs: { enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisCharts()], ngImport: i0, template: `
|
|
3127
3608
|
<section class="showcase-shell">
|
|
3128
3609
|
<header class="showcase-hero">
|
|
3129
3610
|
<div class="showcase-copy">
|
|
@@ -3215,11 +3696,14 @@ class PraxisChartCompositionShowcaseComponent {
|
|
|
3215
3696
|
@if (layoutMode() === 'widget') {
|
|
3216
3697
|
<praxis-dynamic-page
|
|
3217
3698
|
[page]="widgetPage()"
|
|
3218
|
-
[context]="runtimeContext"
|
|
3699
|
+
[context]="runtimeContext()"
|
|
3219
3700
|
[autoPersist]="false"
|
|
3220
3701
|
></praxis-dynamic-page>
|
|
3221
3702
|
} @else {
|
|
3222
|
-
<praxis-dynamic-grid-page
|
|
3703
|
+
<praxis-dynamic-grid-page
|
|
3704
|
+
[page]="gridPage()"
|
|
3705
|
+
[context]="runtimeContext()"
|
|
3706
|
+
></praxis-dynamic-grid-page>
|
|
3223
3707
|
}
|
|
3224
3708
|
</article>
|
|
3225
3709
|
|
|
@@ -3332,11 +3816,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3332
3816
|
@if (layoutMode() === 'widget') {
|
|
3333
3817
|
<praxis-dynamic-page
|
|
3334
3818
|
[page]="widgetPage()"
|
|
3335
|
-
[context]="runtimeContext"
|
|
3819
|
+
[context]="runtimeContext()"
|
|
3336
3820
|
[autoPersist]="false"
|
|
3337
3821
|
></praxis-dynamic-page>
|
|
3338
3822
|
} @else {
|
|
3339
|
-
<praxis-dynamic-grid-page
|
|
3823
|
+
<praxis-dynamic-grid-page
|
|
3824
|
+
[page]="gridPage()"
|
|
3825
|
+
[context]="runtimeContext()"
|
|
3826
|
+
></praxis-dynamic-grid-page>
|
|
3340
3827
|
}
|
|
3341
3828
|
</article>
|
|
3342
3829
|
|
|
@@ -3354,8 +3841,1048 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3354
3841
|
</div>
|
|
3355
3842
|
</section>
|
|
3356
3843
|
`, styles: [":host{display:block}.showcase-shell{display:grid;gap:24px}.showcase-hero{display:grid;gap:20px;padding:28px;border-radius:28px;background:radial-gradient(circle at top right,rgba(18,99,180,.18),transparent 30%),linear-gradient(135deg,#071836f2,#1263b4c7);color:#f7fbff}.showcase-copy h2{margin:0 0 10px;font-size:clamp(1.8rem,3vw,2.6rem);line-height:1.05}.showcase-copy p{margin:0;max-width:52rem;color:#f7fbffe0}.showcase-eyebrow,.panel-kicker{margin:0 0 8px;font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:#f7fbffb8}.showcase-controls{display:flex;flex-wrap:wrap;gap:16px}.control-group{display:grid;gap:8px}.control-group span{font-size:.84rem;color:#f7fbffc2}.control-buttons{display:flex;flex-wrap:wrap;gap:8px}.control-buttons button{border:1px solid rgba(247,251,255,.22);background:#f7fbff14;color:#f7fbff;border-radius:999px;padding:10px 14px;cursor:pointer;transition:background .16s ease,transform .16s ease}.control-buttons button.active{background:#f7fbff;color:#0d2d5f}.showcase-grid{display:grid;gap:20px;grid-template-columns:minmax(0,1.3fr) minmax(320px,.9fr)}.runtime-panel,.payload-panel{display:grid;gap:16px;padding:20px;border-radius:24px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 76%,transparent);background:linear-gradient(180deg,#fffffff0,#f4f7fbf5)}.panel-header{display:flex;align-items:start;justify-content:space-between;gap:12px}.panel-header h3{margin:0;color:#142033}.panel-chip{border-radius:999px;padding:6px 10px;font-size:.76rem;background:#1263b41f;color:#1263b4}.payload-panel pre{margin:0;padding:16px;border-radius:18px;background:#09111f;color:#d7e6ff;overflow:auto;font-size:.84rem;line-height:1.5}@media(max-width:1080px){.showcase-grid{grid-template-columns:1fr}}\n"] }]
|
|
3844
|
+
}], propDecorators: { enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }] } });
|
|
3845
|
+
|
|
3846
|
+
const PRAXIS_CHARTS_EN_US = {
|
|
3847
|
+
'praxis.charts.runtime.editChart': 'Edit chart settings',
|
|
3848
|
+
'praxis.charts.runtime.invalidDocumentTitle': 'Invalid canonical configuration',
|
|
3849
|
+
'praxis.charts.runtime.invalidDocumentDescription': 'The canonical chart document could not be mapped to the current Praxis Charts runtime. Review the chart contract before continuing.',
|
|
3850
|
+
'praxis.charts.editor.section.general': 'General',
|
|
3851
|
+
'praxis.charts.editor.section.data': 'Data',
|
|
3852
|
+
'praxis.charts.editor.section.analytics': 'Analytics',
|
|
3853
|
+
'praxis.charts.editor.section.appearance': 'Appearance',
|
|
3854
|
+
'praxis.charts.editor.section.motion': 'Motion',
|
|
3855
|
+
'praxis.charts.editor.section.events': 'Events',
|
|
3856
|
+
'praxis.charts.editor.section.preview': 'Preview',
|
|
3857
|
+
'praxis.charts.editor.field.chartId': 'Chart ID',
|
|
3858
|
+
'praxis.charts.editor.field.kind': 'Kind',
|
|
3859
|
+
'praxis.charts.editor.field.title': 'Title',
|
|
3860
|
+
'praxis.charts.editor.field.subtitle': 'Subtitle',
|
|
3861
|
+
'praxis.charts.editor.field.height': 'Height',
|
|
3862
|
+
'praxis.charts.editor.field.sourceKind': 'Source',
|
|
3863
|
+
'praxis.charts.editor.field.resource': 'Resource',
|
|
3864
|
+
'praxis.charts.editor.field.operation': 'Operation',
|
|
3865
|
+
'praxis.charts.editor.field.granularity': 'Granularity',
|
|
3866
|
+
'praxis.charts.editor.field.fillGaps': 'Fill missing intervals',
|
|
3867
|
+
'praxis.charts.editor.field.distributionMode': 'Distribution mode',
|
|
3868
|
+
'praxis.charts.editor.field.bucketSize': 'Bucket size',
|
|
3869
|
+
'praxis.charts.editor.field.bucketCount': 'Bucket count',
|
|
3870
|
+
'praxis.charts.editor.field.dimension': 'Dimension',
|
|
3871
|
+
'praxis.charts.editor.field.dimensionRole': 'Dimension role',
|
|
3872
|
+
'praxis.charts.editor.field.metric': 'Metric',
|
|
3873
|
+
'praxis.charts.editor.field.metricLabel': 'Metric label',
|
|
3874
|
+
'praxis.charts.editor.field.metricAggregation': 'Aggregation',
|
|
3875
|
+
'praxis.charts.editor.field.metricAxis': 'Axis',
|
|
3876
|
+
'praxis.charts.editor.field.metricSeriesKind': 'Series kind',
|
|
3877
|
+
'praxis.charts.editor.field.legendEnabled': 'Show legend',
|
|
3878
|
+
'praxis.charts.editor.field.labelsEnabled': 'Show labels',
|
|
3879
|
+
'praxis.charts.editor.field.tooltipEnabled': 'Show tooltip',
|
|
3880
|
+
'praxis.charts.editor.field.palette': 'Palette colors',
|
|
3881
|
+
'praxis.charts.editor.field.emptyTitle': 'Empty title',
|
|
3882
|
+
'praxis.charts.editor.field.emptyDescription': 'Empty description',
|
|
3883
|
+
'praxis.charts.editor.field.loadingTitle': 'Loading title',
|
|
3884
|
+
'praxis.charts.editor.field.loadingDescription': 'Loading description',
|
|
3885
|
+
'praxis.charts.editor.field.errorTitle': 'Error title',
|
|
3886
|
+
'praxis.charts.editor.field.errorDescription': 'Error description',
|
|
3887
|
+
'praxis.charts.editor.field.motionEnabled': 'Enable animations',
|
|
3888
|
+
'praxis.charts.editor.field.motionPreset': 'Motion preset',
|
|
3889
|
+
'praxis.charts.editor.field.eventAction': 'Action',
|
|
3890
|
+
'praxis.charts.editor.field.eventTarget': 'Target',
|
|
3891
|
+
'praxis.charts.editor.field.eventMapping': 'Mapping',
|
|
3892
|
+
'praxis.charts.editor.preview.title': 'Chart preview',
|
|
3893
|
+
'praxis.charts.editor.preview.caption': 'Local preview derived from the canonical contract without remote calls.',
|
|
3894
|
+
'praxis.charts.editor.preview.invalid': 'Preview is unavailable while the contract has blocking errors.',
|
|
3895
|
+
'praxis.charts.editor.issues.title': 'Validation issues',
|
|
3896
|
+
'praxis.charts.editor.issues.empty': 'No issues were identified.',
|
|
3897
|
+
'praxis.charts.editor.appearance.featuresTitle': 'Display features',
|
|
3898
|
+
'praxis.charts.editor.appearance.paletteTitle': 'Palette',
|
|
3899
|
+
'praxis.charts.editor.appearance.paletteHint': 'Use comma or line separated colors to persist theme.palette as a canonical array.',
|
|
3900
|
+
'praxis.charts.editor.appearance.statesTitle': 'State messages',
|
|
3901
|
+
'praxis.charts.editor.analytics.dimensionsTitle': 'Dimensions',
|
|
3902
|
+
'praxis.charts.editor.analytics.metricsTitle': 'Metrics',
|
|
3903
|
+
'praxis.charts.editor.analytics.addDimension': 'Add dimension',
|
|
3904
|
+
'praxis.charts.editor.analytics.addMetric': 'Add metric',
|
|
3905
|
+
'praxis.charts.editor.analytics.removeDimension': 'Remove dimension',
|
|
3906
|
+
'praxis.charts.editor.analytics.removeMetric': 'Remove metric',
|
|
3907
|
+
'praxis.charts.editor.specialization.timeseriesTitle': 'Timeseries options',
|
|
3908
|
+
'praxis.charts.editor.specialization.distributionTitle': 'Distribution options',
|
|
3909
|
+
'praxis.charts.editor.specialization.comboTitle': 'Combo guidance',
|
|
3910
|
+
'praxis.charts.editor.specialization.comboHint': 'Combo charts require at least two metrics and allow per-metric axis and series kind mapping.',
|
|
3911
|
+
'praxis.charts.editor.specialization.pieDonutTitle': 'Composition guidance',
|
|
3912
|
+
'praxis.charts.editor.specialization.pieDonutHint': 'Pie and donut charts keep only the first metric and use the first dimension as the category segment.',
|
|
3913
|
+
'praxis.charts.editor.specialization.scatterTitle': 'Scatter guidance',
|
|
3914
|
+
'praxis.charts.editor.specialization.scatterHint': 'Scatter charts use the first dimension as X and the first metric as Y.',
|
|
3915
|
+
'praxis.charts.editor.events.pointClickTitle': 'Point click',
|
|
3916
|
+
'praxis.charts.editor.events.drillDownTitle': 'Drill down',
|
|
3917
|
+
'praxis.charts.editor.events.none': 'None',
|
|
3918
|
+
'praxis.charts.editor.sourceKind.praxisStats': 'praxis.stats',
|
|
3919
|
+
'praxis.charts.editor.sourceKind.derived': 'derived',
|
|
3920
|
+
'praxis.charts.editor.operation.groupBy': 'group-by',
|
|
3921
|
+
'praxis.charts.editor.operation.timeseries': 'timeseries',
|
|
3922
|
+
'praxis.charts.editor.operation.distribution': 'distribution',
|
|
3923
|
+
'praxis.charts.editor.granularity.hour': 'hour',
|
|
3924
|
+
'praxis.charts.editor.granularity.day': 'day',
|
|
3925
|
+
'praxis.charts.editor.granularity.week': 'week',
|
|
3926
|
+
'praxis.charts.editor.granularity.month': 'month',
|
|
3927
|
+
'praxis.charts.editor.granularity.quarter': 'quarter',
|
|
3928
|
+
'praxis.charts.editor.granularity.year': 'year',
|
|
3929
|
+
'praxis.charts.editor.distributionMode.terms': 'terms',
|
|
3930
|
+
'praxis.charts.editor.distributionMode.histogram': 'histogram',
|
|
3931
|
+
'praxis.charts.editor.motionPreset.subtle': 'subtle',
|
|
3932
|
+
'praxis.charts.editor.motionPreset.standard': 'standard',
|
|
3933
|
+
'praxis.charts.editor.motionPreset.expressive': 'expressive',
|
|
3934
|
+
'praxis.charts.editor.dimensionRole.category': 'category',
|
|
3935
|
+
'praxis.charts.editor.dimensionRole.series': 'series',
|
|
3936
|
+
'praxis.charts.editor.dimensionRole.segment': 'segment',
|
|
3937
|
+
'praxis.charts.editor.dimensionRole.time': 'time',
|
|
3938
|
+
'praxis.charts.editor.dimensionRole.value': 'value',
|
|
3939
|
+
'praxis.charts.editor.metricAggregation.sum': 'sum',
|
|
3940
|
+
'praxis.charts.editor.metricAggregation.count': 'count',
|
|
3941
|
+
'praxis.charts.editor.metricAggregation.avg': 'avg',
|
|
3942
|
+
'praxis.charts.editor.metricAggregation.min': 'min',
|
|
3943
|
+
'praxis.charts.editor.metricAggregation.max': 'max',
|
|
3944
|
+
'praxis.charts.editor.metricAxis.primary': 'primary',
|
|
3945
|
+
'praxis.charts.editor.metricAxis.secondary': 'secondary',
|
|
3946
|
+
'praxis.charts.editor.metricSeriesKind.bar': 'bar',
|
|
3947
|
+
'praxis.charts.editor.metricSeriesKind.line': 'line',
|
|
3948
|
+
'praxis.charts.editor.metricSeriesKind.area': 'area',
|
|
3949
|
+
'praxis.charts.editor.eventAction.filter-widget': 'filter-widget',
|
|
3950
|
+
'praxis.charts.editor.eventAction.open-detail': 'open-detail',
|
|
3951
|
+
'praxis.charts.editor.eventAction.navigate': 'navigate',
|
|
3952
|
+
'praxis.charts.editor.eventAction.update-context': 'update-context',
|
|
3953
|
+
'praxis.charts.editor.eventAction.emit': 'emit',
|
|
3954
|
+
'praxis.charts.editor.kind.bar': 'Bar',
|
|
3955
|
+
'praxis.charts.editor.kind.horizontal-bar': 'Horizontal Bar',
|
|
3956
|
+
'praxis.charts.editor.kind.line': 'Line',
|
|
3957
|
+
'praxis.charts.editor.kind.area': 'Area',
|
|
3958
|
+
'praxis.charts.editor.kind.stacked-bar': 'Stacked Bar',
|
|
3959
|
+
'praxis.charts.editor.kind.stacked-area': 'Stacked Area',
|
|
3960
|
+
'praxis.charts.editor.kind.combo': 'Combo',
|
|
3961
|
+
'praxis.charts.editor.kind.pie': 'Pie',
|
|
3962
|
+
'praxis.charts.editor.kind.donut': 'Donut',
|
|
3963
|
+
'praxis.charts.editor.kind.scatter': 'Scatter',
|
|
3964
|
+
};
|
|
3965
|
+
|
|
3966
|
+
const PRAXIS_CHARTS_PT_BR = {
|
|
3967
|
+
'praxis.charts.runtime.editChart': 'Editar configuracoes do chart',
|
|
3968
|
+
'praxis.charts.runtime.invalidDocumentTitle': 'Configuracao canonica invalida',
|
|
3969
|
+
'praxis.charts.runtime.invalidDocumentDescription': 'O documento canonico do chart nao pode ser mapeado para o runtime atual do Praxis Charts. Revise o contrato do chart antes de continuar.',
|
|
3970
|
+
'praxis.charts.editor.section.general': 'Geral',
|
|
3971
|
+
'praxis.charts.editor.section.data': 'Dados',
|
|
3972
|
+
'praxis.charts.editor.section.analytics': 'Estrutura',
|
|
3973
|
+
'praxis.charts.editor.section.appearance': 'Aparencia',
|
|
3974
|
+
'praxis.charts.editor.section.motion': 'Motion',
|
|
3975
|
+
'praxis.charts.editor.section.events': 'Eventos',
|
|
3976
|
+
'praxis.charts.editor.section.preview': 'Preview',
|
|
3977
|
+
'praxis.charts.editor.field.chartId': 'Chart ID',
|
|
3978
|
+
'praxis.charts.editor.field.kind': 'Tipo',
|
|
3979
|
+
'praxis.charts.editor.field.title': 'Titulo',
|
|
3980
|
+
'praxis.charts.editor.field.subtitle': 'Subtitulo',
|
|
3981
|
+
'praxis.charts.editor.field.height': 'Altura',
|
|
3982
|
+
'praxis.charts.editor.field.sourceKind': 'Fonte',
|
|
3983
|
+
'praxis.charts.editor.field.resource': 'Recurso',
|
|
3984
|
+
'praxis.charts.editor.field.operation': 'Operacao',
|
|
3985
|
+
'praxis.charts.editor.field.granularity': 'Granularidade',
|
|
3986
|
+
'praxis.charts.editor.field.fillGaps': 'Preencher intervalos sem dados',
|
|
3987
|
+
'praxis.charts.editor.field.distributionMode': 'Modo da distribuicao',
|
|
3988
|
+
'praxis.charts.editor.field.bucketSize': 'Tamanho do bucket',
|
|
3989
|
+
'praxis.charts.editor.field.bucketCount': 'Quantidade de buckets',
|
|
3990
|
+
'praxis.charts.editor.field.dimension': 'Dimensao',
|
|
3991
|
+
'praxis.charts.editor.field.dimensionRole': 'Papel da dimensao',
|
|
3992
|
+
'praxis.charts.editor.field.metric': 'Metrica',
|
|
3993
|
+
'praxis.charts.editor.field.metricLabel': 'Label da metrica',
|
|
3994
|
+
'praxis.charts.editor.field.metricAggregation': 'Agregacao',
|
|
3995
|
+
'praxis.charts.editor.field.metricAxis': 'Eixo',
|
|
3996
|
+
'praxis.charts.editor.field.metricSeriesKind': 'Tipo da serie',
|
|
3997
|
+
'praxis.charts.editor.field.legendEnabled': 'Exibir legenda',
|
|
3998
|
+
'praxis.charts.editor.field.labelsEnabled': 'Exibir labels',
|
|
3999
|
+
'praxis.charts.editor.field.tooltipEnabled': 'Exibir tooltip',
|
|
4000
|
+
'praxis.charts.editor.field.palette': 'Cores da paleta',
|
|
4001
|
+
'praxis.charts.editor.field.emptyTitle': 'Titulo do estado vazio',
|
|
4002
|
+
'praxis.charts.editor.field.emptyDescription': 'Descricao do estado vazio',
|
|
4003
|
+
'praxis.charts.editor.field.loadingTitle': 'Titulo do loading',
|
|
4004
|
+
'praxis.charts.editor.field.loadingDescription': 'Descricao do loading',
|
|
4005
|
+
'praxis.charts.editor.field.errorTitle': 'Titulo do erro',
|
|
4006
|
+
'praxis.charts.editor.field.errorDescription': 'Descricao do erro',
|
|
4007
|
+
'praxis.charts.editor.field.motionEnabled': 'Ativar animacoes',
|
|
4008
|
+
'praxis.charts.editor.field.motionPreset': 'Preset de motion',
|
|
4009
|
+
'praxis.charts.editor.field.eventAction': 'Acao',
|
|
4010
|
+
'praxis.charts.editor.field.eventTarget': 'Destino',
|
|
4011
|
+
'praxis.charts.editor.field.eventMapping': 'Mapeamento',
|
|
4012
|
+
'praxis.charts.editor.preview.title': 'Preview do chart',
|
|
4013
|
+
'praxis.charts.editor.preview.caption': 'Preview local derivado do contrato canonico, sem chamadas remotas.',
|
|
4014
|
+
'praxis.charts.editor.preview.invalid': 'O preview fica indisponivel enquanto houver erro bloqueante no contrato.',
|
|
4015
|
+
'praxis.charts.editor.issues.title': 'Issues de validacao',
|
|
4016
|
+
'praxis.charts.editor.issues.empty': 'Nenhum issue identificado.',
|
|
4017
|
+
'praxis.charts.editor.appearance.featuresTitle': 'Recursos visuais',
|
|
4018
|
+
'praxis.charts.editor.appearance.paletteTitle': 'Paleta',
|
|
4019
|
+
'praxis.charts.editor.appearance.paletteHint': 'Use cores separadas por virgula ou linha para persistir `theme.palette` como array canonico.',
|
|
4020
|
+
'praxis.charts.editor.appearance.statesTitle': 'Mensagens de estado',
|
|
4021
|
+
'praxis.charts.editor.analytics.dimensionsTitle': 'Dimensoes',
|
|
4022
|
+
'praxis.charts.editor.analytics.metricsTitle': 'Metricas',
|
|
4023
|
+
'praxis.charts.editor.analytics.addDimension': 'Adicionar dimensao',
|
|
4024
|
+
'praxis.charts.editor.analytics.addMetric': 'Adicionar metrica',
|
|
4025
|
+
'praxis.charts.editor.analytics.removeDimension': 'Remover dimensao',
|
|
4026
|
+
'praxis.charts.editor.analytics.removeMetric': 'Remover metrica',
|
|
4027
|
+
'praxis.charts.editor.specialization.timeseriesTitle': 'Opcoes de series temporais',
|
|
4028
|
+
'praxis.charts.editor.specialization.distributionTitle': 'Opcoes de distribuicao',
|
|
4029
|
+
'praxis.charts.editor.specialization.comboTitle': 'Guia de combo',
|
|
4030
|
+
'praxis.charts.editor.specialization.comboHint': 'Charts combo exigem pelo menos duas metricas e permitem configurar eixo e tipo de serie por metrica.',
|
|
4031
|
+
'praxis.charts.editor.specialization.pieDonutTitle': 'Guia de composicao',
|
|
4032
|
+
'praxis.charts.editor.specialization.pieDonutHint': 'Charts pie e donut mantem apenas a primeira metrica e usam a primeira dimensao como segmento categorico.',
|
|
4033
|
+
'praxis.charts.editor.specialization.scatterTitle': 'Guia de scatter',
|
|
4034
|
+
'praxis.charts.editor.specialization.scatterHint': 'Charts scatter usam a primeira dimensao como eixo X e a primeira metrica como eixo Y.',
|
|
4035
|
+
'praxis.charts.editor.events.pointClickTitle': 'Clique no ponto',
|
|
4036
|
+
'praxis.charts.editor.events.drillDownTitle': 'Drill down',
|
|
4037
|
+
'praxis.charts.editor.events.none': 'Nenhuma',
|
|
4038
|
+
'praxis.charts.editor.sourceKind.praxisStats': 'praxis.stats',
|
|
4039
|
+
'praxis.charts.editor.sourceKind.derived': 'derived',
|
|
4040
|
+
'praxis.charts.editor.operation.groupBy': 'group-by',
|
|
4041
|
+
'praxis.charts.editor.operation.timeseries': 'timeseries',
|
|
4042
|
+
'praxis.charts.editor.operation.distribution': 'distribution',
|
|
4043
|
+
'praxis.charts.editor.granularity.hour': 'hour',
|
|
4044
|
+
'praxis.charts.editor.granularity.day': 'day',
|
|
4045
|
+
'praxis.charts.editor.granularity.week': 'week',
|
|
4046
|
+
'praxis.charts.editor.granularity.month': 'month',
|
|
4047
|
+
'praxis.charts.editor.granularity.quarter': 'quarter',
|
|
4048
|
+
'praxis.charts.editor.granularity.year': 'year',
|
|
4049
|
+
'praxis.charts.editor.distributionMode.terms': 'terms',
|
|
4050
|
+
'praxis.charts.editor.distributionMode.histogram': 'histogram',
|
|
4051
|
+
'praxis.charts.editor.motionPreset.subtle': 'sutil',
|
|
4052
|
+
'praxis.charts.editor.motionPreset.standard': 'padrao',
|
|
4053
|
+
'praxis.charts.editor.motionPreset.expressive': 'expressivo',
|
|
4054
|
+
'praxis.charts.editor.dimensionRole.category': 'category',
|
|
4055
|
+
'praxis.charts.editor.dimensionRole.series': 'series',
|
|
4056
|
+
'praxis.charts.editor.dimensionRole.segment': 'segment',
|
|
4057
|
+
'praxis.charts.editor.dimensionRole.time': 'time',
|
|
4058
|
+
'praxis.charts.editor.dimensionRole.value': 'value',
|
|
4059
|
+
'praxis.charts.editor.metricAggregation.sum': 'sum',
|
|
4060
|
+
'praxis.charts.editor.metricAggregation.count': 'count',
|
|
4061
|
+
'praxis.charts.editor.metricAggregation.avg': 'avg',
|
|
4062
|
+
'praxis.charts.editor.metricAggregation.min': 'min',
|
|
4063
|
+
'praxis.charts.editor.metricAggregation.max': 'max',
|
|
4064
|
+
'praxis.charts.editor.metricAxis.primary': 'primary',
|
|
4065
|
+
'praxis.charts.editor.metricAxis.secondary': 'secondary',
|
|
4066
|
+
'praxis.charts.editor.metricSeriesKind.bar': 'bar',
|
|
4067
|
+
'praxis.charts.editor.metricSeriesKind.line': 'line',
|
|
4068
|
+
'praxis.charts.editor.metricSeriesKind.area': 'area',
|
|
4069
|
+
'praxis.charts.editor.eventAction.filter-widget': 'filter-widget',
|
|
4070
|
+
'praxis.charts.editor.eventAction.open-detail': 'open-detail',
|
|
4071
|
+
'praxis.charts.editor.eventAction.navigate': 'navigate',
|
|
4072
|
+
'praxis.charts.editor.eventAction.update-context': 'update-context',
|
|
4073
|
+
'praxis.charts.editor.eventAction.emit': 'emit',
|
|
4074
|
+
'praxis.charts.editor.kind.bar': 'Bar',
|
|
4075
|
+
'praxis.charts.editor.kind.horizontal-bar': 'Horizontal Bar',
|
|
4076
|
+
'praxis.charts.editor.kind.line': 'Line',
|
|
4077
|
+
'praxis.charts.editor.kind.area': 'Area',
|
|
4078
|
+
'praxis.charts.editor.kind.stacked-bar': 'Stacked Bar',
|
|
4079
|
+
'praxis.charts.editor.kind.stacked-area': 'Stacked Area',
|
|
4080
|
+
'praxis.charts.editor.kind.combo': 'Combo',
|
|
4081
|
+
'praxis.charts.editor.kind.pie': 'Pie',
|
|
4082
|
+
'praxis.charts.editor.kind.donut': 'Donut',
|
|
4083
|
+
'praxis.charts.editor.kind.scatter': 'Scatter',
|
|
4084
|
+
};
|
|
4085
|
+
|
|
4086
|
+
const PRAXIS_CHARTS_I18N = new InjectionToken('PRAXIS_CHARTS_I18N', {
|
|
4087
|
+
factory: () => ({}),
|
|
4088
|
+
});
|
|
4089
|
+
function createPraxisChartsI18nConfig(options = {}) {
|
|
4090
|
+
const localeDictionaries = {
|
|
4091
|
+
'pt-BR': {
|
|
4092
|
+
...PRAXIS_CHARTS_PT_BR,
|
|
4093
|
+
...(options.dictionaries?.['pt-BR'] ?? {}),
|
|
4094
|
+
},
|
|
4095
|
+
'en-US': {
|
|
4096
|
+
...PRAXIS_CHARTS_EN_US,
|
|
4097
|
+
...(options.dictionaries?.['en-US'] ?? {}),
|
|
4098
|
+
},
|
|
4099
|
+
};
|
|
4100
|
+
for (const [locale, dictionary] of Object.entries(options.dictionaries ?? {})) {
|
|
4101
|
+
if (locale === 'pt-BR' || locale === 'en-US') {
|
|
4102
|
+
continue;
|
|
4103
|
+
}
|
|
4104
|
+
localeDictionaries[locale] = {
|
|
4105
|
+
...(localeDictionaries[locale] ?? {}),
|
|
4106
|
+
...dictionary,
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
return {
|
|
4110
|
+
locale: options.locale,
|
|
4111
|
+
fallbackLocale: options.fallbackLocale ?? 'pt-BR',
|
|
4112
|
+
namespaces: {
|
|
4113
|
+
charts: localeDictionaries,
|
|
4114
|
+
},
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
function providePraxisChartsI18n(options = {}) {
|
|
4118
|
+
return [
|
|
4119
|
+
{
|
|
4120
|
+
provide: PRAXIS_CHARTS_I18N,
|
|
4121
|
+
useValue: {},
|
|
4122
|
+
},
|
|
4123
|
+
providePraxisI18n(createPraxisChartsI18nConfig(options)),
|
|
4124
|
+
];
|
|
4125
|
+
}
|
|
4126
|
+
function resolvePraxisChartsText(value, fallback) {
|
|
4127
|
+
if (typeof value === 'string') {
|
|
4128
|
+
return { text: value };
|
|
4129
|
+
}
|
|
4130
|
+
if (value?.key || value?.text) {
|
|
4131
|
+
return value;
|
|
4132
|
+
}
|
|
4133
|
+
return { text: fallback ?? '' };
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
class ChartEditorDefaultsService {
|
|
4137
|
+
create() {
|
|
4138
|
+
return {
|
|
4139
|
+
version: '1.0.0',
|
|
4140
|
+
kind: 'bar',
|
|
4141
|
+
title: 'Untitled chart',
|
|
4142
|
+
source: {
|
|
4143
|
+
kind: 'derived',
|
|
4144
|
+
},
|
|
4145
|
+
dimensions: [{ field: 'category', role: 'category' }],
|
|
4146
|
+
metrics: [{ field: 'value', aggregation: 'sum', label: 'Value' }],
|
|
4147
|
+
legend: { enabled: true },
|
|
4148
|
+
labels: { enabled: false },
|
|
4149
|
+
tooltip: { enabled: true },
|
|
4150
|
+
motion: {
|
|
4151
|
+
enabled: true,
|
|
4152
|
+
preset: 'standard',
|
|
4153
|
+
},
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartEditorDefaultsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4157
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartEditorDefaultsService, providedIn: 'root' });
|
|
4158
|
+
}
|
|
4159
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartEditorDefaultsService, decorators: [{
|
|
4160
|
+
type: Injectable,
|
|
4161
|
+
args: [{ providedIn: 'root' }]
|
|
3357
4162
|
}] });
|
|
3358
4163
|
|
|
4164
|
+
class ChartEditorPreviewMapperService {
|
|
4165
|
+
mapper;
|
|
4166
|
+
constructor(mapper) {
|
|
4167
|
+
this.mapper = mapper;
|
|
4168
|
+
}
|
|
4169
|
+
build(document) {
|
|
4170
|
+
const config = this.mapper.toPraxisChartConfig(document);
|
|
4171
|
+
return {
|
|
4172
|
+
config,
|
|
4173
|
+
data: this.buildRows(document),
|
|
4174
|
+
};
|
|
4175
|
+
}
|
|
4176
|
+
buildRows(document) {
|
|
4177
|
+
const dimensionField = document.dimensions?.[0]?.field?.trim();
|
|
4178
|
+
const metrics = document.metrics ?? [];
|
|
4179
|
+
if (!dimensionField || !metrics.length) {
|
|
4180
|
+
return [];
|
|
4181
|
+
}
|
|
4182
|
+
if (document.kind === 'scatter') {
|
|
4183
|
+
return metrics[0]?.field
|
|
4184
|
+
? [
|
|
4185
|
+
{ [dimensionField]: 10, [metrics[0].field]: 22 },
|
|
4186
|
+
{ [dimensionField]: 18, [metrics[0].field]: 31 },
|
|
4187
|
+
{ [dimensionField]: 27, [metrics[0].field]: 45 },
|
|
4188
|
+
]
|
|
4189
|
+
: [];
|
|
4190
|
+
}
|
|
4191
|
+
const categories = document.source.kind === 'praxis.stats' && document.source.operation === 'timeseries'
|
|
4192
|
+
? ['2026-01', '2026-02', '2026-03']
|
|
4193
|
+
: ['Alpha', 'Beta', 'Gamma'];
|
|
4194
|
+
return categories.map((category, index) => {
|
|
4195
|
+
const row = {
|
|
4196
|
+
[dimensionField]: category,
|
|
4197
|
+
};
|
|
4198
|
+
metrics.forEach((metric, metricIndex) => {
|
|
4199
|
+
if (!metric.field) {
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
row[metric.field] = (index + 1) * 10 * (metricIndex + 1) + metricIndex * 5;
|
|
4203
|
+
});
|
|
4204
|
+
return row;
|
|
4205
|
+
});
|
|
4206
|
+
}
|
|
4207
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartEditorPreviewMapperService, deps: [{ token: PraxisChartCanonicalContractMapperService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4208
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartEditorPreviewMapperService, providedIn: 'root' });
|
|
4209
|
+
}
|
|
4210
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChartEditorPreviewMapperService, decorators: [{
|
|
4211
|
+
type: Injectable,
|
|
4212
|
+
args: [{ providedIn: 'root' }]
|
|
4213
|
+
}], ctorParameters: () => [{ type: PraxisChartCanonicalContractMapperService }] });
|
|
4214
|
+
|
|
4215
|
+
class PraxisChartConfigEditor {
|
|
4216
|
+
documentInput = input(null, ...(ngDevMode ? [{ debugName: "documentInput", alias: 'document' }] : [{ alias: 'document' }]));
|
|
4217
|
+
modeInput = input('edit', ...(ngDevMode ? [{ debugName: "modeInput", alias: 'mode' }] : [{ alias: 'mode' }]));
|
|
4218
|
+
readonlyInput = input(false, ...(ngDevMode ? [{ debugName: "readonlyInput", alias: 'readonly' }] : [{ alias: 'readonly' }]));
|
|
4219
|
+
availableResourcesInput = input([], ...(ngDevMode ? [{ debugName: "availableResourcesInput", alias: 'availableResources' }] : [{ alias: 'availableResources' }]));
|
|
4220
|
+
availableFieldsInput = input([], ...(ngDevMode ? [{ debugName: "availableFieldsInput", alias: 'availableFields' }] : [{ alias: 'availableFields' }]));
|
|
4221
|
+
availableTargetsInput = input([], ...(ngDevMode ? [{ debugName: "availableTargetsInput", alias: 'availableTargets' }] : [{ alias: 'availableTargets' }]));
|
|
4222
|
+
apply = output();
|
|
4223
|
+
save = output();
|
|
4224
|
+
resetChange = output();
|
|
4225
|
+
documentChange = output();
|
|
4226
|
+
isDirty$ = new BehaviorSubject(false);
|
|
4227
|
+
isValid$ = new BehaviorSubject(true);
|
|
4228
|
+
isBusy$ = new BehaviorSubject(false);
|
|
4229
|
+
sections = [
|
|
4230
|
+
{ id: 'general', labelKey: 'praxis.charts.editor.section.general', fallback: 'General' },
|
|
4231
|
+
{ id: 'data', labelKey: 'praxis.charts.editor.section.data', fallback: 'Data' },
|
|
4232
|
+
{ id: 'analytics', labelKey: 'praxis.charts.editor.section.analytics', fallback: 'Analytics' },
|
|
4233
|
+
{ id: 'appearance', labelKey: 'praxis.charts.editor.section.appearance', fallback: 'Appearance' },
|
|
4234
|
+
{ id: 'motion', labelKey: 'praxis.charts.editor.section.motion', fallback: 'Motion' },
|
|
4235
|
+
{ id: 'events', labelKey: 'praxis.charts.editor.section.events', fallback: 'Events' },
|
|
4236
|
+
{ id: 'preview', labelKey: 'praxis.charts.editor.section.preview', fallback: 'Preview' },
|
|
4237
|
+
];
|
|
4238
|
+
chartKinds = [
|
|
4239
|
+
'bar',
|
|
4240
|
+
'horizontal-bar',
|
|
4241
|
+
'line',
|
|
4242
|
+
'area',
|
|
4243
|
+
'stacked-bar',
|
|
4244
|
+
'stacked-area',
|
|
4245
|
+
'combo',
|
|
4246
|
+
'pie',
|
|
4247
|
+
'donut',
|
|
4248
|
+
'scatter',
|
|
4249
|
+
];
|
|
4250
|
+
sourceKinds = ['praxis.stats', 'derived'];
|
|
4251
|
+
operations = ['group-by', 'timeseries', 'distribution'];
|
|
4252
|
+
timeGranularities = ['hour', 'day', 'week', 'month', 'quarter', 'year'];
|
|
4253
|
+
distributionModes = ['terms', 'histogram'];
|
|
4254
|
+
dimensionRoles = ['category', 'series', 'segment', 'time', 'value'];
|
|
4255
|
+
metricAggregations = ['sum', 'count', 'avg', 'min', 'max'];
|
|
4256
|
+
metricAxes = ['primary', 'secondary'];
|
|
4257
|
+
metricSeriesKinds = ['bar', 'line', 'area'];
|
|
4258
|
+
motionPresets = ['subtle', 'standard', 'expressive'];
|
|
4259
|
+
eventActionOptions = ['filter-widget', 'open-detail', 'navigate', 'update-context', 'emit'];
|
|
4260
|
+
activeSection = signal('general', ...(ngDevMode ? [{ debugName: "activeSection" }] : []));
|
|
4261
|
+
injectedData = inject(SETTINGS_PANEL_DATA, { optional: true });
|
|
4262
|
+
defaults = inject(ChartEditorDefaultsService);
|
|
4263
|
+
normalizer = inject(ChartContractNormalizerService);
|
|
4264
|
+
validator = inject(ChartContractValidationService);
|
|
4265
|
+
previewMapper = inject(ChartEditorPreviewMapperService);
|
|
4266
|
+
i18n = inject(PraxisI18nService);
|
|
4267
|
+
currentDocument = signal(this.defaults.create(), ...(ngDevMode ? [{ debugName: "currentDocument" }] : []));
|
|
4268
|
+
initialDocument = signal(this.defaults.create(), ...(ngDevMode ? [{ debugName: "initialDocument" }] : []));
|
|
4269
|
+
lastExternalSignature = null;
|
|
4270
|
+
normalizedDocument = computed(() => this.normalizer.normalize(this.currentDocument()), ...(ngDevMode ? [{ debugName: "normalizedDocument" }] : []));
|
|
4271
|
+
validation = computed(() => this.validator.validate(this.normalizedDocument()), ...(ngDevMode ? [{ debugName: "validation" }] : []));
|
|
4272
|
+
issues = computed(() => this.validation().issues, ...(ngDevMode ? [{ debugName: "issues" }] : []));
|
|
4273
|
+
availableResources = computed(() => this.availableResourcesInput().length
|
|
4274
|
+
? this.availableResourcesInput()
|
|
4275
|
+
: (this.injectedData?.availableResources ?? []), ...(ngDevMode ? [{ debugName: "availableResources" }] : []));
|
|
4276
|
+
availableFields = computed(() => this.availableFieldsInput().length
|
|
4277
|
+
? this.availableFieldsInput()
|
|
4278
|
+
: (this.injectedData?.availableFields ?? []), ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
|
|
4279
|
+
availableTargets = computed(() => this.availableTargetsInput().length
|
|
4280
|
+
? this.availableTargetsInput()
|
|
4281
|
+
: (this.injectedData?.availableTargets ?? []), ...(ngDevMode ? [{ debugName: "availableTargets" }] : []));
|
|
4282
|
+
preview = computed(() => {
|
|
4283
|
+
if (!this.validation().valid) {
|
|
4284
|
+
return null;
|
|
4285
|
+
}
|
|
4286
|
+
return this.previewMapper.build(this.normalizedDocument());
|
|
4287
|
+
}, ...(ngDevMode ? [{ debugName: "preview" }] : []));
|
|
4288
|
+
constructor() {
|
|
4289
|
+
effect(() => {
|
|
4290
|
+
const externalDocument = this.documentInput()
|
|
4291
|
+
?? this.injectedData?.chartDocument
|
|
4292
|
+
?? this.injectedData?.document
|
|
4293
|
+
?? null;
|
|
4294
|
+
if (!externalDocument) {
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
const signature = JSON.stringify(externalDocument);
|
|
4298
|
+
if (signature === this.lastExternalSignature || this.isDirty$.getValue()) {
|
|
4299
|
+
return;
|
|
4300
|
+
}
|
|
4301
|
+
const normalized = this.normalizer.normalize(externalDocument);
|
|
4302
|
+
this.currentDocument.set(structuredClone(normalized));
|
|
4303
|
+
this.initialDocument.set(structuredClone(normalized));
|
|
4304
|
+
this.lastExternalSignature = signature;
|
|
4305
|
+
this.refreshState(false);
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
getSettingsValue() {
|
|
4309
|
+
return structuredClone(this.normalizedDocument());
|
|
4310
|
+
}
|
|
4311
|
+
onSave() {
|
|
4312
|
+
return this.saveChanges().document;
|
|
4313
|
+
}
|
|
4314
|
+
reset() {
|
|
4315
|
+
const snapshot = structuredClone(this.initialDocument());
|
|
4316
|
+
this.currentDocument.set(snapshot);
|
|
4317
|
+
this.refreshState(false);
|
|
4318
|
+
this.resetChange.emit({ document: snapshot });
|
|
4319
|
+
}
|
|
4320
|
+
applyChanges() {
|
|
4321
|
+
const payload = this.createApplyPayload();
|
|
4322
|
+
this.apply.emit(payload);
|
|
4323
|
+
return payload;
|
|
4324
|
+
}
|
|
4325
|
+
saveChanges() {
|
|
4326
|
+
const payload = this.createSavePayload();
|
|
4327
|
+
this.initialDocument.set(structuredClone(payload.document));
|
|
4328
|
+
this.refreshState(false);
|
|
4329
|
+
this.save.emit(payload);
|
|
4330
|
+
return payload;
|
|
4331
|
+
}
|
|
4332
|
+
setSection(section) {
|
|
4333
|
+
this.activeSection.set(section);
|
|
4334
|
+
}
|
|
4335
|
+
setChartId(value) {
|
|
4336
|
+
this.patchDocument((document) => ({
|
|
4337
|
+
...document,
|
|
4338
|
+
chartId: value.trim() || undefined,
|
|
4339
|
+
}));
|
|
4340
|
+
}
|
|
4341
|
+
setKind(value) {
|
|
4342
|
+
this.patchDocument((document) => ({
|
|
4343
|
+
...document,
|
|
4344
|
+
kind: value,
|
|
4345
|
+
}));
|
|
4346
|
+
}
|
|
4347
|
+
setTitle(value) {
|
|
4348
|
+
this.patchDocument((document) => ({
|
|
4349
|
+
...document,
|
|
4350
|
+
title: value.trim() || undefined,
|
|
4351
|
+
}));
|
|
4352
|
+
}
|
|
4353
|
+
setSubtitle(value) {
|
|
4354
|
+
this.patchDocument((document) => ({
|
|
4355
|
+
...document,
|
|
4356
|
+
subtitle: value.trim() || undefined,
|
|
4357
|
+
}));
|
|
4358
|
+
}
|
|
4359
|
+
setHeight(value) {
|
|
4360
|
+
this.patchDocument((document) => ({
|
|
4361
|
+
...document,
|
|
4362
|
+
height: value.trim() || undefined,
|
|
4363
|
+
}));
|
|
4364
|
+
}
|
|
4365
|
+
setSourceKind(value) {
|
|
4366
|
+
this.patchDocument((document) => ({
|
|
4367
|
+
...document,
|
|
4368
|
+
source: value === 'derived'
|
|
4369
|
+
? { kind: 'derived' }
|
|
4370
|
+
: {
|
|
4371
|
+
kind: 'praxis.stats',
|
|
4372
|
+
resource: document.source.kind === 'praxis.stats' ? document.source.resource : '',
|
|
4373
|
+
operation: document.source.kind === 'praxis.stats' ? document.source.operation ?? 'group-by' : 'group-by',
|
|
4374
|
+
options: document.source.kind === 'praxis.stats' ? document.source.options : undefined,
|
|
4375
|
+
},
|
|
4376
|
+
}));
|
|
4377
|
+
}
|
|
4378
|
+
setResource(value) {
|
|
4379
|
+
this.patchDocument((document) => ({
|
|
4380
|
+
...document,
|
|
4381
|
+
source: document.source.kind === 'praxis.stats'
|
|
4382
|
+
? {
|
|
4383
|
+
...document.source,
|
|
4384
|
+
resource: value.trim() || undefined,
|
|
4385
|
+
}
|
|
4386
|
+
: document.source,
|
|
4387
|
+
}));
|
|
4388
|
+
}
|
|
4389
|
+
setOperation(value) {
|
|
4390
|
+
this.patchDocument((document) => ({
|
|
4391
|
+
...document,
|
|
4392
|
+
source: document.source.kind === 'praxis.stats'
|
|
4393
|
+
? {
|
|
4394
|
+
...document.source,
|
|
4395
|
+
operation: value,
|
|
4396
|
+
}
|
|
4397
|
+
: document.source,
|
|
4398
|
+
}));
|
|
4399
|
+
}
|
|
4400
|
+
setGranularity(value) {
|
|
4401
|
+
this.patchDocument((document) => ({
|
|
4402
|
+
...document,
|
|
4403
|
+
source: document.source.kind === 'praxis.stats'
|
|
4404
|
+
? {
|
|
4405
|
+
...document.source,
|
|
4406
|
+
options: {
|
|
4407
|
+
...(document.source.options ?? {}),
|
|
4408
|
+
granularity: value,
|
|
4409
|
+
},
|
|
4410
|
+
}
|
|
4411
|
+
: document.source,
|
|
4412
|
+
}));
|
|
4413
|
+
}
|
|
4414
|
+
setFillGaps(value) {
|
|
4415
|
+
this.patchDocument((document) => ({
|
|
4416
|
+
...document,
|
|
4417
|
+
source: document.source.kind === 'praxis.stats'
|
|
4418
|
+
? {
|
|
4419
|
+
...document.source,
|
|
4420
|
+
options: {
|
|
4421
|
+
...(document.source.options ?? {}),
|
|
4422
|
+
fillGaps: value,
|
|
4423
|
+
},
|
|
4424
|
+
}
|
|
4425
|
+
: document.source,
|
|
4426
|
+
}));
|
|
4427
|
+
}
|
|
4428
|
+
setDistributionMode(value) {
|
|
4429
|
+
this.patchDocument((document) => ({
|
|
4430
|
+
...document,
|
|
4431
|
+
source: document.source.kind === 'praxis.stats'
|
|
4432
|
+
? {
|
|
4433
|
+
...document.source,
|
|
4434
|
+
options: {
|
|
4435
|
+
...(document.source.options ?? {}),
|
|
4436
|
+
mode: value,
|
|
4437
|
+
},
|
|
4438
|
+
}
|
|
4439
|
+
: document.source,
|
|
4440
|
+
}));
|
|
4441
|
+
}
|
|
4442
|
+
setBucketSize(value) {
|
|
4443
|
+
const parsed = Number(value);
|
|
4444
|
+
this.patchDocument((document) => ({
|
|
4445
|
+
...document,
|
|
4446
|
+
source: document.source.kind === 'praxis.stats'
|
|
4447
|
+
? {
|
|
4448
|
+
...document.source,
|
|
4449
|
+
options: {
|
|
4450
|
+
...(document.source.options ?? {}),
|
|
4451
|
+
bucketSize: Number.isFinite(parsed) && value.trim() !== '' ? parsed : undefined,
|
|
4452
|
+
},
|
|
4453
|
+
}
|
|
4454
|
+
: document.source,
|
|
4455
|
+
}));
|
|
4456
|
+
}
|
|
4457
|
+
setBucketCount(value) {
|
|
4458
|
+
const parsed = Number(value);
|
|
4459
|
+
this.patchDocument((document) => ({
|
|
4460
|
+
...document,
|
|
4461
|
+
source: document.source.kind === 'praxis.stats'
|
|
4462
|
+
? {
|
|
4463
|
+
...document.source,
|
|
4464
|
+
options: {
|
|
4465
|
+
...(document.source.options ?? {}),
|
|
4466
|
+
bucketCount: Number.isFinite(parsed) && value.trim() !== '' ? parsed : undefined,
|
|
4467
|
+
},
|
|
4468
|
+
}
|
|
4469
|
+
: document.source,
|
|
4470
|
+
}));
|
|
4471
|
+
}
|
|
4472
|
+
addDimension() {
|
|
4473
|
+
this.patchDocument((document) => ({
|
|
4474
|
+
...document,
|
|
4475
|
+
dimensions: [
|
|
4476
|
+
...(document.dimensions ?? []),
|
|
4477
|
+
{ field: '', role: 'category' },
|
|
4478
|
+
],
|
|
4479
|
+
}));
|
|
4480
|
+
}
|
|
4481
|
+
removeDimension(index) {
|
|
4482
|
+
this.patchDocument((document) => ({
|
|
4483
|
+
...document,
|
|
4484
|
+
dimensions: (document.dimensions ?? []).filter((_, currentIndex) => currentIndex !== index),
|
|
4485
|
+
}));
|
|
4486
|
+
}
|
|
4487
|
+
setDimensionField(index, field) {
|
|
4488
|
+
this.patchDocument((document) => ({
|
|
4489
|
+
...document,
|
|
4490
|
+
dimensions: (document.dimensions ?? []).map((dimension, currentIndex) => currentIndex === index
|
|
4491
|
+
? { ...dimension, field: field.trim() || '' }
|
|
4492
|
+
: dimension),
|
|
4493
|
+
}));
|
|
4494
|
+
}
|
|
4495
|
+
setDimensionRole(index, role) {
|
|
4496
|
+
this.patchDocument((document) => ({
|
|
4497
|
+
...document,
|
|
4498
|
+
dimensions: (document.dimensions ?? []).map((dimension, currentIndex) => currentIndex === index
|
|
4499
|
+
? { ...dimension, role }
|
|
4500
|
+
: dimension),
|
|
4501
|
+
}));
|
|
4502
|
+
}
|
|
4503
|
+
addMetric() {
|
|
4504
|
+
this.patchDocument((document) => ({
|
|
4505
|
+
...document,
|
|
4506
|
+
metrics: [
|
|
4507
|
+
...(document.metrics ?? []),
|
|
4508
|
+
{ field: '', aggregation: 'sum', label: '' },
|
|
4509
|
+
],
|
|
4510
|
+
}));
|
|
4511
|
+
}
|
|
4512
|
+
removeMetric(index) {
|
|
4513
|
+
this.patchDocument((document) => ({
|
|
4514
|
+
...document,
|
|
4515
|
+
metrics: (document.metrics ?? []).filter((_, currentIndex) => currentIndex !== index),
|
|
4516
|
+
}));
|
|
4517
|
+
}
|
|
4518
|
+
setMetricField(index, field) {
|
|
4519
|
+
this.patchDocument((document) => ({
|
|
4520
|
+
...document,
|
|
4521
|
+
metrics: (document.metrics ?? []).map((metric, currentIndex) => currentIndex === index
|
|
4522
|
+
? { ...metric, field: field.trim() || '' }
|
|
4523
|
+
: metric),
|
|
4524
|
+
}));
|
|
4525
|
+
}
|
|
4526
|
+
setMetricLabel(index, label) {
|
|
4527
|
+
this.patchDocument((document) => ({
|
|
4528
|
+
...document,
|
|
4529
|
+
metrics: (document.metrics ?? []).map((metric, currentIndex) => currentIndex === index
|
|
4530
|
+
? { ...metric, label: label.trim() || undefined }
|
|
4531
|
+
: metric),
|
|
4532
|
+
}));
|
|
4533
|
+
}
|
|
4534
|
+
setMetricAggregation(index, aggregation) {
|
|
4535
|
+
this.patchDocument((document) => ({
|
|
4536
|
+
...document,
|
|
4537
|
+
metrics: (document.metrics ?? []).map((metric, currentIndex) => currentIndex === index
|
|
4538
|
+
? { ...metric, aggregation }
|
|
4539
|
+
: metric),
|
|
4540
|
+
}));
|
|
4541
|
+
}
|
|
4542
|
+
setMetricAxis(index, axis) {
|
|
4543
|
+
this.patchDocument((document) => ({
|
|
4544
|
+
...document,
|
|
4545
|
+
metrics: (document.metrics ?? []).map((metric, currentIndex) => currentIndex === index
|
|
4546
|
+
? { ...metric, axis }
|
|
4547
|
+
: metric),
|
|
4548
|
+
}));
|
|
4549
|
+
}
|
|
4550
|
+
setMetricSeriesKind(index, seriesKind) {
|
|
4551
|
+
this.patchDocument((document) => ({
|
|
4552
|
+
...document,
|
|
4553
|
+
metrics: (document.metrics ?? []).map((metric, currentIndex) => currentIndex === index
|
|
4554
|
+
? { ...metric, seriesKind }
|
|
4555
|
+
: metric),
|
|
4556
|
+
}));
|
|
4557
|
+
}
|
|
4558
|
+
setMotionEnabled(enabled) {
|
|
4559
|
+
this.patchDocument((document) => ({
|
|
4560
|
+
...document,
|
|
4561
|
+
motion: {
|
|
4562
|
+
...(document.motion ?? {}),
|
|
4563
|
+
enabled,
|
|
4564
|
+
},
|
|
4565
|
+
}));
|
|
4566
|
+
}
|
|
4567
|
+
setMotionPreset(preset) {
|
|
4568
|
+
this.patchDocument((document) => ({
|
|
4569
|
+
...document,
|
|
4570
|
+
motion: {
|
|
4571
|
+
...(document.motion ?? {}),
|
|
4572
|
+
enabled: document.motion?.enabled ?? true,
|
|
4573
|
+
preset,
|
|
4574
|
+
},
|
|
4575
|
+
}));
|
|
4576
|
+
}
|
|
4577
|
+
setFeatureEnabled(feature, enabled) {
|
|
4578
|
+
this.patchDocument((document) => ({
|
|
4579
|
+
...document,
|
|
4580
|
+
[feature]: {
|
|
4581
|
+
enabled,
|
|
4582
|
+
},
|
|
4583
|
+
}));
|
|
4584
|
+
}
|
|
4585
|
+
setPalette(value) {
|
|
4586
|
+
const palette = value
|
|
4587
|
+
.split(/[\n,]/)
|
|
4588
|
+
.map((item) => item.trim())
|
|
4589
|
+
.filter(Boolean);
|
|
4590
|
+
this.patchDocument((document) => ({
|
|
4591
|
+
...document,
|
|
4592
|
+
theme: {
|
|
4593
|
+
...(document.theme ?? {}),
|
|
4594
|
+
palette: palette.length ? palette : undefined,
|
|
4595
|
+
},
|
|
4596
|
+
}));
|
|
4597
|
+
}
|
|
4598
|
+
setStateTitle(stateKey, value) {
|
|
4599
|
+
this.patchStateDescriptor(stateKey, (descriptor) => ({
|
|
4600
|
+
...descriptor,
|
|
4601
|
+
title: value.trim() || undefined,
|
|
4602
|
+
}));
|
|
4603
|
+
}
|
|
4604
|
+
setStateDescription(stateKey, value) {
|
|
4605
|
+
this.patchStateDescriptor(stateKey, (descriptor) => ({
|
|
4606
|
+
...descriptor,
|
|
4607
|
+
description: value.trim() || undefined,
|
|
4608
|
+
}));
|
|
4609
|
+
}
|
|
4610
|
+
setEventAction(eventKey, action) {
|
|
4611
|
+
this.patchDocument((document) => {
|
|
4612
|
+
const currentEvent = document.events?.[eventKey];
|
|
4613
|
+
if (!action) {
|
|
4614
|
+
return {
|
|
4615
|
+
...document,
|
|
4616
|
+
events: {
|
|
4617
|
+
...(document.events ?? {}),
|
|
4618
|
+
[eventKey]: undefined,
|
|
4619
|
+
},
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
return {
|
|
4623
|
+
...document,
|
|
4624
|
+
events: {
|
|
4625
|
+
...(document.events ?? {}),
|
|
4626
|
+
[eventKey]: {
|
|
4627
|
+
...(currentEvent ?? {}),
|
|
4628
|
+
action,
|
|
4629
|
+
},
|
|
4630
|
+
},
|
|
4631
|
+
};
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
setEventTarget(eventKey, target) {
|
|
4635
|
+
this.patchEvent(eventKey, (currentEvent) => ({
|
|
4636
|
+
...currentEvent,
|
|
4637
|
+
target: target.trim() || undefined,
|
|
4638
|
+
}));
|
|
4639
|
+
}
|
|
4640
|
+
setEventMapping(eventKey, value) {
|
|
4641
|
+
const mapping = this.parseMappingText(value);
|
|
4642
|
+
this.patchEvent(eventKey, (currentEvent) => ({
|
|
4643
|
+
...currentEvent,
|
|
4644
|
+
mapping: Object.keys(mapping).length ? mapping : undefined,
|
|
4645
|
+
}));
|
|
4646
|
+
}
|
|
4647
|
+
isReadonly() {
|
|
4648
|
+
return this.readonlyInput() || this.injectedData?.readonly === true;
|
|
4649
|
+
}
|
|
4650
|
+
titleValue() {
|
|
4651
|
+
const value = this.currentDocument().title;
|
|
4652
|
+
if (typeof value === 'string') {
|
|
4653
|
+
return value;
|
|
4654
|
+
}
|
|
4655
|
+
return value?.fallback ?? '';
|
|
4656
|
+
}
|
|
4657
|
+
subtitleValue() {
|
|
4658
|
+
const value = this.currentDocument().subtitle;
|
|
4659
|
+
if (typeof value === 'string') {
|
|
4660
|
+
return value;
|
|
4661
|
+
}
|
|
4662
|
+
return value?.fallback ?? '';
|
|
4663
|
+
}
|
|
4664
|
+
resourceValue() {
|
|
4665
|
+
return this.currentDocument().source.kind === 'praxis.stats'
|
|
4666
|
+
? this.currentDocument().source.resource ?? ''
|
|
4667
|
+
: '';
|
|
4668
|
+
}
|
|
4669
|
+
resourceOptions() {
|
|
4670
|
+
return this.availableResources();
|
|
4671
|
+
}
|
|
4672
|
+
granularityValue() {
|
|
4673
|
+
return this.doc().source.kind === 'praxis.stats'
|
|
4674
|
+
? (this.doc().source.options?.granularity ?? 'day')
|
|
4675
|
+
: 'day';
|
|
4676
|
+
}
|
|
4677
|
+
fillGapsValue() {
|
|
4678
|
+
return this.doc().source.kind === 'praxis.stats'
|
|
4679
|
+
? this.doc().source.options?.fillGaps ?? false
|
|
4680
|
+
: false;
|
|
4681
|
+
}
|
|
4682
|
+
distributionModeValue() {
|
|
4683
|
+
return this.doc().source.kind === 'praxis.stats'
|
|
4684
|
+
? (this.doc().source.options?.mode ?? 'terms')
|
|
4685
|
+
: 'terms';
|
|
4686
|
+
}
|
|
4687
|
+
bucketSizeValue() {
|
|
4688
|
+
const value = this.doc().source.kind === 'praxis.stats'
|
|
4689
|
+
? this.doc().source.options?.bucketSize
|
|
4690
|
+
: undefined;
|
|
4691
|
+
return value === undefined ? '' : String(value);
|
|
4692
|
+
}
|
|
4693
|
+
bucketCountValue() {
|
|
4694
|
+
const value = this.doc().source.kind === 'praxis.stats'
|
|
4695
|
+
? this.doc().source.options?.bucketCount
|
|
4696
|
+
: undefined;
|
|
4697
|
+
return value === undefined ? '' : String(value);
|
|
4698
|
+
}
|
|
4699
|
+
heightValue() {
|
|
4700
|
+
const height = this.currentDocument().height;
|
|
4701
|
+
return height === undefined || height === null ? '' : String(height);
|
|
4702
|
+
}
|
|
4703
|
+
featureEnabled(feature) {
|
|
4704
|
+
const value = this.doc()[feature];
|
|
4705
|
+
if (typeof value === 'boolean') {
|
|
4706
|
+
return value;
|
|
4707
|
+
}
|
|
4708
|
+
return value?.enabled ?? false;
|
|
4709
|
+
}
|
|
4710
|
+
paletteValue() {
|
|
4711
|
+
const palette = this.doc().theme?.palette;
|
|
4712
|
+
return Array.isArray(palette) ? palette.join(', ') : '';
|
|
4713
|
+
}
|
|
4714
|
+
stateTitle(stateKey) {
|
|
4715
|
+
const value = this.doc().state?.[stateKey]?.title;
|
|
4716
|
+
if (typeof value === 'string') {
|
|
4717
|
+
return value;
|
|
4718
|
+
}
|
|
4719
|
+
return value?.fallback ?? '';
|
|
4720
|
+
}
|
|
4721
|
+
stateDescription(stateKey) {
|
|
4722
|
+
const value = this.doc().state?.[stateKey]?.description;
|
|
4723
|
+
if (typeof value === 'string') {
|
|
4724
|
+
return value;
|
|
4725
|
+
}
|
|
4726
|
+
return value?.fallback ?? '';
|
|
4727
|
+
}
|
|
4728
|
+
eventAction(eventKey) {
|
|
4729
|
+
return this.doc().events?.[eventKey]?.action ?? '';
|
|
4730
|
+
}
|
|
4731
|
+
eventTarget(eventKey) {
|
|
4732
|
+
return this.doc().events?.[eventKey]?.target ?? '';
|
|
4733
|
+
}
|
|
4734
|
+
targetOptions() {
|
|
4735
|
+
return this.availableTargets();
|
|
4736
|
+
}
|
|
4737
|
+
eventMappingText(eventKey) {
|
|
4738
|
+
const mapping = this.doc().events?.[eventKey]?.mapping;
|
|
4739
|
+
if (!mapping) {
|
|
4740
|
+
return '';
|
|
4741
|
+
}
|
|
4742
|
+
return Object.entries(mapping)
|
|
4743
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
4744
|
+
.join('\n');
|
|
4745
|
+
}
|
|
4746
|
+
dimensions() {
|
|
4747
|
+
return this.doc().dimensions ?? [];
|
|
4748
|
+
}
|
|
4749
|
+
metrics() {
|
|
4750
|
+
return this.doc().metrics ?? [];
|
|
4751
|
+
}
|
|
4752
|
+
fieldOptions(role) {
|
|
4753
|
+
const fields = this.availableFields();
|
|
4754
|
+
if (!fields.length) {
|
|
4755
|
+
return [];
|
|
4756
|
+
}
|
|
4757
|
+
return fields.filter((field) => {
|
|
4758
|
+
if (!field.roles?.length) {
|
|
4759
|
+
return role === 'dimension' ? field.aggregable !== true : true;
|
|
4760
|
+
}
|
|
4761
|
+
return field.roles.includes(role);
|
|
4762
|
+
});
|
|
4763
|
+
}
|
|
4764
|
+
showMetricAxisControls() {
|
|
4765
|
+
return this.doc().kind === 'combo';
|
|
4766
|
+
}
|
|
4767
|
+
showMetricSeriesKindControls() {
|
|
4768
|
+
return this.doc().kind === 'combo';
|
|
4769
|
+
}
|
|
4770
|
+
showTimeseriesControls() {
|
|
4771
|
+
return this.doc().source.kind === 'praxis.stats' && this.doc().source.operation === 'timeseries';
|
|
4772
|
+
}
|
|
4773
|
+
showDistributionControls() {
|
|
4774
|
+
return this.doc().source.kind === 'praxis.stats' && this.doc().source.operation === 'distribution';
|
|
4775
|
+
}
|
|
4776
|
+
showComboPanel() {
|
|
4777
|
+
return this.doc().kind === 'combo';
|
|
4778
|
+
}
|
|
4779
|
+
showPieDonutPanel() {
|
|
4780
|
+
return this.doc().kind === 'pie' || this.doc().kind === 'donut';
|
|
4781
|
+
}
|
|
4782
|
+
showScatterPanel() {
|
|
4783
|
+
return this.doc().kind === 'scatter';
|
|
4784
|
+
}
|
|
4785
|
+
issueTrackBy(_, issue) {
|
|
4786
|
+
return `${issue.code}:${issue.field}`;
|
|
4787
|
+
}
|
|
4788
|
+
t(key, fallback) {
|
|
4789
|
+
return this.i18n.resolve(resolvePraxisChartsText({ key, text: fallback }, fallback));
|
|
4790
|
+
}
|
|
4791
|
+
doc() {
|
|
4792
|
+
return this.currentDocument();
|
|
4793
|
+
}
|
|
4794
|
+
patchStateDescriptor(stateKey, updater) {
|
|
4795
|
+
this.patchDocument((document) => ({
|
|
4796
|
+
...document,
|
|
4797
|
+
state: {
|
|
4798
|
+
...(document.state ?? {}),
|
|
4799
|
+
[stateKey]: updater(document.state?.[stateKey] ?? {}),
|
|
4800
|
+
},
|
|
4801
|
+
}));
|
|
4802
|
+
}
|
|
4803
|
+
patchEvent(eventKey, updater) {
|
|
4804
|
+
this.patchDocument((document) => {
|
|
4805
|
+
const currentEvent = document.events?.[eventKey];
|
|
4806
|
+
if (!currentEvent?.action) {
|
|
4807
|
+
return document;
|
|
4808
|
+
}
|
|
4809
|
+
return {
|
|
4810
|
+
...document,
|
|
4811
|
+
events: {
|
|
4812
|
+
...(document.events ?? {}),
|
|
4813
|
+
[eventKey]: updater(currentEvent),
|
|
4814
|
+
},
|
|
4815
|
+
};
|
|
4816
|
+
});
|
|
4817
|
+
}
|
|
4818
|
+
parseMappingText(value) {
|
|
4819
|
+
return value
|
|
4820
|
+
.split('\n')
|
|
4821
|
+
.map((line) => line.trim())
|
|
4822
|
+
.filter(Boolean)
|
|
4823
|
+
.reduce((accumulator, line) => {
|
|
4824
|
+
const separatorIndex = line.indexOf('=');
|
|
4825
|
+
if (separatorIndex <= 0) {
|
|
4826
|
+
return accumulator;
|
|
4827
|
+
}
|
|
4828
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
4829
|
+
const mappedValue = line.slice(separatorIndex + 1).trim();
|
|
4830
|
+
if (!key || !mappedValue) {
|
|
4831
|
+
return accumulator;
|
|
4832
|
+
}
|
|
4833
|
+
accumulator[key] = mappedValue;
|
|
4834
|
+
return accumulator;
|
|
4835
|
+
}, {});
|
|
4836
|
+
}
|
|
4837
|
+
createApplyPayload() {
|
|
4838
|
+
return {
|
|
4839
|
+
document: this.getSettingsValue(),
|
|
4840
|
+
issues: [...this.issues()],
|
|
4841
|
+
dirty: this.isDirty$.getValue(),
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4844
|
+
createSavePayload() {
|
|
4845
|
+
return {
|
|
4846
|
+
document: this.getSettingsValue(),
|
|
4847
|
+
issues: [...this.issues()],
|
|
4848
|
+
dirty: this.isDirty$.getValue(),
|
|
4849
|
+
};
|
|
4850
|
+
}
|
|
4851
|
+
patchDocument(updater) {
|
|
4852
|
+
if (this.isReadonly()) {
|
|
4853
|
+
return;
|
|
4854
|
+
}
|
|
4855
|
+
this.currentDocument.set(updater(structuredClone(this.currentDocument())));
|
|
4856
|
+
this.refreshState(true);
|
|
4857
|
+
}
|
|
4858
|
+
refreshState(markDirty) {
|
|
4859
|
+
this.isDirty$.next(markDirty);
|
|
4860
|
+
this.isValid$.next(this.validation().valid);
|
|
4861
|
+
this.documentChange.emit(structuredClone(this.normalizedDocument()));
|
|
4862
|
+
}
|
|
4863
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4864
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisChartConfigEditor, isStandalone: true, selector: "praxis-chart-config-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, modeInput: { classPropertyName: "modeInput", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, availableResourcesInput: { classPropertyName: "availableResourcesInput", publicName: "availableResources", isSignal: true, isRequired: false, transformFunction: null }, availableFieldsInput: { classPropertyName: "availableFieldsInput", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null }, availableTargetsInput: { classPropertyName: "availableTargetsInput", publicName: "availableTargets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", save: "save", resetChange: "resetChange", documentChange: "documentChange" }, providers: [providePraxisChartsI18n()], ngImport: i0, template: "<div class=\"editor-shell\">\n <div class=\"editor-nav\">\n @for (section of sections; track section.id) {\n <button\n mat-stroked-button\n type=\"button\"\n [class.active]=\"activeSection() === section.id\"\n (click)=\"setSection(section.id)\"\n >\n {{ t(section.labelKey, section.fallback) }}\n </button>\n }\n </div>\n\n <div class=\"editor-layout\">\n <div class=\"editor-form\">\n <mat-card class=\"editor-card\">\n <mat-card-content>\n @switch (activeSection()) {\n @case ('general') {\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.chartId', 'Chart ID') }}</mat-label>\n <input matInput [ngModel]=\"doc().chartId || ''\" (ngModelChange)=\"setChartId($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.kind', 'Kind') }}</mat-label>\n <mat-select [ngModel]=\"doc().kind\" (ngModelChange)=\"setKind($event)\" [disabled]=\"isReadonly()\">\n @for (kind of chartKinds; track kind) {\n <mat-option [value]=\"kind\">\n {{ t('praxis.charts.editor.kind.' + kind, kind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.title', 'Title') }}</mat-label>\n <input matInput [ngModel]=\"titleValue()\" (ngModelChange)=\"setTitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.subtitle', 'Subtitle') }}</mat-label>\n <input matInput [ngModel]=\"subtitleValue()\" (ngModelChange)=\"setSubtitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.height', 'Height') }}</mat-label>\n <input matInput [ngModel]=\"heightValue()\" (ngModelChange)=\"setHeight($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </div>\n }\n\n @case ('data') {\n <div class=\"editor-stack\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sourceKind', 'Source') }}</mat-label>\n <mat-select [ngModel]=\"doc().source.kind\" (ngModelChange)=\"setSourceKind($event)\" [disabled]=\"isReadonly()\">\n @for (sourceKind of sourceKinds; track sourceKind) {\n <mat-option [value]=\"sourceKind\">\n {{ t('praxis.charts.editor.sourceKind.' + (sourceKind === 'praxis.stats' ? 'praxisStats' : 'derived'), sourceKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (doc().source.kind === 'praxis.stats') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.resource', 'Resource') }}</mat-label>\n @if (resourceOptions().length) {\n <mat-select [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\">\n @for (resource of resourceOptions(); track resource.id) {\n <mat-option [value]=\"resource.path\">{{ resource.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.operation', 'Operation') }}</mat-label>\n <mat-select\n [ngModel]=\"doc().source.operation || 'group-by'\"\n (ngModelChange)=\"setOperation($event)\"\n [disabled]=\"isReadonly()\"\n >\n @for (operation of operations; track operation) {\n <mat-option [value]=\"operation\">\n {{ t('praxis.charts.editor.operation.' + (operation === 'group-by' ? 'groupBy' : operation), operation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n @if (showTimeseriesControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.timeseriesTitle', 'Timeseries options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.granularity', 'Granularity') }}</mat-label>\n <mat-select [ngModel]=\"granularityValue()\" (ngModelChange)=\"setGranularity($event)\" [disabled]=\"isReadonly()\">\n @for (granularity of timeGranularities; track granularity) {\n <mat-option [value]=\"granularity\">\n {{ t('praxis.charts.editor.granularity.' + granularity, granularity) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-slide-toggle\n [ngModel]=\"fillGapsValue()\"\n (ngModelChange)=\"setFillGaps($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.fillGaps', 'Fill missing intervals') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showDistributionControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.distributionTitle', 'Distribution options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.distributionMode', 'Distribution mode') }}</mat-label>\n <mat-select [ngModel]=\"distributionModeValue()\" (ngModelChange)=\"setDistributionMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of distributionModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.distributionMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketSize', 'Bucket size') }}</mat-label>\n <input matInput [ngModel]=\"bucketSizeValue()\" (ngModelChange)=\"setBucketSize($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketCount', 'Bucket count') }}</mat-label>\n <input matInput [ngModel]=\"bucketCountValue()\" (ngModelChange)=\"setBucketCount($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n }\n </div>\n }\n\n @case ('motion') {\n <div class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"normalizedDocument().motion?.enabled !== false\"\n (ngModelChange)=\"setMotionEnabled($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.motionEnabled', 'Enable animations') }}\n </mat-slide-toggle>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.motionPreset', 'Motion preset') }}</mat-label>\n <mat-select\n [ngModel]=\"normalizedDocument().motion?.preset || 'standard'\"\n (ngModelChange)=\"setMotionPreset($event)\"\n [disabled]=\"isReadonly() || normalizedDocument().motion?.enabled === false\"\n >\n @for (preset of motionPresets; track preset) {\n <mat-option [value]=\"preset\">\n {{ t('praxis.charts.editor.motionPreset.' + preset, preset) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n @case ('appearance') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.featuresTitle', 'Display features') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('legend')\"\n (ngModelChange)=\"setFeatureEnabled('legend', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.legendEnabled', 'Show legend') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('labels')\"\n (ngModelChange)=\"setFeatureEnabled('labels', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.labelsEnabled', 'Show labels') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('tooltip')\"\n (ngModelChange)=\"setFeatureEnabled('tooltip', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.tooltipEnabled', 'Show tooltip') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.paletteTitle', 'Palette') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.palette', 'Palette colors') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"paletteValue()\"\n (ngModelChange)=\"setPalette($event)\"\n [disabled]=\"isReadonly()\"\n ></textarea>\n </mat-form-field>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.paletteHint', 'Use comma or line separated colors to persist theme.palette as a canonical array.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.statesTitle', 'State messages') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyTitle', 'Empty title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('empty')\" (ngModelChange)=\"setStateTitle('empty', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyDescription', 'Empty description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('empty')\" (ngModelChange)=\"setStateDescription('empty', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingTitle', 'Loading title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('loading')\" (ngModelChange)=\"setStateTitle('loading', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingDescription', 'Loading description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('loading')\" (ngModelChange)=\"setStateDescription('loading', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorTitle', 'Error title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('error')\" (ngModelChange)=\"setStateTitle('error', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorDescription', 'Error description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('error')\" (ngModelChange)=\"setStateDescription('error', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('analytics') {\n <div class=\"editor-stack\">\n @if (showComboPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.comboTitle', 'Combo guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.comboHint', 'Combo charts require at least two metrics and allow per-metric axis and series kind mapping.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showPieDonutPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.pieDonutTitle', 'Composition guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.pieDonutHint', 'Pie and donut charts keep only the first metric and use the first dimension as the category segment.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showScatterPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.scatterTitle', 'Scatter guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.scatterHint', 'Scatter charts use the first dimension as X and the first metric as Y, so keep both fields mapped.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.dimensionsTitle', 'Dimensions') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (dimension of dimensions(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimension', 'Dimension') }}</mat-label>\n @if (fieldOptions('dimension').length) {\n <mat-select [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('dimension'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimensionRole', 'Dimension role') }}</mat-label>\n <mat-select [ngModel]=\"dimension.role || 'category'\" (ngModelChange)=\"setDimensionRole($index, $event)\" [disabled]=\"isReadonly()\">\n @for (role of dimensionRoles; track role) {\n <mat-option [value]=\"role\">\n {{ t('praxis.charts.editor.dimensionRole.' + role, role) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeDimension($index)\" [disabled]=\"isReadonly() || dimensions().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeDimension', 'Remove dimension') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addDimension()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addDimension', 'Add dimension') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.metricsTitle', 'Metrics') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (metric of metrics(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metric', 'Metric') }}</mat-label>\n @if (fieldOptions('metric').length) {\n <mat-select [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('metric'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricLabel', 'Metric label') }}</mat-label>\n <input matInput [ngModel]=\"metric.label || ''\" (ngModelChange)=\"setMetricLabel($index, $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAggregation', 'Aggregation') }}</mat-label>\n <mat-select [ngModel]=\"metric.aggregation || 'sum'\" (ngModelChange)=\"setMetricAggregation($index, $event)\" [disabled]=\"isReadonly()\">\n @for (aggregation of metricAggregations; track aggregation) {\n <mat-option [value]=\"aggregation\">\n {{ t('praxis.charts.editor.metricAggregation.' + aggregation, aggregation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (showMetricAxisControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAxis', 'Axis') }}</mat-label>\n <mat-select [ngModel]=\"metric.axis || 'primary'\" (ngModelChange)=\"setMetricAxis($index, $event)\" [disabled]=\"isReadonly()\">\n @for (axis of metricAxes; track axis) {\n <mat-option [value]=\"axis\">\n {{ t('praxis.charts.editor.metricAxis.' + axis, axis) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (showMetricSeriesKindControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricSeriesKind', 'Series kind') }}</mat-label>\n <mat-select [ngModel]=\"metric.seriesKind || 'bar'\" (ngModelChange)=\"setMetricSeriesKind($index, $event)\" [disabled]=\"isReadonly()\">\n @for (seriesKind of metricSeriesKinds; track seriesKind) {\n <mat-option [value]=\"seriesKind\">\n {{ t('praxis.charts.editor.metricSeriesKind.' + seriesKind, seriesKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeMetric($index)\" [disabled]=\"isReadonly() || metrics().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeMetric', 'Remove metric') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addMetric()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addMetric', 'Add metric') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('events') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.pointClickTitle', 'Point click') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('pointClick')\" (ngModelChange)=\"setEventAction('pointClick', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('pointClick')\"\n (ngModelChange)=\"setEventMapping('pointClick', $event)\"\n [disabled]=\"isReadonly() || !eventAction('pointClick')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.drillDownTitle', 'Drill down') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('drillDown')\" (ngModelChange)=\"setEventAction('drillDown', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('drillDown')\"\n (ngModelChange)=\"setEventMapping('drillDown', $event)\"\n [disabled]=\"isReadonly() || !eventAction('drillDown')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('preview') {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n }\n }\n </mat-card-content>\n </mat-card>\n </div>\n\n <div class=\"editor-side\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.issues.title', 'Validation issues') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (issues().length) {\n <ul class=\"editor-issues\">\n @for (issue of issues(); track issueTrackBy($index, issue)) {\n <li class=\"editor-issue\">\n <strong>{{ issue.field }}</strong>\n <span>{{ issue.message }}</span>\n </li>\n }\n </ul>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.issues.empty', 'No issues were identified.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.preview.title', 'Chart preview') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (preview(); as chartPreview) {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n <praxis-chart [config]=\"chartPreview.config\" [data]=\"chartPreview.data\"></praxis-chart>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.preview.invalid', 'Preview is unavailable while the contract has blocking errors.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}.editor-shell{display:grid;gap:18px}.editor-nav{display:flex;gap:8px;flex-wrap:wrap}.editor-nav button.active{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 18%,transparent);color:var(--md-sys-color-primary, #1263b4)}.editor-layout{display:grid;gap:18px;grid-template-columns:minmax(0,1.35fr) minmax(320px,.9fr);align-items:start}.editor-form,.editor-side{display:grid;gap:16px}.editor-card{border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#1263b408,#1263b400)}.editor-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-stack{display:grid;gap:14px}.editor-row-card{padding:14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 54%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 92%,rgba(18,99,180,.04))}.editor-row-actions{display:flex;justify-content:flex-end}.editor-field{width:100%}.editor-issues{display:grid;gap:10px;margin:0;padding:0;list-style:none}.editor-issue{padding:12px 14px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-error, #b3261e) 8%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-error, #b3261e) 18%,transparent)}.editor-issue strong{display:block;margin-bottom:4px}.editor-caption{margin:0 0 12px;color:var(--md-sys-color-on-surface-variant, #5a5d67);font-size:.92rem}.editor-empty{padding:18px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-variant, #eceff4) 78%,transparent)}@media(max-width:960px){.editor-layout{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i3$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i3$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisChartComponent, selector: "praxis-chart", inputs: ["config", "data", "chartDocument", "filterCriteria", "enableCustomization", "availableResources", "availableFields", "availableTargets"], outputs: ["pointClick", "queryRequest", "loadStateChange", "chartDocumentApplied", "chartDocumentSaved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4865
|
+
}
|
|
4866
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisChartConfigEditor, decorators: [{
|
|
4867
|
+
type: Component,
|
|
4868
|
+
args: [{ selector: 'praxis-chart-config-editor', standalone: true, imports: [
|
|
4869
|
+
CommonModule,
|
|
4870
|
+
FormsModule,
|
|
4871
|
+
MatButtonModule,
|
|
4872
|
+
MatCardModule,
|
|
4873
|
+
MatFormFieldModule,
|
|
4874
|
+
MatInputModule,
|
|
4875
|
+
MatSelectModule,
|
|
4876
|
+
MatSlideToggleModule,
|
|
4877
|
+
PraxisChartComponent,
|
|
4878
|
+
], providers: [providePraxisChartsI18n()], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"editor-shell\">\n <div class=\"editor-nav\">\n @for (section of sections; track section.id) {\n <button\n mat-stroked-button\n type=\"button\"\n [class.active]=\"activeSection() === section.id\"\n (click)=\"setSection(section.id)\"\n >\n {{ t(section.labelKey, section.fallback) }}\n </button>\n }\n </div>\n\n <div class=\"editor-layout\">\n <div class=\"editor-form\">\n <mat-card class=\"editor-card\">\n <mat-card-content>\n @switch (activeSection()) {\n @case ('general') {\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.chartId', 'Chart ID') }}</mat-label>\n <input matInput [ngModel]=\"doc().chartId || ''\" (ngModelChange)=\"setChartId($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.kind', 'Kind') }}</mat-label>\n <mat-select [ngModel]=\"doc().kind\" (ngModelChange)=\"setKind($event)\" [disabled]=\"isReadonly()\">\n @for (kind of chartKinds; track kind) {\n <mat-option [value]=\"kind\">\n {{ t('praxis.charts.editor.kind.' + kind, kind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.title', 'Title') }}</mat-label>\n <input matInput [ngModel]=\"titleValue()\" (ngModelChange)=\"setTitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.subtitle', 'Subtitle') }}</mat-label>\n <input matInput [ngModel]=\"subtitleValue()\" (ngModelChange)=\"setSubtitle($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.height', 'Height') }}</mat-label>\n <input matInput [ngModel]=\"heightValue()\" (ngModelChange)=\"setHeight($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </div>\n }\n\n @case ('data') {\n <div class=\"editor-stack\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.sourceKind', 'Source') }}</mat-label>\n <mat-select [ngModel]=\"doc().source.kind\" (ngModelChange)=\"setSourceKind($event)\" [disabled]=\"isReadonly()\">\n @for (sourceKind of sourceKinds; track sourceKind) {\n <mat-option [value]=\"sourceKind\">\n {{ t('praxis.charts.editor.sourceKind.' + (sourceKind === 'praxis.stats' ? 'praxisStats' : 'derived'), sourceKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (doc().source.kind === 'praxis.stats') {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.resource', 'Resource') }}</mat-label>\n @if (resourceOptions().length) {\n <mat-select [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\">\n @for (resource of resourceOptions(); track resource.id) {\n <mat-option [value]=\"resource.path\">{{ resource.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"resourceValue()\" (ngModelChange)=\"setResource($event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.operation', 'Operation') }}</mat-label>\n <mat-select\n [ngModel]=\"doc().source.operation || 'group-by'\"\n (ngModelChange)=\"setOperation($event)\"\n [disabled]=\"isReadonly()\"\n >\n @for (operation of operations; track operation) {\n <mat-option [value]=\"operation\">\n {{ t('praxis.charts.editor.operation.' + (operation === 'group-by' ? 'groupBy' : operation), operation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n @if (showTimeseriesControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.timeseriesTitle', 'Timeseries options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.granularity', 'Granularity') }}</mat-label>\n <mat-select [ngModel]=\"granularityValue()\" (ngModelChange)=\"setGranularity($event)\" [disabled]=\"isReadonly()\">\n @for (granularity of timeGranularities; track granularity) {\n <mat-option [value]=\"granularity\">\n {{ t('praxis.charts.editor.granularity.' + granularity, granularity) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-slide-toggle\n [ngModel]=\"fillGapsValue()\"\n (ngModelChange)=\"setFillGaps($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.fillGaps', 'Fill missing intervals') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showDistributionControls()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.distributionTitle', 'Distribution options') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.distributionMode', 'Distribution mode') }}</mat-label>\n <mat-select [ngModel]=\"distributionModeValue()\" (ngModelChange)=\"setDistributionMode($event)\" [disabled]=\"isReadonly()\">\n @for (mode of distributionModes; track mode) {\n <mat-option [value]=\"mode\">\n {{ t('praxis.charts.editor.distributionMode.' + mode, mode) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketSize', 'Bucket size') }}</mat-label>\n <input matInput [ngModel]=\"bucketSizeValue()\" (ngModelChange)=\"setBucketSize($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.bucketCount', 'Bucket count') }}</mat-label>\n <input matInput [ngModel]=\"bucketCountValue()\" (ngModelChange)=\"setBucketCount($event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n }\n </div>\n }\n\n @case ('motion') {\n <div class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"normalizedDocument().motion?.enabled !== false\"\n (ngModelChange)=\"setMotionEnabled($event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.motionEnabled', 'Enable animations') }}\n </mat-slide-toggle>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.motionPreset', 'Motion preset') }}</mat-label>\n <mat-select\n [ngModel]=\"normalizedDocument().motion?.preset || 'standard'\"\n (ngModelChange)=\"setMotionPreset($event)\"\n [disabled]=\"isReadonly() || normalizedDocument().motion?.enabled === false\"\n >\n @for (preset of motionPresets; track preset) {\n <mat-option [value]=\"preset\">\n {{ t('praxis.charts.editor.motionPreset.' + preset, preset) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\n @case ('appearance') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.featuresTitle', 'Display features') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('legend')\"\n (ngModelChange)=\"setFeatureEnabled('legend', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.legendEnabled', 'Show legend') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('labels')\"\n (ngModelChange)=\"setFeatureEnabled('labels', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.labelsEnabled', 'Show labels') }}\n </mat-slide-toggle>\n\n <mat-slide-toggle\n [ngModel]=\"featureEnabled('tooltip')\"\n (ngModelChange)=\"setFeatureEnabled('tooltip', $event)\"\n [disabled]=\"isReadonly()\"\n >\n {{ t('praxis.charts.editor.field.tooltipEnabled', 'Show tooltip') }}\n </mat-slide-toggle>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.paletteTitle', 'Palette') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.palette', 'Palette colors') }}</mat-label>\n <textarea\n matInput\n rows=\"3\"\n [ngModel]=\"paletteValue()\"\n (ngModelChange)=\"setPalette($event)\"\n [disabled]=\"isReadonly()\"\n ></textarea>\n </mat-form-field>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.appearance.paletteHint', 'Use comma or line separated colors to persist theme.palette as a canonical array.') }}\n </p>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.appearance.statesTitle', 'State messages') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyTitle', 'Empty title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('empty')\" (ngModelChange)=\"setStateTitle('empty', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.emptyDescription', 'Empty description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('empty')\" (ngModelChange)=\"setStateDescription('empty', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingTitle', 'Loading title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('loading')\" (ngModelChange)=\"setStateTitle('loading', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.loadingDescription', 'Loading description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('loading')\" (ngModelChange)=\"setStateDescription('loading', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorTitle', 'Error title') }}</mat-label>\n <input matInput [ngModel]=\"stateTitle('error')\" (ngModelChange)=\"setStateTitle('error', $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.errorDescription', 'Error description') }}</mat-label>\n <textarea matInput rows=\"2\" [ngModel]=\"stateDescription('error')\" (ngModelChange)=\"setStateDescription('error', $event)\" [disabled]=\"isReadonly()\"></textarea>\n </mat-form-field>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('analytics') {\n <div class=\"editor-stack\">\n @if (showComboPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.comboTitle', 'Combo guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.comboHint', 'Combo charts require at least two metrics and allow per-metric axis and series kind mapping.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showPieDonutPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.pieDonutTitle', 'Composition guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.pieDonutHint', 'Pie and donut charts keep only the first metric and use the first dimension as the category segment.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n @if (showScatterPanel()) {\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.specialization.scatterTitle', 'Scatter guidance') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.specialization.scatterHint', 'Scatter charts use the first dimension as X and the first metric as Y, so keep both fields mapped.') }}\n </p>\n </mat-card-content>\n </mat-card>\n }\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.dimensionsTitle', 'Dimensions') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (dimension of dimensions(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimension', 'Dimension') }}</mat-label>\n @if (fieldOptions('dimension').length) {\n <mat-select [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('dimension'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"dimension.field || ''\" (ngModelChange)=\"setDimensionField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.dimensionRole', 'Dimension role') }}</mat-label>\n <mat-select [ngModel]=\"dimension.role || 'category'\" (ngModelChange)=\"setDimensionRole($index, $event)\" [disabled]=\"isReadonly()\">\n @for (role of dimensionRoles; track role) {\n <mat-option [value]=\"role\">\n {{ t('praxis.charts.editor.dimensionRole.' + role, role) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeDimension($index)\" [disabled]=\"isReadonly() || dimensions().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeDimension', 'Remove dimension') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addDimension()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addDimension', 'Add dimension') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.analytics.metricsTitle', 'Metrics') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-stack\">\n @for (metric of metrics(); track $index) {\n <div class=\"editor-row-card\">\n <div class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metric', 'Metric') }}</mat-label>\n @if (fieldOptions('metric').length) {\n <mat-select [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\">\n @for (field of fieldOptions('metric'); track field.field) {\n <mat-option [value]=\"field.field\">{{ field.label || field.field }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"metric.field || ''\" (ngModelChange)=\"setMetricField($index, $event)\" [disabled]=\"isReadonly()\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricLabel', 'Metric label') }}</mat-label>\n <input matInput [ngModel]=\"metric.label || ''\" (ngModelChange)=\"setMetricLabel($index, $event)\" [disabled]=\"isReadonly()\" />\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAggregation', 'Aggregation') }}</mat-label>\n <mat-select [ngModel]=\"metric.aggregation || 'sum'\" (ngModelChange)=\"setMetricAggregation($index, $event)\" [disabled]=\"isReadonly()\">\n @for (aggregation of metricAggregations; track aggregation) {\n <mat-option [value]=\"aggregation\">\n {{ t('praxis.charts.editor.metricAggregation.' + aggregation, aggregation) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (showMetricAxisControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricAxis', 'Axis') }}</mat-label>\n <mat-select [ngModel]=\"metric.axis || 'primary'\" (ngModelChange)=\"setMetricAxis($index, $event)\" [disabled]=\"isReadonly()\">\n @for (axis of metricAxes; track axis) {\n <mat-option [value]=\"axis\">\n {{ t('praxis.charts.editor.metricAxis.' + axis, axis) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (showMetricSeriesKindControls()) {\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.metricSeriesKind', 'Series kind') }}</mat-label>\n <mat-select [ngModel]=\"metric.seriesKind || 'bar'\" (ngModelChange)=\"setMetricSeriesKind($index, $event)\" [disabled]=\"isReadonly()\">\n @for (seriesKind of metricSeriesKinds; track seriesKind) {\n <mat-option [value]=\"seriesKind\">\n {{ t('praxis.charts.editor.metricSeriesKind.' + seriesKind, seriesKind) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </div>\n\n <div class=\"editor-row-actions\">\n <button mat-button type=\"button\" (click)=\"removeMetric($index)\" [disabled]=\"isReadonly() || metrics().length <= 1\">\n {{ t('praxis.charts.editor.analytics.removeMetric', 'Remove metric') }}\n </button>\n </div>\n </div>\n }\n\n <div>\n <button mat-stroked-button type=\"button\" (click)=\"addMetric()\" [disabled]=\"isReadonly()\">\n {{ t('praxis.charts.editor.analytics.addMetric', 'Add metric') }}\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('events') {\n <div class=\"editor-stack\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.pointClickTitle', 'Point click') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('pointClick')\" (ngModelChange)=\"setEventAction('pointClick', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('pointClick')\" (ngModelChange)=\"setEventTarget('pointClick', $event)\" [disabled]=\"isReadonly() || !eventAction('pointClick')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('pointClick')\"\n (ngModelChange)=\"setEventMapping('pointClick', $event)\"\n [disabled]=\"isReadonly() || !eventAction('pointClick')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.events.drillDownTitle', 'Drill down') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content class=\"editor-grid\">\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventAction', 'Action') }}</mat-label>\n <mat-select [ngModel]=\"eventAction('drillDown')\" (ngModelChange)=\"setEventAction('drillDown', $event)\" [disabled]=\"isReadonly()\">\n <mat-option value=\"\">{{ t('praxis.charts.editor.events.none', 'None') }}</mat-option>\n @for (action of eventActionOptions; track action) {\n <mat-option [value]=\"action\">\n {{ t('praxis.charts.editor.eventAction.' + action, action) }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventTarget', 'Target') }}</mat-label>\n @if (targetOptions().length) {\n <mat-select [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\">\n @for (target of targetOptions(); track target.id) {\n <mat-option [value]=\"target.id\">{{ target.label }}</mat-option>\n }\n </mat-select>\n } @else {\n <input matInput [ngModel]=\"eventTarget('drillDown')\" (ngModelChange)=\"setEventTarget('drillDown', $event)\" [disabled]=\"isReadonly() || !eventAction('drillDown')\" />\n }\n </mat-form-field>\n\n <mat-form-field class=\"editor-field\" appearance=\"outline\">\n <mat-label>{{ t('praxis.charts.editor.field.eventMapping', 'Mapping') }}</mat-label>\n <textarea\n matInput\n rows=\"4\"\n [ngModel]=\"eventMappingText('drillDown')\"\n (ngModelChange)=\"setEventMapping('drillDown', $event)\"\n [disabled]=\"isReadonly() || !eventAction('drillDown')\"\n ></textarea>\n </mat-form-field>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n @case ('preview') {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n }\n }\n </mat-card-content>\n </mat-card>\n </div>\n\n <div class=\"editor-side\">\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.issues.title', 'Validation issues') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (issues().length) {\n <ul class=\"editor-issues\">\n @for (issue of issues(); track issueTrackBy($index, issue)) {\n <li class=\"editor-issue\">\n <strong>{{ issue.field }}</strong>\n <span>{{ issue.message }}</span>\n </li>\n }\n </ul>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.issues.empty', 'No issues were identified.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n\n <mat-card class=\"editor-card\">\n <mat-card-header>\n <mat-card-title>{{ t('praxis.charts.editor.preview.title', 'Chart preview') }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (preview(); as chartPreview) {\n <p class=\"editor-caption\">\n {{ t('praxis.charts.editor.preview.caption', 'Local preview derived from the canonical contract without remote calls.') }}\n </p>\n <praxis-chart [config]=\"chartPreview.config\" [data]=\"chartPreview.data\"></praxis-chart>\n } @else {\n <div class=\"editor-empty\">\n {{ t('praxis.charts.editor.preview.invalid', 'Preview is unavailable while the contract has blocking errors.') }}\n </div>\n }\n </mat-card-content>\n </mat-card>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface, #1a1b20)}.editor-shell{display:grid;gap:18px}.editor-nav{display:flex;gap:8px;flex-wrap:wrap}.editor-nav button.active{background:color-mix(in srgb,var(--md-sys-color-primary, #1263b4) 18%,transparent);color:var(--md-sys-color-primary, #1263b4)}.editor-layout{display:grid;gap:18px;grid-template-columns:minmax(0,1.35fr) minmax(320px,.9fr);align-items:start}.editor-form,.editor-side{display:grid;gap:16px}.editor-card{border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 72%,transparent);background:linear-gradient(180deg,#1263b408,#1263b400)}.editor-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-stack{display:grid;gap:14px}.editor-row-card{padding:14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #c5c7ce) 54%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface, #fff) 92%,rgba(18,99,180,.04))}.editor-row-actions{display:flex;justify-content:flex-end}.editor-field{width:100%}.editor-issues{display:grid;gap:10px;margin:0;padding:0;list-style:none}.editor-issue{padding:12px 14px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-error, #b3261e) 8%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-error, #b3261e) 18%,transparent)}.editor-issue strong{display:block;margin-bottom:4px}.editor-caption{margin:0 0 12px;color:var(--md-sys-color-on-surface-variant, #5a5d67);font-size:.92rem}.editor-empty{padding:18px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-variant, #eceff4) 78%,transparent)}@media(max-width:960px){.editor-layout{grid-template-columns:minmax(0,1fr)}}\n"] }]
|
|
4879
|
+
}], ctorParameters: () => [], propDecorators: { documentInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], modeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], availableResourcesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableResources", required: false }] }], availableFieldsInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], availableTargetsInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableTargets", required: false }] }], apply: [{ type: i0.Output, args: ["apply"] }], save: [{ type: i0.Output, args: ["save"] }], resetChange: [{ type: i0.Output, args: ["resetChange"] }], documentChange: [{ type: i0.Output, args: ["documentChange"] }] } });
|
|
4880
|
+
|
|
4881
|
+
var praxisChartConfigEditor = /*#__PURE__*/Object.freeze({
|
|
4882
|
+
__proto__: null,
|
|
4883
|
+
PraxisChartConfigEditor: PraxisChartConfigEditor
|
|
4884
|
+
});
|
|
4885
|
+
|
|
3359
4886
|
/*
|
|
3360
4887
|
* Public API Surface of praxis-charts
|
|
3361
4888
|
*/
|
|
@@ -3364,5 +4891,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3364
4891
|
* Generated bundle index. Do not edit.
|
|
3365
4892
|
*/
|
|
3366
4893
|
|
|
3367
|
-
export { PRAXIS_CHART_BACKEND_MOCK_BAR, PRAXIS_CHART_BACKEND_MOCK_COMBO, PRAXIS_CHART_BACKEND_MOCK_DONUT, PRAXIS_CHART_BACKEND_MOCK_HORIZONTAL_BAR, PRAXIS_CHART_BACKEND_MOCK_MULTI_METRIC_BAR, PRAXIS_CHART_BACKEND_MOCK_SCATTER, PRAXIS_CHART_BACKEND_MOCK_STACKED_AREA, PRAXIS_CHART_BACKEND_MOCK_TIMESERIES, PRAXIS_CHART_COMPONENT_METADATA, PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH, PRAXIS_CHART_DRILLDOWN_PANEL_METADATA, PRAXIS_CHART_ENGINE, PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA, PraxisChartBackendPayloadAdapterService, PraxisChartCanonicalContractMapperService, PraxisChartComponent, PraxisChartCompositionShowcaseComponent, PraxisChartDataTransformerService, PraxisChartDrilldownPanelComponent, PraxisChartOptionBuilderService, PraxisChartSchemaMapperService, PraxisChartStateProbeComponent, PraxisChartStatsApiService, buildPraxisChartInteractiveGridPage, buildPraxisChartInteractiveWidgetPage, buildPraxisChartMockGridPage, buildPraxisChartMockWidgetPage, providePraxisChartDrilldownPanelMetadata, providePraxisChartStateProbeMetadata, providePraxisCharts, providePraxisChartsMetadata };
|
|
4894
|
+
export { ChartContractNormalizerService, ChartContractValidationService, ChartEditorDefaultsService, ChartEditorPreviewMapperService, PRAXIS_CHARTS_I18N, PRAXIS_CHART_BACKEND_MOCK_BAR, PRAXIS_CHART_BACKEND_MOCK_COMBO, PRAXIS_CHART_BACKEND_MOCK_DONUT, PRAXIS_CHART_BACKEND_MOCK_HORIZONTAL_BAR, PRAXIS_CHART_BACKEND_MOCK_MULTI_METRIC_BAR, PRAXIS_CHART_BACKEND_MOCK_SCATTER, PRAXIS_CHART_BACKEND_MOCK_STACKED_AREA, PRAXIS_CHART_BACKEND_MOCK_TIMESERIES, PRAXIS_CHART_COMPONENT_METADATA, PRAXIS_CHART_DRILLDOWN_DATA_BY_MONTH, PRAXIS_CHART_DRILLDOWN_PANEL_METADATA, PRAXIS_CHART_ENGINE, PRAXIS_CHART_STATE_PROBE_COMPONENT_METADATA, PraxisChartBackendPayloadAdapterService, PraxisChartCanonicalContractMapperService, PraxisChartComponent, PraxisChartCompositionShowcaseComponent, PraxisChartConfigEditor, PraxisChartDataTransformerService, PraxisChartDrilldownPanelComponent, PraxisChartOptionBuilderService, PraxisChartSchemaMapperService, PraxisChartStateProbeComponent, PraxisChartStatsApiService, buildPraxisChartInteractiveGridPage, buildPraxisChartInteractiveWidgetPage, buildPraxisChartMockGridPage, buildPraxisChartMockWidgetPage, createPraxisChartsI18nConfig, providePraxisChartDrilldownPanelMetadata, providePraxisChartStateProbeMetadata, providePraxisCharts, providePraxisChartsI18n, providePraxisChartsMetadata, resolvePraxisChartsText };
|
|
3368
4895
|
//# sourceMappingURL=praxisui-charts.mjs.map
|