ng-prime-tools 1.0.95 → 1.0.97

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.
@@ -4223,14 +4223,24 @@ const createDefaultChartOptions = (chartType, theme, config) => {
4223
4223
  label: (context) => config.tooltipLabel(context, isCircularChart),
4224
4224
  },
4225
4225
  },
4226
+ /*
4227
+ * Do not use `display: isCircularChart`.
4228
+ * For non-circular charts, this plugin version can still enter
4229
+ * its drawing lifecycle and throw `undefined.length`.
4230
+ */
4226
4231
  datalabels: {
4227
- display: isCircularChart,
4232
+ display: () => isCircularChart,
4228
4233
  color: theme.textColor,
4229
4234
  anchor: isCircularChart ? 'center' : 'end',
4230
4235
  align: isCircularChart ? 'center' : 'top',
4231
4236
  clamp: true,
4232
4237
  clip: false,
4233
- formatter: (value, context) => config.dataLabel(value, context, isCircularChart),
4238
+ formatter: (value, context) => {
4239
+ if (!isCircularChart) {
4240
+ return '';
4241
+ }
4242
+ return config.dataLabel(value, context, true);
4243
+ },
4234
4244
  font: {
4235
4245
  family: config.fontFamily,
4236
4246
  size: PT_CHART_FONT_SIZES.dataLabel,
@@ -4322,7 +4332,11 @@ class PTChartComponent {
4322
4332
  this.colorSchemeChangeListener = () => {
4323
4333
  this.refreshTheme();
4324
4334
  };
4325
- Chart.register(...registerables, ChartDataLabels);
4335
+ /*
4336
+ * Do not register ChartDataLabels globally.
4337
+ * It must only be enabled for pie, doughnut and polar-area charts.
4338
+ */
4339
+ Chart.register(...registerables);
4326
4340
  }
4327
4341
  ngAfterViewInit() {
4328
4342
  this.viewInitialized = true;
@@ -4355,10 +4369,9 @@ class PTChartComponent {
4355
4369
  if (!this.viewInitialized || !this.chartConfig) {
4356
4370
  return;
4357
4371
  }
4358
- const canvas = this.canvasRef.nativeElement;
4359
4372
  this.destroyChart();
4360
4373
  const configuration = this.buildChartConfiguration();
4361
- this.chart = new Chart(canvas, configuration);
4374
+ this.chart = new Chart(this.canvasRef.nativeElement, configuration);
4362
4375
  this.currentChartType = this.chartConfig.type;
4363
4376
  this.scheduleChartResize();
4364
4377
  }
@@ -4392,6 +4405,12 @@ class PTChartComponent {
4392
4405
  type: chartType,
4393
4406
  data: this.cloneChartData(this.chartConfig.data),
4394
4407
  options: this.mergeChartOptions(defaultOptions, consumerOptions, chartType),
4408
+ /*
4409
+ * ChartDataLabels is attached only for circular charts.
4410
+ * It is not registered globally, so it cannot break line,
4411
+ * bar, scatter, bubble or comparison charts.
4412
+ */
4413
+ plugins: isCircularChartType(chartType) ? [ChartDataLabels] : [],
4395
4414
  };
4396
4415
  }
4397
4416
  buildDefaultOptions(chartType, theme) {
@@ -4465,12 +4484,21 @@ class PTChartComponent {
4465
4484
  ...consumerPlugins?.tooltip?.callbacks,
4466
4485
  },
4467
4486
  },
4487
+ },
4488
+ };
4489
+ /*
4490
+ * Add datalabel options only to circular charts.
4491
+ * For Cartesian charts, the plugin is not installed at all.
4492
+ */
4493
+ if (isCircularChartType(chartType)) {
4494
+ mergedOptions.plugins = {
4495
+ ...mergedOptions.plugins,
4468
4496
  datalabels: {
4469
4497
  ...defaultPlugins?.datalabels,
4470
4498
  ...consumerPlugins?.datalabels,
4471
4499
  },
4472
- },
4473
- };
4500
+ };
4501
+ }
4474
4502
  if (isCartesianChartType(chartType)) {
4475
4503
  mergedOptions.scales = this.mergeScales(defaultOptions.scales, consumerOptions.scales);
4476
4504
  }
@@ -7724,122 +7752,253 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
7724
7752
  }]
7725
7753
  }] });
7726
7754
 
