From c0f02424fd877fab19371ae6bc05a706151399c7 Mon Sep 17 00:00:00 2001 From: Jeremy Foster Date: Thu, 29 Aug 2024 08:22:45 -0700 Subject: [PATCH] Add chart auto resize --- .../ReportSectionMediaAnalytics.tsx | 2 +- .../ReportSectionMediaAnalyticsChart.tsx | 47 ++++++++++++--- .../template/ReportSectionMediaAnalytics.tsx | 2 +- .../settings/template/charts/ChartViewer.tsx | 31 +++++----- .../settings/template/utils/calcAutoSize.ts | 33 +++++++++++ .../template/utils/generateChartOptions.ts | 8 +-- .../settings/template/utils/getChartData.ts | 2 - .../edit/settings/template/utils/index.ts | 2 + .../template/utils/shouldResizeChart.ts | 21 +++++++ .../Models/Charts/ChartDatasetModel.cs | 8 +-- .../Charts/Options/ChartOptionsModel.cs | 12 ++++ libs/net/template/ReportEngine.cs | 57 ++++++++++++++++++- 12 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 app/subscriber/src/features/my-reports/edit/settings/template/utils/calcAutoSize.ts create mode 100644 app/subscriber/src/features/my-reports/edit/settings/template/utils/shouldResizeChart.ts diff --git a/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalytics.tsx b/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalytics.tsx index d3bb0952bc..6e83a92f2b 100644 --- a/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalytics.tsx +++ b/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalytics.tsx @@ -234,7 +234,7 @@ export const ReportSectionMediaAnalytics = ({ index }: IReportSectionMediaAnalyt ? chart.settings.datasetValue[0] : '', excludeEmptyValues: false, - isHorizontal: false, + isHorizontal: true, showDataLabels: false, width: 500, options: { ...chart.settings?.options }, diff --git a/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalyticsChart.tsx b/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalyticsChart.tsx index 700e75e258..9bfc4e2d3f 100644 --- a/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalyticsChart.tsx +++ b/app/editor/src/features/admin/reports/components/ReportSectionMediaAnalyticsChart.tsx @@ -89,22 +89,55 @@ export const ReportSectionMediaAnalyticsChart = ({ options={chartTypeOptions.filter((o) => chart.settings.chartTypes.includes(o.value))} isClearable={false} /> + { + const value = e.target.value.trim(); + setFieldValue( + `sections.${sectionIndex}.chartTemplates.${chartIndex}.sectionSettings.aspectRatio`, + value ? +value : undefined, + ); + }} type="number" width="10ch" + disabled={!!chart.sectionSettings.height} /> + or { + const value = e.target.value.trim(); + setFieldValue( + `sections.${sectionIndex}.chartTemplates.${chartIndex}.sectionSettings`, + { + ...chart.sectionSettings, + height: value ? +value : undefined, + aspectRatio: value ? undefined : 1, + }, + ); + }} type="number" width="10ch" + disabled={!!chart.sectionSettings.aspectRatio} /> + - { setFieldValue( `sections.${sectionIndex}.chartTemplates.${chartIndex}.sectionSettings`, mergeChartSettings(chart.settings.options, chart.sectionSettings, { - isHorizontal: e.target.checked, + isHorizontal: !e.target.checked, }), ); }} diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/ReportSectionMediaAnalytics.tsx b/app/subscriber/src/features/my-reports/edit/settings/template/ReportSectionMediaAnalytics.tsx index 69cc99984d..2d91aa762b 100644 --- a/app/subscriber/src/features/my-reports/edit/settings/template/ReportSectionMediaAnalytics.tsx +++ b/app/subscriber/src/features/my-reports/edit/settings/template/ReportSectionMediaAnalytics.tsx @@ -80,7 +80,7 @@ export const ReportSectionMediaAnalytics = React.forwardRef< ? chart.settings.datasetValue[0] : '', excludeEmptyValues: false, - isHorizontal: false, + isHorizontal: true, showDataLabels: false, width: 500, options: { ...chart.settings?.options }, diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/charts/ChartViewer.tsx b/app/subscriber/src/features/my-reports/edit/settings/template/charts/ChartViewer.tsx index 7ff32b026d..b07f259372 100644 --- a/app/subscriber/src/features/my-reports/edit/settings/template/charts/ChartViewer.tsx +++ b/app/subscriber/src/features/my-reports/edit/settings/template/charts/ChartViewer.tsx @@ -16,12 +16,14 @@ import React from 'react'; import { useModal } from 'tno-core'; import { + calcAutoSize, determineBackgroundColor, determineBorderColor, generateChartOptions, getSectionLabel, IChartData, IChartDataset, + shouldResizeChart, } from '../utils'; import { IChartSectionProps } from './IChartSectionProps'; @@ -65,9 +67,11 @@ export const ChartViewer: React.FC = ({ const uid = `${section.id}_${sectionIndex}`; React.useEffect(() => { - const axisColumnWidth = - data && chart.sectionSettings.width ? chart.sectionSettings.width / data.labels.length : 0; - if (data && chart.sectionSettings.autoResize && axisColumnWidth < minAxisColumnWidth) { + if ( + data && + chart.sectionSettings.autoResize && + shouldResizeChart(data.labels.length, chart.sectionSettings, minAxisColumnWidth) + ) { toggleModal(); } // Only update when values change in data or chart. @@ -75,6 +79,7 @@ export const ChartViewer: React.FC = ({ }, [ chart.sectionSettings.autoResize, chart.sectionSettings.width, + chart.sectionSettings.height, data?.labels.length, minAxisColumnWidth, ]); @@ -135,17 +140,15 @@ export const ChartViewer: React.FC = ({ if (data) { // Calculate a new size for the chart based on the number of labels on the axis. // Handle the direction of the axis, and aspect ratio. - const isHorizontal = - chart.sectionSettings.isHorizontal || - chart.sectionSettings.isHorizontal === undefined; - const size = data.labels.length * minAxisColumnWidth; - const height = isHorizontal ? chart.sectionSettings.height : size; - setFieldValue(`sections.${sectionIndex}.chartTemplates.${chartIndex}.sectionSettings`, { - ...chart.sectionSettings, - width: isHorizontal ? size : chart.sectionSettings.width, - height: height, - aspectRatio: height ? undefined : chart.sectionSettings.aspectRatio, - }); + const settings = calcAutoSize( + data.labels.length, + chart.sectionSettings, + minAxisColumnWidth, + ); + setFieldValue( + `sections.${sectionIndex}.chartTemplates.${chartIndex}.sectionSettings`, + settings, + ); } toggleModal(); }} diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/utils/calcAutoSize.ts b/app/subscriber/src/features/my-reports/edit/settings/template/utils/calcAutoSize.ts new file mode 100644 index 0000000000..477f585a06 --- /dev/null +++ b/app/subscriber/src/features/my-reports/edit/settings/template/utils/calcAutoSize.ts @@ -0,0 +1,33 @@ +import { IChartSectionSettingsModel } from 'tno-core'; + +/** + * Determines if the size of the chart will fit the axis labels. + * If not it will generate a new chart section settings. + * @param axisLabelCount The number of axis labels. + * @param chartSectionSettings The chart section settings. + * @param minAxisColumnWidth The minimum size of an axis column (defaults 30). + * @returns Updated chart section settings. + */ +export const calcAutoSize = ( + axisLabelCount: number, + chartSectionSettings: IChartSectionSettingsModel, + minAxisColumnWidth: number = 30, +) => { + const size = chartSectionSettings.isHorizontal + ? chartSectionSettings.width + : chartSectionSettings.height; + if (!size || !axisLabelCount) return chartSectionSettings; + const axisColumnWidth = size / axisLabelCount; + if (axisColumnWidth < minAxisColumnWidth) { + const newSize = minAxisColumnWidth * axisLabelCount; + const width = chartSectionSettings.isHorizontal ? newSize : chartSectionSettings.width; + const height = chartSectionSettings.isHorizontal ? chartSectionSettings.height : newSize; + return { + ...chartSectionSettings, + width, + height, + aspectRatio: height ? undefined : chartSectionSettings.aspectRatio, + }; + } + return chartSectionSettings; +}; diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/utils/generateChartOptions.ts b/app/subscriber/src/features/my-reports/edit/settings/template/utils/generateChartOptions.ts index cc32c430d5..ac75513d8c 100644 --- a/app/subscriber/src/features/my-reports/edit/settings/template/utils/generateChartOptions.ts +++ b/app/subscriber/src/features/my-reports/edit/settings/template/utils/generateChartOptions.ts @@ -23,10 +23,10 @@ export const generateChartOptions = ( x: { stacked: chartSectionSettings.stacked, display: chartSectionSettings.xShowAxisLabels, - suggestedMin: chartSectionSettings.isHorizontal + suggestedMin: !chartSectionSettings.isHorizontal ? chartSectionSettings.scaleSuggestedMin : undefined, - suggestedMax: chartSectionSettings.isHorizontal + suggestedMax: !chartSectionSettings.isHorizontal ? chartSectionSettings.scaleSuggestedMax ?? scaleCalcMax : undefined, title: { @@ -46,10 +46,10 @@ export const generateChartOptions = ( y: { stacked: chartSectionSettings.stacked, display: chartSectionSettings.yShowAxisLabels, - suggestedMin: !chartSectionSettings.isHorizontal + suggestedMin: chartSectionSettings.isHorizontal ? chartSectionSettings.scaleSuggestedMin : undefined, - suggestedMax: !chartSectionSettings.isHorizontal + suggestedMax: chartSectionSettings.isHorizontal ? chartSectionSettings.scaleSuggestedMax ?? scaleCalcMax : undefined, title: { diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/utils/getChartData.ts b/app/subscriber/src/features/my-reports/edit/settings/template/utils/getChartData.ts index c42b27d3a9..7bd9b56214 100644 --- a/app/subscriber/src/features/my-reports/edit/settings/template/utils/getChartData.ts +++ b/app/subscriber/src/features/my-reports/edit/settings/template/utils/getChartData.ts @@ -97,8 +97,6 @@ export const getChartData = ( }; }); - console.debug(labels, groups, results); - return { labels, datasets: results, diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/utils/index.ts b/app/subscriber/src/features/my-reports/edit/settings/template/utils/index.ts index c570ae23fd..8a687daeb9 100644 --- a/app/subscriber/src/features/my-reports/edit/settings/template/utils/index.ts +++ b/app/subscriber/src/features/my-reports/edit/settings/template/utils/index.ts @@ -1,3 +1,4 @@ +export * from './calcAutoSize'; export * from './calcAverageSentiment'; export * from './calcDataPoint'; export * from './convertToChart'; @@ -26,3 +27,4 @@ export * from './selectColorForDataset'; export * from './selectSentimentColorForDataset'; export * from './selectSentimentColorForValue'; export * from './separateDatasets'; +export * from './shouldResizeChart'; diff --git a/app/subscriber/src/features/my-reports/edit/settings/template/utils/shouldResizeChart.ts b/app/subscriber/src/features/my-reports/edit/settings/template/utils/shouldResizeChart.ts new file mode 100644 index 0000000000..da43e15cef --- /dev/null +++ b/app/subscriber/src/features/my-reports/edit/settings/template/utils/shouldResizeChart.ts @@ -0,0 +1,21 @@ +import { IChartSectionSettingsModel } from 'tno-core'; + +/** + * Determines if the size of the chart will fit the axis labels. + * @param axisLabelCount The number of axis labels. + * @param chartSectionSettings The chart section settings. + * @param minAxisColumnWidth The minimum size of an axis column (defaults 30). + * @returns True if the chart should resize. + */ +export const shouldResizeChart = ( + axisLabelCount: number, + chartSectionSettings: IChartSectionSettingsModel, + minAxisColumnWidth: number = 30, +) => { + const size = !chartSectionSettings.isHorizontal + ? chartSectionSettings.height + : chartSectionSettings.width; + if (!size || !axisLabelCount) return false; + const axisColumnWidth = size / axisLabelCount; + return axisColumnWidth < minAxisColumnWidth; +}; diff --git a/libs/net/template/Models/Charts/ChartDatasetModel.cs b/libs/net/template/Models/Charts/ChartDatasetModel.cs index d41379c44a..616612e9a2 100644 --- a/libs/net/template/Models/Charts/ChartDatasetModel.cs +++ b/libs/net/template/Models/Charts/ChartDatasetModel.cs @@ -21,16 +21,16 @@ public class ChartDatasetModel public double?[] Data { get; set; } = Array.Empty(); /// - /// get/set - + /// get/set - Either an array of colours, or a function. /// [JsonPropertyName("backgroundColor")] - public string[]? BackgroundColor { get; set; } + public object? BackgroundColor { get; set; } /// - /// get/set - + /// get/set - Either an array of colours, or a function. /// [JsonPropertyName("borderColor")] - public string[]? BorderColor { get; set; } + public object? BorderColor { get; set; } /// /// get/set - diff --git a/libs/net/template/Models/Charts/Options/ChartOptionsModel.cs b/libs/net/template/Models/Charts/Options/ChartOptionsModel.cs index d19e67ee33..9286cbacf1 100644 --- a/libs/net/template/Models/Charts/Options/ChartOptionsModel.cs +++ b/libs/net/template/Models/Charts/Options/ChartOptionsModel.cs @@ -25,5 +25,17 @@ public class ChartOptionsModel /// [JsonPropertyName("indexAxis")] public string? IndexAxis { get; set; } + + /// + /// get/set - + /// + [JsonPropertyName("aspectRatio")] + public string? AspectRatio { get; set; } + + /// + /// get/set - + /// + [JsonPropertyName("maintainAspectRatio")] + public string? MaintainAspectRatio { get; set; } #endregion } diff --git a/libs/net/template/ReportEngine.cs b/libs/net/template/ReportEngine.cs index aefa4b81a4..e49a53ec3b 100644 --- a/libs/net/template/ReportEngine.cs +++ b/libs/net/template/ReportEngine.cs @@ -190,22 +190,22 @@ public async Task GenerateBase64ImageAsync( // Get the Chart JSON data. var data = model.ChartData ?? (await this.GenerateJsonAsync(model, isPreview)).Json; var dataJson = data.ToJson(); + var dataModel = JsonSerializer.Deserialize(dataJson, this.SerializerOptions); // If chart settings require auto scale if (model.ChartTemplate.SectionSettings.ScaleCalcMax.HasValue) { // Determine the maximum scale and add the auto max to it. - var dataModel = JsonSerializer.Deserialize(dataJson, this.SerializerOptions); var max = dataModel?.Datasets.Any() == true ? dataModel?.Datasets.Max(ds => ds.Data.Any() ? ds.Data.Max(v => v ?? 0) : 0) : null; if (max.HasValue) { - var suggestedMax = (int)max + model.ChartTemplate.SectionSettings.ScaleCalcMax.Value; var sectionJsonText = model.ChartTemplate.SectionSettings.Options.ToJson(); if (sectionJsonText != "{}") { var chartOptions = JsonSerializer.Deserialize(model.ChartTemplate.SectionSettings.Options); if (chartOptions != null) { + var suggestedMax = (int)max + model.ChartTemplate.SectionSettings.ScaleCalcMax.Value; chartOptions.Scales.X.SuggestedMax = suggestedMax; chartOptions.Scales.Y.SuggestedMax = suggestedMax; model.ChartTemplate.SectionSettings.Options = JsonDocument.Parse(JsonSerializer.Serialize(chartOptions, this.SerializerOptions)); @@ -213,6 +213,20 @@ public async Task GenerateBase64ImageAsync( } } } + if (dataModel != null && model.ChartTemplate.SectionSettings.AutoResize == true && ShouldResize(dataModel, model.ChartTemplate.SectionSettings, 30)) + { + // If the chart should be resized, calculate the new size and update the options. + UpdateChartSize(dataModel, model.ChartTemplate.SectionSettings, 30); + var sectionJsonText = model.ChartTemplate.SectionSettings.Options.ToJson(); + if (sectionJsonText != "{}") + { + var chartOptions = JsonSerializer.Deserialize(model.ChartTemplate.SectionSettings.Options); + if (chartOptions != null) + { + model.ChartTemplate.SectionSettings.Options = JsonDocument.Parse(JsonSerializer.Serialize(chartOptions, this.SerializerOptions)); + } + } + } var optionsJson = model.ChartTemplate.SectionSettings.Options != null ? JsonSerializer.Serialize(MergeChartOptions(model.ChartTemplate.Settings, model.ChartTemplate.SectionSettings)) : "{}"; // Modify the chart options based on section settings. @@ -232,6 +246,45 @@ public async Task GenerateBase64ImageAsync( return await response.Content.ReadAsStringAsync(); } + /// + /// Determine if the chart should be resized, based on the number of axis labels. + /// + /// + /// + /// + /// + private bool ShouldResize(ChartDataModel data, API.Models.Settings.ChartSectionSettingsModel settings, int minAxisColumnWidth) + { + var axisLabelCount = data.Labels.Length; + var size = settings.IsHorizontal == false ? settings.Height : settings.Width; + if (size == null || size <= 0 || axisLabelCount <= 0) return false; + var currentAxisColumnWidth = size / axisLabelCount; + return currentAxisColumnWidth < minAxisColumnWidth; + } + + /// + /// Resize the chart based on the number of axis labels. + /// + /// + /// + /// + private void UpdateChartSize(ChartDataModel data, API.Models.Settings.ChartSectionSettingsModel settings, int minAxisColumnWidth) + { + var axisLabelCount = data.Labels.Length; + var size = settings.IsHorizontal == false ? settings.Height : settings.Width; + if (size == null || size <= 0 || axisLabelCount <= 0) return; + var currentAxisColumnWidth = size / axisLabelCount; + if (currentAxisColumnWidth < minAxisColumnWidth) + { + var newSize = minAxisColumnWidth * axisLabelCount; + var width = settings.IsHorizontal == false ? settings.Width : newSize; + var height = settings.IsHorizontal == false ? newSize : settings.Height; + settings.Width = width; + settings.Height = height; + settings.AspectRatio = height.HasValue ? null : settings.AspectRatio; + } + } + #region Content /// /// Generate the output of the report with the Razor engine.