7755
+ const PT_CHART_COMPARISON_DEFAULT_WIDTH = '100%';
7756
+ const PT_CHART_COMPARISON_DEFAULT_HEIGHT = '100%';
7757
+ const PT_CHART_COMPARISON_DEFAULT_MEDIAN_TITLE = 'Médiane';
7758
+ const PT_CHART_COMPARISON_DEFAULT_X_AXIS_TITLE = 'Time';
7759
+ const PT_CHART_COMPARISON_DEFAULT_Y_AXIS_TITLE = 'Value';
7760
+ const PT_CHART_COMPARISON_DEFAULT_RESIZE_DELAY = 100;
7761
+ const PT_CHART_COMPARISON_MEDIAN_DATASET = {
7762
+ borderColor: '#0000ff',
7763
+ borderWidth: 3,
7764
+ backgroundColor: 'transparent',
7765
+ pointRadius: 0,
7766
+ pointHoverRadius: 0,
7767
+ fill: false,
7768
+ tension: 0.1,
7769
+ borderDash: [],
7770
+ };
7771
+ const PT_CHART_COMPARISON_DEFAULT_TOOLTIP = {
7772
+ mode: 'index',
7773
+ intersect: false,
7774
+ position: 'nearest',
7775
+ };
7776
+ const PT_CHART_COMPARISON_DEFAULT_X_TICK_FONT_SIZE = 12;
7777
+ const PT_CHART_COMPARISON_DEFAULT_Y_TICK_FONT = {
7778
+ size: 16,
7779
+ weight: 'bold',
7780
+ };
7781
+ const PT_CHART_COMPARISON_DEFAULT_Y_TICK_COLOR = '#333333';
7782
+ const PT_CHART_COMPARISON_DEFAULT_Y_GRID_COLOR = 'rgba(0, 0, 0, 0.10)';
7783
+ const PT_CHART_COMPARISON_DEFAULT_Y_BORDER_COLOR = '#000000';
7784
+ const createComparisonChartBaseOptions = () => ({
7785
+ responsive: true,
7786
+ maintainAspectRatio: false,
7787
+ resizeDelay: PT_CHART_COMPARISON_DEFAULT_RESIZE_DELAY,
7788
+ });
7789
+
7727
7790
  class PTChartComparisonComponent {
7728
7791
  constructor() {
7729
- this.medianTitle = 'Médiane';
7730
- this.xAxisTitle = 'Time';
7731
- this.yAxisTitle = 'Value';
7732
- this.chartHeight = '400px';
7733
- this.chartWidth = '1200px';
7734
- Chart.register(...registerables, ChartDataLabels);
7792
+ this.medianTitle = PT_CHART_COMPARISON_DEFAULT_MEDIAN_TITLE;
7793
+ this.xAxisTitle = PT_CHART_COMPARISON_DEFAULT_X_AXIS_TITLE;
7794
+ this.yAxisTitle = PT_CHART_COMPARISON_DEFAULT_Y_AXIS_TITLE;
7795
+ this.viewInitialized = false;
7796
+ /*
7797
+ * Do not register ChartDataLabels here.
7798
+ * PTChartComparison is a line/comparison chart and does not use it.
7799
+ */
7800
+ Chart.register(...registerables);
7735
7801
  }
7736
- ngOnInit() {
7802
+ ngAfterViewInit() {
7803
+ this.viewInitialized = true;
7737
7804
  this.initializeChart();
7805
+ this.observeResize();
7806
+ this.scheduleResize();
7807
+ }
7808
+ ngOnChanges(changes) {
7809
+ if (!this.viewInitialized) {
7810
+ return;
7811
+ }
7812
+ if (changes['chartConfig'] ||
7813
+ changes['chartHeight'] ||
7814
+ changes['chartWidth'] ||
7815
+ changes['medianTitle'] ||
7816
+ changes['xAxisTitle'] ||
7817
+ changes['yAxisTitle'] ||
7818
+ changes['yMin'] ||
7819
+ changes['yMax'] ||
7820
+ changes['yStepSize']) {
7821
+ this.initializeChart();
7822
+ }
7738
7823
  }
7739
7824
  ngOnDestroy() {
7825
+ this.resizeObserver?.disconnect();
7740
7826
  this.destroyChart();
7741
7827
  }
7828
+ get resolvedChartWidth() {
7829
+ return (this.chartConfig?.chartWidth?.trim() ||
7830
+ this.chartWidth?.trim() ||
7831
+ PT_CHART_COMPARISON_DEFAULT_WIDTH);
7832
+ }
7833
+ get resolvedChartHeight() {
7834
+ return (this.chartConfig?.chartHeight?.trim() ||
7835
+ this.chartHeight?.trim() ||
7836
+ PT_CHART_COMPARISON_DEFAULT_HEIGHT);
7837
+ }
7742
7838
  initializeChart() {
7743
- const canvas = this.canvasRef.nativeElement;
7839
+ if (!this.chartConfig || !this.canvasRef) {
7840
+ return;
7841
+ }
7744
7842
  this.destroyChart();
7745
- const config = {
7843
+ const configuration = {
7746
7844
  type: this.chartConfig.type || 'line',
7747
7845
  data: this.getFormattedChartData(),
7748
7846
  options: this.getChartOptions(),
7749
7847
  };
7750
- this.chart = new Chart(canvas, config);
7848
+ this.chart = new Chart(this.canvasRef.nativeElement, configuration);
7849
+ this.scheduleResize();
7751
7850
  }
7752
7851
  getFormattedChartData() {
7753
- const medianValues = this.calculateMedian();
7852
+ const sourceDatasets = this.chartConfig.data.datasets ?? [];
7754
7853
  return {
7755
- labels: this.chartConfig.data.labels,
7854
+ labels: [...(this.chartConfig.data.labels ?? [])],
7756
7855
  datasets: [
7757
7856
  {
7758
- label: this.chartConfig.medianTitle || this.medianTitle || 'Médiane',
7759
- data: medianValues,
7760
- borderColor: '#0000FF',
7761
- borderWidth: 3,
7762
- backgroundColor: 'transparent',
7763
- pointRadius: 0,
7764
- fill: false,
7765
- tension: 0.1,
7766
- borderDash: [], // ✅ médiane continue
7857
+ label: this.chartConfig.medianTitle?.trim() ||
7858
+ this.medianTitle ||
7859
+ PT_CHART_COMPARISON_DEFAULT_MEDIAN_TITLE,
7860
+ data: this.calculateMedian(),
7861
+ ...PT_CHART_COMPARISON_MEDIAN_DATASET,
7767
7862
  },
7768
- ...this.chartConfig.data.datasets,
7863
+ ...sourceDatasets.map((dataset) => ({
7864
+ ...dataset,
7865
+ data: [...(dataset.data ?? [])],
7866
+ })),
7769
7867
  ],
7770
7868
  };
7771
7869
  }
7772
7870
  calculateMedian() {
7773
7871
  const datasets = this.chartConfig.data.datasets;
7774
- return this.chartConfig.data.labels.map((_, index) => {
7872
+ const labels = this.chartConfig.data.labels ?? [];
7873
+ return labels.map((_, index) => {
7775
7874
  const valuesAtTime = datasets
7776
- .map((dataset) => dataset.data[index])
7777
- .filter((val) => typeof val === 'number' && !Number.isNaN(val));
7778
- if (valuesAtTime.length === 0)
7875
+ .map((dataset) => dataset.data?.[index])
7876
+ .filter((value) => typeof value === 'number' && Number.isFinite(value))
7877
+ .sort((left, right) => left - right);
7878
+ if (!valuesAtTime.length) {
7779
7879
  return 0;
7780
- valuesAtTime.sort((a, b) => a - b);
7781
- const middle = Math.floor(valuesAtTime.length / 2);
7782
- return valuesAtTime.length % 2 === 0
7783
- ? (valuesAtTime[middle - 1] + valuesAtTime[middle]) / 2
7784
- : valuesAtTime[middle];
7880
+ }
7881
+ const middleIndex = Math.floor(valuesAtTime.length / 2);
7882
+ if (valuesAtTime.length % 2 !== 0) {
7883
+ return valuesAtTime[middleIndex];
7884
+ }
7885
+ return (valuesAtTime[middleIndex - 1] + valuesAtTime[middleIndex]) / 2;
7785
7886
  });
7786
7887
  }
7787
7888
  getChartOptions() {
7788
7889
  const externalOptions = this.chartConfig.options ?? {};
7890
+ const externalPlugins = externalOptions.plugins ?? {};
7891
+ const externalScales = externalOptions.scales;
7892
+ const configuredYMin = this.chartConfig.scales?.y?.min ?? this.yMin;
7893
+ const configuredYMax = this.chartConfig.scales?.y?.max ?? this.yMax;
7894
+ const configuredYStepSize = this.chartConfig.scales?.y?.ticks?.stepSize ?? this.yStepSize;
7789
7895
  return {
7896
+ ...createComparisonChartBaseOptions(),
7897
+ ...externalOptions,
7790
7898
  responsive: true,
7791
7899
  maintainAspectRatio: false,
7900
+ interaction: {
7901
+ mode: 'index',
7902
+ intersect: false,
7903
+ ...(externalOptions.interaction ?? {}),
7904
+ },
7792
7905
  plugins: {
7793
- legend: { display: true, position: 'top' },
7794
- tooltip: { mode: 'index', intersect: false },
7795
- datalabels: {
7796
- display: false, // ✅ supprime les nombres au-dessus des lignes
7906
+ ...externalPlugins,
7907
+ legend: {
7908
+ display: true,
7909
+ position: 'top',
7910
+ ...(externalPlugins.legend ?? {}),
7911
+ },
7912
+ tooltip: {
7913
+ enabled: true,
7914
+ ...PT_CHART_COMPARISON_DEFAULT_TOOLTIP,
7915
+ ...(externalPlugins.tooltip ?? {}),
7797
7916
  },
7798
- ...(externalOptions.plugins ?? {}),
7799
7917
  },
7800
7918
  scales: {
7801
7919
  x: {
7802
- title: { display: true, text: this.chartConfig.xAxisTitle || 'Time' },
7803
- ticks: { font: { size: 12 } },
7804
- ...externalOptions.scales?.x,
7920
+ title: {
7921
+ display: true,
7922
+ text: this.chartConfig.xAxisTitle?.trim() ||
7923
+ this.xAxisTitle ||
7924
+ PT_CHART_COMPARISON_DEFAULT_X_AXIS_TITLE,
7925
+ },
7926
+ ticks: {
7927
+ font: {
7928
+ size: PT_CHART_COMPARISON_DEFAULT_X_TICK_FONT_SIZE,
7929
+ },
7930
+ },
7931
+ ...(externalScales?.['x'] ?? {}),
7805
7932
  },
7806
7933
  y: {
7807
7934
  title: {
7808
7935
  display: true,
7809
- text: this.chartConfig.yAxisTitle || 'Value',
7936
+ text: this.chartConfig.yAxisTitle?.trim() ||
7937
+ this.yAxisTitle ||
7938
+ PT_CHART_COMPARISON_DEFAULT_Y_AXIS_TITLE,
7810
7939
  },
7811
- min: this.chartConfig.scales?.y?.min,
7812
- max: this.chartConfig.scales?.y?.max,
7940
+ min: configuredYMin,
7941
+ max: configuredYMax,
7813
7942
  ticks: {
7814
- stepSize: this.chartConfig.scales?.y?.ticks?.stepSize,
7815
- font: { size: 16, weight: 'bold' },
7816
- color: '#333',
7943
+ stepSize: configuredYStepSize,
7944
+ font: PT_CHART_COMPARISON_DEFAULT_Y_TICK_FONT,
7945
+ color: PT_CHART_COMPARISON_DEFAULT_Y_TICK_COLOR,
7817
7946
  },
7818
7947
  grid: {
7819
- color: 'rgba(0,0,0,0.1)',
7948
+ color: PT_CHART_COMPARISON_DEFAULT_Y_GRID_COLOR,
7820
7949
  },
7821
7950
  border: {
7822
7951
  display: true,
7823
- color: '#000',
7952
+ color: PT_CHART_COMPARISON_DEFAULT_Y_BORDER_COLOR,
7824
7953
  },
7825
- ...externalOptions.scales?.y,
7954
+ ...(externalScales?.['y'] ?? {}),
7826
7955
  },
7827
7956
  },
7828
- ...externalOptions,
7829
7957
  };
7830
7958
  }
7831
- destroyChart() {
7832
- if (this.chart) {
7833
- this.chart.destroy();
7834
- this.chart = undefined;
7959
+ observeResize() {
7960
+ if (typeof ResizeObserver === 'undefined') {
7961
+ return;
7835
7962
  }
7963
+ const canvas = this.canvasRef.nativeElement;
7964
+ const chartInner = canvas.parentElement;
7965
+ const chartScrollContainer = chartInner?.parentElement;
7966
+ const hostElement = chartScrollContainer?.parentElement;
7967
+ this.resizeObserver = new ResizeObserver(() => {
7968
+ this.scheduleResize();
7969
+ });
7970
+ if (chartInner) {
7971
+ this.resizeObserver.observe(chartInner);
7972
+ }
7973
+ if (chartScrollContainer) {
7974
+ this.resizeObserver.observe(chartScrollContainer);
7975
+ }
7976
+ if (hostElement) {
7977
+ this.resizeObserver.observe(hostElement);
7978
+ }
7979
+ }
7980
+ scheduleResize() {
7981
+ if (!this.chart) {
7982
+ return;
7983
+ }
7984
+ requestAnimationFrame(() => {
7985
+ if (!this.chart) {
7986
+ return;
7987
+ }
7988
+ this.chart.resize();
7989
+ this.chart.update('none');
7990
+ });
7991
+ }
7992
+ destroyChart() {
7993
+ this.chart?.destroy();
7994
+ this.chart = undefined;
7836
7995
  }
7837
7996
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTChartComparisonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7838
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PTChartComparisonComponent, isStandalone: false, selector: "pt-chart-comparison", inputs: { chartConfig: "chartConfig", medianTitle: "medianTitle", xAxisTitle: "xAxisTitle", yAxisTitle: "yAxisTitle", yMin: "yMin", yMax: "yMax", yStepSize: "yStepSize", chartHeight: "chartHeight", chartWidth: "chartWidth" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["chartCanvas"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"chart-scroll-container\">\n <div\n class=\"chart-inner\"\n [style.width]=\"chartConfig.chartWidth || '1200px'\"\n [style.height]=\"chartConfig.chartHeight || '400px'\"\n >\n <canvas #chartCanvas></canvas>\n </div>\n</div>\n", styles: [".chart-scroll-container{width:100%;overflow-x:auto;white-space:nowrap;padding-bottom:10px}.chart-inner{display:inline-block}\n"] }); }
7997
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PTChartComparisonComponent, isStandalone: false, selector: "pt-chart-comparison", inputs: { chartConfig: "chartConfig", medianTitle: "medianTitle", xAxisTitle: "xAxisTitle", yAxisTitle: "yAxisTitle", yMin: "yMin", yMax: "yMax", yStepSize: "yStepSize", chartHeight: "chartHeight", chartWidth: "chartWidth" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["chartCanvas"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-scroll-container\">\n <div\n class=\"chart-inner\"\n [style.width]=\"resolvedChartWidth\"\n [style.height]=\"resolvedChartHeight\"\n >\n <canvas #chartCanvas></canvas>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-width:0;min-height:0}.chart-scroll-container{width:100%;height:100%;min-width:0;min-height:0;overflow-x:auto;overflow-y:hidden;box-sizing:border-box;padding-bottom:.625rem}.chart-inner{display:block;min-width:0;min-height:0;box-sizing:border-box}.chart-inner canvas{display:block;width:100%!important;height:100%!important;max-width:none;max-height:none}\n"] }); }
7839
7998
  }
7840
7999
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTChartComparisonComponent, decorators: [{
7841
8000
  type: Component,
7842
- args: [{ selector: 'pt-chart-comparison', standalone: false, template: "<div class=\"chart-scroll-container\">\n <div\n class=\"chart-inner\"\n [style.width]=\"chartConfig.chartWidth || '1200px'\"\n [style.height]=\"chartConfig.chartHeight || '400px'\"\n >\n <canvas #chartCanvas></canvas>\n </div>\n</div>\n", styles: [".chart-scroll-container{width:100%;overflow-x:auto;white-space:nowrap;padding-bottom:10px}.chart-inner{display:inline-block}\n"] }]
8001
+ args: [{ selector: 'pt-chart-comparison', standalone: false, template: "<div class=\"chart-scroll-container\">\n <div\n class=\"chart-inner\"\n [style.width]=\"resolvedChartWidth\"\n [style.height]=\"resolvedChartHeight\"\n >\n <canvas #chartCanvas></canvas>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-width:0;min-height:0}.chart-scroll-container{width:100%;height:100%;min-width:0;min-height:0;overflow-x:auto;overflow-y:hidden;box-sizing:border-box;padding-bottom:.625rem}.chart-inner{display:block;min-width:0;min-height:0;box-sizing:border-box}.chart-inner canvas{display:block;width:100%!important;height:100%!important;max-width:none;max-height:none}\n"] }]
7843
8002
  }], ctorParameters: () => [], propDecorators: { chartConfig: [{
7844
8003
  type: Input
7845
8004
  }], medianTitle: [{