@odoo/o-spreadsheet 19.5.0-alpha.11 → 19.5.0-alpha.12
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/dist/material_symbols_outlined.css +9 -0
- package/dist/o_spreadsheet.cjs +2313 -656
- package/dist/o_spreadsheet.css +169 -33
- package/dist/o_spreadsheet.esm.js +2305 -648
- package/dist/o_spreadsheet.iife.js +2313 -656
- package/dist/o_spreadsheet.min.iife.js +344 -344
- package/dist/o_spreadsheet.xml +209 -128
- package/dist/types/actions/data_actions.d.ts +1 -0
- package/dist/types/components/bottom_bar/bottom_bar_statistic/bottom_bar_statistic.d.ts +3 -0
- package/dist/types/components/dashboard/clickable_cell_sort_icon/clickable_cell_sort_icon.d.ts +1 -1
- package/dist/types/components/figures/figure/figure.d.ts +1 -0
- package/dist/types/components/figures/figure_carousel/figure_carousel.d.ts +10 -0
- package/dist/types/components/figures/figure_container/figure_container.d.ts +1 -2
- package/dist/types/components/grid/grid.d.ts +1 -0
- package/dist/types/components/helpers/chart_drag_and_drop.d.ts +15 -0
- package/dist/types/components/helpers/dom_helpers.d.ts +3 -2
- package/dist/types/components/link/link_editor/link_editor.d.ts +1 -1
- package/dist/types/components/side_panel/carousel_panel/carousel_panel.d.ts +1 -1
- package/dist/types/components/side_panel/chart/building_blocks/color_scale/color_scale_picker.d.ts +1 -1
- package/dist/types/components/side_panel/data_analysis/chart_suggestion_preview.d.ts +19 -0
- package/dist/types/components/side_panel/data_analysis/data_analysis_panel.d.ts +17 -0
- package/dist/types/components/side_panel/data_analysis/data_analysis_store.d.ts +15 -0
- package/dist/types/components/side_panel/pivot/pivot_layout_configurator/pivot_layout_configurator.d.ts +1 -6
- package/dist/types/components/side_panel/side_panel/side_panel_store.d.ts +2 -1
- package/dist/types/components/standalone_grid_canvas/carousel_data_view_print.d.ts +32 -0
- package/dist/types/components/standalone_grid_canvas/standalone_grid_canvas.d.ts +6 -0
- package/dist/types/components/standalone_viewport/standalone_viewport.d.ts +6 -3
- package/dist/types/components/standalone_viewport/standalone_viewport_store.d.ts +2 -1
- package/dist/types/components/tables/hovered_table_store.d.ts +6 -7
- package/dist/types/constants.d.ts +13 -1
- package/dist/types/functions/helpers.d.ts +4 -0
- package/dist/types/functions/module_operators.d.ts +1 -1
- package/dist/types/functions/module_text.d.ts +11 -11
- package/dist/types/functions/module_web.d.ts +1 -1
- package/dist/types/helpers/carousel_helpers.d.ts +8 -1
- package/dist/types/helpers/data_analysis.d.ts +16 -0
- package/dist/types/helpers/figures/charts/chart_data_source_helpers.d.ts +10 -0
- package/dist/types/helpers/figures/charts/chart_suggestion_engine.d.ts +8 -0
- package/dist/types/helpers/figures/charts/chart_ui_common.d.ts +8 -1
- package/dist/types/helpers/figures/charts/charts_suggestions_constants.d.ts +37 -0
- package/dist/types/helpers/figures/charts/gauge_chart_rendering.d.ts +6 -2
- package/dist/types/helpers/text_helper.d.ts +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/registries/figures_registry.d.ts +1 -0
- package/dist/types/stores/cell_hover_overlay_store.d.ts +15 -0
- package/dist/types/stores/chart_drag_store.d.ts +10 -0
- package/dist/types/stores/grid_renderer_store.d.ts +3 -2
- package/dist/types/types/chart/chart_suggestion.d.ts +136 -0
- package/dist/types/types/commands.d.ts +16 -9
- package/package.json +4 -4
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 19.5.0-alpha.
|
|
6
|
-
* @date 2026-08-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.5.0-alpha.12
|
|
6
|
+
* @date 2026-08-19T09:47:03.774Z
|
|
7
|
+
* @hash 68ca68e
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as owl from "@odoo/owl";
|
|
@@ -206,6 +206,16 @@ function getBoundingRectAsPOJO(el) {
|
|
|
206
206
|
height: rect.height
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
|
+
function getBoundingRectWithMargins(el) {
|
|
210
|
+
const style = getComputedStyle(el);
|
|
211
|
+
const rect = el.getBoundingClientRect();
|
|
212
|
+
return {
|
|
213
|
+
x: rect.x,
|
|
214
|
+
y: rect.y,
|
|
215
|
+
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
216
|
+
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
217
|
+
};
|
|
218
|
+
}
|
|
209
219
|
/**
|
|
210
220
|
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
211
221
|
*/
|
|
@@ -649,6 +659,14 @@ const DEFAULT_CAROUSEL_TITLE_STYLE = {
|
|
|
649
659
|
fontSize: 18,
|
|
650
660
|
color: TEXT_BODY
|
|
651
661
|
};
|
|
662
|
+
const CAROUSEL_LAYOUT = {
|
|
663
|
+
paddingX: 24,
|
|
664
|
+
paddingY: 8,
|
|
665
|
+
headerPaddingTop: 4,
|
|
666
|
+
separatorWidth: 1,
|
|
667
|
+
headerLineHeight: 1.5,
|
|
668
|
+
minHeaderHeight: 25
|
|
669
|
+
};
|
|
652
670
|
const DEFAULT_TOKEN_COLOR = "light-dark(#000000, #ffffff)";
|
|
653
671
|
const functionColor = DEFAULT_TOKEN_COLOR;
|
|
654
672
|
const operatorColor = "#3da4ab";
|
|
@@ -4071,6 +4089,18 @@ function toString(data) {
|
|
|
4071
4089
|
default: return "";
|
|
4072
4090
|
}
|
|
4073
4091
|
}
|
|
4092
|
+
/**
|
|
4093
|
+
* Only converts the decimal separator, ignore number format such as % or dates
|
|
4094
|
+
*/
|
|
4095
|
+
function toLocaleString(data, locale) {
|
|
4096
|
+
const value = toValue(data);
|
|
4097
|
+
switch (typeof value) {
|
|
4098
|
+
case "string": return value;
|
|
4099
|
+
case "number": return value.toString().replace(".", locale.decimalSeparator);
|
|
4100
|
+
case "boolean": return value ? "TRUE" : "FALSE";
|
|
4101
|
+
default: return "";
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4074
4104
|
/** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
|
|
4075
4105
|
const normalizeString = memoize(function normalizeString(str) {
|
|
4076
4106
|
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -9247,8 +9277,8 @@ function computeCachedTextDimension(context, text, font) {
|
|
|
9247
9277
|
function fontSizeInPixels(fontSize) {
|
|
9248
9278
|
return Math.round(fontSize * 96 / 72);
|
|
9249
9279
|
}
|
|
9250
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
9251
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" :
|
|
9280
|
+
function computeTextFont(style, fontUnit = "pt", fontWeight = 400) {
|
|
9281
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : fontWeight} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9252
9282
|
}
|
|
9253
9283
|
function computeTextFontSizeInPixels(style) {
|
|
9254
9284
|
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
@@ -11882,7 +11912,7 @@ var ChartJsComponent = class extends Component {
|
|
|
11882
11912
|
chartId: types$1.string(),
|
|
11883
11913
|
isFullScreen: types$1.boolean().optional()
|
|
11884
11914
|
});
|
|
11885
|
-
canvas = signal(
|
|
11915
|
+
canvas = signal.ref(HTMLCanvasElement);
|
|
11886
11916
|
chart;
|
|
11887
11917
|
currentRuntime;
|
|
11888
11918
|
animationStore;
|
|
@@ -12605,7 +12635,7 @@ var ScorecardChart = class extends Component {
|
|
|
12605
12635
|
chartId: types$1.string(),
|
|
12606
12636
|
isFullScreen: types$1.boolean().optional()
|
|
12607
12637
|
});
|
|
12608
|
-
canvas = signal(
|
|
12638
|
+
canvas = signal.ref(HTMLCanvasElement);
|
|
12609
12639
|
zoomStore;
|
|
12610
12640
|
get runtime() {
|
|
12611
12641
|
return this.env.model.getters.getChartRuntime(this.props.chartId);
|
|
@@ -13990,6 +14020,44 @@ function getCarouselItemTitle(getters, item) {
|
|
|
13990
14020
|
const definition = getters.getChartDefinition(item.chartId);
|
|
13991
14021
|
return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
|
|
13992
14022
|
}
|
|
14023
|
+
function getCarouselLayout(figureRect, carousel, selectedItem) {
|
|
14024
|
+
const layout = CAROUSEL_LAYOUT;
|
|
14025
|
+
const title = {
|
|
14026
|
+
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
14027
|
+
...carousel.title
|
|
14028
|
+
};
|
|
14029
|
+
const x = figureRect.x + layout.paddingX;
|
|
14030
|
+
const width = Math.max(0, figureRect.width - 2 * layout.paddingX);
|
|
14031
|
+
const titleHeight = title.text ? title.fontSize * layout.headerLineHeight : 0;
|
|
14032
|
+
const headerRect = {
|
|
14033
|
+
x,
|
|
14034
|
+
y: figureRect.y + layout.paddingY + layout.headerPaddingTop,
|
|
14035
|
+
width,
|
|
14036
|
+
height: Math.max(layout.minHeaderHeight, titleHeight)
|
|
14037
|
+
};
|
|
14038
|
+
const headerBottom = headerRect.y + headerRect.height;
|
|
14039
|
+
const separatorRect = title.text ? {
|
|
14040
|
+
x,
|
|
14041
|
+
y: headerBottom + layout.paddingY,
|
|
14042
|
+
width,
|
|
14043
|
+
height: layout.separatorWidth
|
|
14044
|
+
} : void 0;
|
|
14045
|
+
const contentPaddingTop = selectedItem?.type === "carouselDataView" ? layout.paddingY : 0;
|
|
14046
|
+
const rectAboveContent = separatorRect || headerRect;
|
|
14047
|
+
const contentY = rectAboveContent.y + rectAboveContent.height + contentPaddingTop;
|
|
14048
|
+
const contentBottom = figureRect.y + figureRect.height - layout.paddingY;
|
|
14049
|
+
return {
|
|
14050
|
+
headerRect,
|
|
14051
|
+
separatorRect,
|
|
14052
|
+
contentRect: {
|
|
14053
|
+
x,
|
|
14054
|
+
y: contentY,
|
|
14055
|
+
width,
|
|
14056
|
+
height: Math.max(0, contentBottom - contentY)
|
|
14057
|
+
},
|
|
14058
|
+
headerLineHeight: layout.headerLineHeight
|
|
14059
|
+
};
|
|
14060
|
+
}
|
|
13993
14061
|
|
|
13994
14062
|
//#endregion
|
|
13995
14063
|
//#region src/helpers/figures/charts/gauge_chart_rendering.ts
|
|
@@ -14001,7 +14069,7 @@ const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
|
14001
14069
|
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
14002
14070
|
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
14003
14071
|
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
14004
|
-
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
14072
|
+
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
|
|
14005
14073
|
const size = dimensions ?? getCanvasSize(canvas);
|
|
14006
14074
|
const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
14007
14075
|
canvas.width = size.width * dpr;
|
|
@@ -14014,7 +14082,7 @@ function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
|
14014
14082
|
height: size.height / zoom,
|
|
14015
14083
|
x: 0,
|
|
14016
14084
|
y: 0
|
|
14017
|
-
}, runtime, ctx);
|
|
14085
|
+
}, runtime, ctx, options);
|
|
14018
14086
|
drawBackground(ctx, config);
|
|
14019
14087
|
drawGauge(ctx, config);
|
|
14020
14088
|
drawInflectionValues(ctx, config);
|
|
@@ -14102,7 +14170,8 @@ function drawTitle(ctx, config) {
|
|
|
14102
14170
|
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
14103
14171
|
ctx.restore();
|
|
14104
14172
|
}
|
|
14105
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
14173
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
|
|
14174
|
+
const labelFontSize = options?.labelFontSize ?? 12;
|
|
14106
14175
|
const maxValue = runtime.maxValue;
|
|
14107
14176
|
const minValue = runtime.minValue;
|
|
14108
14177
|
const gaugeValue = getGaugeValue(runtime, "animated");
|
|
@@ -14120,14 +14189,14 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14120
14189
|
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
14121
14190
|
const minLabelPosition = {
|
|
14122
14191
|
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
14123
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14192
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14124
14193
|
};
|
|
14125
14194
|
const maxLabelPosition = {
|
|
14126
14195
|
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
14127
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14196
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14128
14197
|
};
|
|
14129
14198
|
const textColor = chartMutedFontColor(runtime.background);
|
|
14130
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
14199
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
|
|
14131
14200
|
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
14132
14201
|
if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
|
|
14133
14202
|
fontSize: 16,
|
|
@@ -14175,13 +14244,13 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14175
14244
|
minLabel: {
|
|
14176
14245
|
label: runtime.minValue.label,
|
|
14177
14246
|
textPosition: minLabelPosition,
|
|
14178
|
-
fontSize:
|
|
14247
|
+
fontSize: labelFontSize,
|
|
14179
14248
|
color: textColor
|
|
14180
14249
|
},
|
|
14181
14250
|
maxLabel: {
|
|
14182
14251
|
label: runtime.maxValue.label,
|
|
14183
14252
|
textPosition: maxLabelPosition,
|
|
14184
|
-
fontSize:
|
|
14253
|
+
fontSize: labelFontSize,
|
|
14185
14254
|
color: textColor
|
|
14186
14255
|
}
|
|
14187
14256
|
};
|
|
@@ -14215,27 +14284,27 @@ function getGaugeRect(boundingRect, title) {
|
|
|
14215
14284
|
*
|
|
14216
14285
|
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
14217
14286
|
*/
|
|
14218
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
14287
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
|
|
14219
14288
|
const maxValue = runtime.maxValue;
|
|
14220
14289
|
const minValue = runtime.minValue;
|
|
14221
14290
|
const gaugeCircleCenter = {
|
|
14222
14291
|
x: gaugeRect.x + gaugeRect.width / 2,
|
|
14223
14292
|
y: gaugeRect.y + gaugeRect.height
|
|
14224
14293
|
};
|
|
14225
|
-
const textStyle = { fontSize:
|
|
14294
|
+
const textStyle = { fontSize: labelFontSize };
|
|
14226
14295
|
const inflectionValues = [];
|
|
14227
14296
|
const inflectionValuesTextRects = [];
|
|
14228
14297
|
for (const inflectionValue of runtime.inflectionValues) {
|
|
14229
14298
|
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
14230
14299
|
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
14231
14300
|
const angle = Math.PI - Math.PI * percentage;
|
|
14232
|
-
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2,
|
|
14233
|
-
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ?
|
|
14301
|
+
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2, labelFontSize);
|
|
14302
|
+
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ? labelFontSize : 0;
|
|
14234
14303
|
inflectionValuesTextRects.push(textRect);
|
|
14235
14304
|
inflectionValues.push({
|
|
14236
14305
|
rotation: angle,
|
|
14237
14306
|
label: inflectionValue.label,
|
|
14238
|
-
fontSize:
|
|
14307
|
+
fontSize: labelFontSize,
|
|
14239
14308
|
color: textColor,
|
|
14240
14309
|
offset
|
|
14241
14310
|
});
|
|
@@ -14355,6 +14424,44 @@ const CHART_COMMON_OPTIONS = {
|
|
|
14355
14424
|
"mouseup"
|
|
14356
14425
|
]
|
|
14357
14426
|
};
|
|
14427
|
+
/**
|
|
14428
|
+
* Sample down a Chart.js configuration's data points to at most `maxPoints`, evenly spaced.
|
|
14429
|
+
* Used for lightweight previews (drag preview, chart suggestions) where rendering every point
|
|
14430
|
+
* of a large dataset would make Chart.js slow without any visible benefit on a small canvas.
|
|
14431
|
+
*/
|
|
14432
|
+
function limitChartConfigDataPoints(config, maxPoints = 150) {
|
|
14433
|
+
const data = config.data;
|
|
14434
|
+
if (!data) return config;
|
|
14435
|
+
return {
|
|
14436
|
+
...config,
|
|
14437
|
+
data: {
|
|
14438
|
+
...data,
|
|
14439
|
+
labels: limitArrayDataPoints(data.labels, maxPoints),
|
|
14440
|
+
datasets: data.datasets?.map((dataset) => limitDatasetDataPoints(dataset, maxPoints))
|
|
14441
|
+
}
|
|
14442
|
+
};
|
|
14443
|
+
}
|
|
14444
|
+
function limitDatasetDataPoints(dataset, maxPoints) {
|
|
14445
|
+
const values = dataset.data;
|
|
14446
|
+
if (!Array.isArray(values) || values.length <= maxPoints) return dataset;
|
|
14447
|
+
const length = values.length;
|
|
14448
|
+
const indices = sampleIndices(length, maxPoints);
|
|
14449
|
+
const limited = { ...dataset };
|
|
14450
|
+
for (const key of Object.keys(limited)) {
|
|
14451
|
+
const value = limited[key];
|
|
14452
|
+
if (Array.isArray(value) && value.length === length) limited[key] = indices.map((i) => value[i]);
|
|
14453
|
+
}
|
|
14454
|
+
return limited;
|
|
14455
|
+
}
|
|
14456
|
+
function limitArrayDataPoints(arr, maxPoints) {
|
|
14457
|
+
if (!Array.isArray(arr) || arr.length <= maxPoints) return arr;
|
|
14458
|
+
return sampleIndices(arr.length, maxPoints).map((i) => arr[i]);
|
|
14459
|
+
}
|
|
14460
|
+
/** Evenly spaced indices sampled from [0, length). */
|
|
14461
|
+
function sampleIndices(length, maxPoints) {
|
|
14462
|
+
const step = length / maxPoints;
|
|
14463
|
+
return Array.from({ length: maxPoints }, (_, i) => Math.min(length - 1, Math.floor(i * step)));
|
|
14464
|
+
}
|
|
14358
14465
|
async function chartToImageUrl(runtime, figure, type) {
|
|
14359
14466
|
try {
|
|
14360
14467
|
const canvas = createRenderingSurface(figure.width, figure.height);
|
|
@@ -14402,7 +14509,7 @@ async function canvasToObjectUrl(canvas) {
|
|
|
14402
14509
|
*
|
|
14403
14510
|
* @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
|
|
14404
14511
|
*/
|
|
14405
|
-
function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
14512
|
+
function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
|
|
14406
14513
|
if ("chartJsConfig" in runtime) {
|
|
14407
14514
|
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
14408
14515
|
const extensionsLoaded = areChartJSExtensionsLoaded();
|
|
@@ -14418,8 +14525,8 @@ function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
|
14418
14525
|
chart.destroy();
|
|
14419
14526
|
if (!extensionsLoaded) unregisterChartJsExtensions();
|
|
14420
14527
|
};
|
|
14421
|
-
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
|
|
14422
|
-
else if (type === "gauge") drawGaugeChart(canvas, runtime,
|
|
14528
|
+
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas, zoom);
|
|
14529
|
+
else if (type === "gauge") drawGaugeChart(canvas, runtime, zoom, size);
|
|
14423
14530
|
return () => {};
|
|
14424
14531
|
}
|
|
14425
14532
|
|
|
@@ -14957,7 +15064,7 @@ var ZoomableChartStore = class extends SpreadsheetStore {
|
|
|
14957
15064
|
var ZoomableChartJsComponent = class extends ChartJsComponent {
|
|
14958
15065
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
14959
15066
|
store;
|
|
14960
|
-
masterChartCanvas = signal(
|
|
15067
|
+
masterChartCanvas = signal.ref(HTMLCanvasElement);
|
|
14961
15068
|
masterChart;
|
|
14962
15069
|
mode;
|
|
14963
15070
|
hasLinearScale;
|
|
@@ -15711,7 +15818,7 @@ var GaugeChartComponent = class extends Component {
|
|
|
15711
15818
|
chartId: types$1.string(),
|
|
15712
15819
|
isFullScreen: types$1.boolean().optional()
|
|
15713
15820
|
});
|
|
15714
|
-
canvas = signal(
|
|
15821
|
+
canvas = signal.ref(HTMLCanvasElement);
|
|
15715
15822
|
animationStore;
|
|
15716
15823
|
zoomStore;
|
|
15717
15824
|
get runtime() {
|
|
@@ -15948,7 +16055,7 @@ var Menu = class extends Component {
|
|
|
15948
16055
|
onKeyDown: types$1.function().optional(),
|
|
15949
16056
|
disableKeyboardNavigation: types$1.boolean().optional()
|
|
15950
16057
|
});
|
|
15951
|
-
menuRef = signal(
|
|
16058
|
+
menuRef = signal.ref();
|
|
15952
16059
|
setup() {
|
|
15953
16060
|
useLayoutEffect(() => {
|
|
15954
16061
|
const menuEl = this.menuRef();
|
|
@@ -16068,8 +16175,8 @@ var Popover = class extends Component {
|
|
|
16068
16175
|
zIndex: types$1.number().optional(),
|
|
16069
16176
|
class: types$1.string().optional()
|
|
16070
16177
|
});
|
|
16071
|
-
popoverRef = signal(
|
|
16072
|
-
popoverContentRef = signal(
|
|
16178
|
+
popoverRef = signal.ref();
|
|
16179
|
+
popoverContentRef = signal.ref();
|
|
16073
16180
|
currentPosition = void 0;
|
|
16074
16181
|
currentDisplayValue = void 0;
|
|
16075
16182
|
spreadsheetRect = useSpreadsheetRect();
|
|
@@ -16257,7 +16364,7 @@ var MenuPopover = class MenuPopover extends Component {
|
|
|
16257
16364
|
isHoveringChild: false
|
|
16258
16365
|
});
|
|
16259
16366
|
state = proxy({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
|
|
16260
|
-
menuRef = signal(
|
|
16367
|
+
menuRef = signal.ref();
|
|
16261
16368
|
openingTimeOut = useTimeOut();
|
|
16262
16369
|
setup() {
|
|
16263
16370
|
const domFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -16546,43 +16653,29 @@ var PositionMap = class {
|
|
|
16546
16653
|
};
|
|
16547
16654
|
|
|
16548
16655
|
//#endregion
|
|
16549
|
-
//#region src/
|
|
16550
|
-
var
|
|
16551
|
-
mutators = [
|
|
16552
|
-
|
|
16656
|
+
//#region src/stores/cell_hover_overlay_store.ts
|
|
16657
|
+
var CellHoverOverlayStore = class extends SpreadsheetStore {
|
|
16658
|
+
mutators = [
|
|
16659
|
+
"hover",
|
|
16660
|
+
"register",
|
|
16661
|
+
"unRegister"
|
|
16662
|
+
];
|
|
16663
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
16664
|
+
providers = [];
|
|
16553
16665
|
overlayColors = new PositionMap();
|
|
16554
|
-
hover(
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
this.
|
|
16666
|
+
hover(hoveredPosition) {
|
|
16667
|
+
this.overlayColors = new PositionMap();
|
|
16668
|
+
if (!hoveredPosition) return;
|
|
16669
|
+
for (const provider of this.providers) {
|
|
16670
|
+
const positions = provider.getHighlightedPositions(hoveredPosition);
|
|
16671
|
+
for (const position of positions) this.overlayColors.set(position, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16672
|
+
}
|
|
16558
16673
|
}
|
|
16559
|
-
|
|
16560
|
-
this.
|
|
16674
|
+
register(highlightProvider) {
|
|
16675
|
+
this.providers.push(highlightProvider);
|
|
16561
16676
|
}
|
|
16562
|
-
|
|
16563
|
-
this.
|
|
16564
|
-
if (!this.position) return;
|
|
16565
|
-
const { sheetId, col, row } = this.position;
|
|
16566
|
-
const table = this.getters.getTable({
|
|
16567
|
-
sheetId,
|
|
16568
|
-
col,
|
|
16569
|
-
row
|
|
16570
|
-
});
|
|
16571
|
-
if (!table) return;
|
|
16572
|
-
const { left, right, top } = table.range.zone;
|
|
16573
|
-
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
16574
|
-
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
16575
|
-
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
16576
|
-
sheetId,
|
|
16577
|
-
col,
|
|
16578
|
-
row
|
|
16579
|
-
}).formattedValue;
|
|
16580
|
-
});
|
|
16581
|
-
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
|
|
16582
|
-
sheetId,
|
|
16583
|
-
col,
|
|
16584
|
-
row
|
|
16585
|
-
}, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16677
|
+
unRegister(highlightProvider) {
|
|
16678
|
+
this.providers = this.providers.filter((h) => h !== highlightProvider);
|
|
16586
16679
|
}
|
|
16587
16680
|
};
|
|
16588
16681
|
|
|
@@ -16594,9 +16687,9 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16594
16687
|
position: types$1.CellPosition(),
|
|
16595
16688
|
sortDirection: types$1.or([types$1.SortDirection, types$1.literal("none")])
|
|
16596
16689
|
});
|
|
16597
|
-
|
|
16690
|
+
hoveredCellOverlayStore;
|
|
16598
16691
|
setup() {
|
|
16599
|
-
this.
|
|
16692
|
+
this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
|
|
16600
16693
|
}
|
|
16601
16694
|
get style() {
|
|
16602
16695
|
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
|
|
@@ -16616,7 +16709,7 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16616
16709
|
}
|
|
16617
16710
|
}
|
|
16618
16711
|
getBackgroundColor(cellStyle) {
|
|
16619
|
-
const overlayColor = this.
|
|
16712
|
+
const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
|
|
16620
16713
|
if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
|
|
16621
16714
|
return cellStyle.fillColor || "#FFFFFF";
|
|
16622
16715
|
}
|
|
@@ -21355,7 +21448,7 @@ var HoveredIconStore = class extends SpreadsheetStore {
|
|
|
21355
21448
|
//#region src/components/grid_overlay/grid_overlay.ts
|
|
21356
21449
|
function useCellHovered(env, gridRef) {
|
|
21357
21450
|
const delayedHoveredCell = useStore(DelayedHoveredCellStore);
|
|
21358
|
-
const
|
|
21451
|
+
const cellHoverOverlay = useStore(CellHoverOverlayStore);
|
|
21359
21452
|
const viewStore = useStore(ViewportsStore);
|
|
21360
21453
|
const hoveredPosition = {
|
|
21361
21454
|
col: void 0,
|
|
@@ -21399,7 +21492,9 @@ function useCellHovered(env, gridRef) {
|
|
|
21399
21492
|
if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
|
|
21400
21493
|
({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
|
|
21401
21494
|
lastMoved = Date.now();
|
|
21402
|
-
|
|
21495
|
+
const position = getPosition();
|
|
21496
|
+
if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
|
|
21497
|
+
else cellHoverOverlay.hover(getPosition());
|
|
21403
21498
|
}
|
|
21404
21499
|
}
|
|
21405
21500
|
function recompute() {
|
|
@@ -21410,7 +21505,7 @@ function useCellHovered(env, gridRef) {
|
|
|
21410
21505
|
const zoomedMouseEvent = withZoom(env, e);
|
|
21411
21506
|
const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
|
|
21412
21507
|
const gridRect = getElBoundingRect(gridRef());
|
|
21413
|
-
if (y
|
|
21508
|
+
if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
|
|
21414
21509
|
else return pause();
|
|
21415
21510
|
}
|
|
21416
21511
|
useListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
|
|
@@ -21447,7 +21542,7 @@ var GridOverlay = class extends Component {
|
|
|
21447
21542
|
gridOverlayDimensions: types$1.string(),
|
|
21448
21543
|
hasFooter: types$1.boolean().optional(() => true)
|
|
21449
21544
|
});
|
|
21450
|
-
gridOverlayRef = signal(
|
|
21545
|
+
gridOverlayRef = signal.ref();
|
|
21451
21546
|
cellPopovers;
|
|
21452
21547
|
paintFormatStore;
|
|
21453
21548
|
hoveredIconStore;
|
|
@@ -21474,7 +21569,7 @@ var GridOverlay = class extends Component {
|
|
|
21474
21569
|
return el;
|
|
21475
21570
|
}
|
|
21476
21571
|
get style() {
|
|
21477
|
-
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "
|
|
21572
|
+
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
|
|
21478
21573
|
}
|
|
21479
21574
|
get isPaintingFormat() {
|
|
21480
21575
|
return this.paintFormatStore.isActive;
|
|
@@ -22114,8 +22209,9 @@ var FormulaFingerprintStore = class extends SpreadsheetStore {
|
|
|
22114
22209
|
const CELL_ANIMATION_DURATION = 200;
|
|
22115
22210
|
var GridRenderer = class extends DisposableStore {
|
|
22116
22211
|
renderer;
|
|
22212
|
+
storeGetters = ["drawLayer"];
|
|
22117
22213
|
fingerprints;
|
|
22118
|
-
|
|
22214
|
+
cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
22119
22215
|
hoveredIcon;
|
|
22120
22216
|
lastRenderSheetId = void 0;
|
|
22121
22217
|
lastRenderBoxes = /* @__PURE__ */ new Map();
|
|
@@ -22129,15 +22225,14 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22129
22225
|
const model = get(ModelStore);
|
|
22130
22226
|
this.getters = model.getters;
|
|
22131
22227
|
this.fingerprints = get(FormulaFingerprintStore);
|
|
22132
|
-
this.hoveredTables = get(HoveredTableStore);
|
|
22133
22228
|
this.hoveredIcon = get(HoveredIconStore);
|
|
22134
22229
|
model.on("command-dispatched", this, this.handle);
|
|
22135
22230
|
model.on("command-finalized", this, this.finalize);
|
|
22136
|
-
this.renderer.register(this);
|
|
22231
|
+
if (this.renderer) this.renderer.register(this);
|
|
22137
22232
|
this.onDispose(() => {
|
|
22138
22233
|
model.off("command-dispatched", this);
|
|
22139
22234
|
model.off("command-finalized", this);
|
|
22140
|
-
this.renderer.unRegister(this);
|
|
22235
|
+
if (this.renderer) this.renderer.unRegister(this);
|
|
22141
22236
|
});
|
|
22142
22237
|
}
|
|
22143
22238
|
handle(cmd) {
|
|
@@ -22681,7 +22776,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22681
22776
|
border: this.getters.getCellComputedBorder(position) || void 0,
|
|
22682
22777
|
style,
|
|
22683
22778
|
dataBarFill,
|
|
22684
|
-
overlayColor: this.
|
|
22779
|
+
overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
|
|
22685
22780
|
isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
|
|
22686
22781
|
icons: cellIcons,
|
|
22687
22782
|
disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
|
|
@@ -22857,6 +22952,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22857
22952
|
return boxes;
|
|
22858
22953
|
}
|
|
22859
22954
|
getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
|
|
22955
|
+
if (!this.renderer) return boxes;
|
|
22860
22956
|
this.updateAnimationsProgress(timeStamp);
|
|
22861
22957
|
this.addNewAnimations(boxes, oldBoxes, timeStamp);
|
|
22862
22958
|
if (this.animations.size > 0) {
|
|
@@ -23001,7 +23097,7 @@ var ScrollBar = class extends Component {
|
|
|
23001
23097
|
offset: types$1.Pixel(),
|
|
23002
23098
|
onScroll: types$1.function()
|
|
23003
23099
|
});
|
|
23004
|
-
scrollbarRef = signal(
|
|
23100
|
+
scrollbarRef = signal.ref();
|
|
23005
23101
|
scrollbar;
|
|
23006
23102
|
setup() {
|
|
23007
23103
|
this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
|
|
@@ -23103,6 +23199,9 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23103
23199
|
zoneToDisplay: this.range.zone,
|
|
23104
23200
|
getFooterSize: () => 0
|
|
23105
23201
|
});
|
|
23202
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
23203
|
+
cellHoverOverlayStore.register(this);
|
|
23204
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
23106
23205
|
}
|
|
23107
23206
|
handle(cmd) {
|
|
23108
23207
|
this.cachedColDimensions = void 0;
|
|
@@ -23251,6 +23350,16 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23251
23350
|
}
|
|
23252
23351
|
return normalizedWeights;
|
|
23253
23352
|
}
|
|
23353
|
+
getHighlightedPositions(position) {
|
|
23354
|
+
const highlightedPositions = [];
|
|
23355
|
+
const { col, row, sheetId } = position;
|
|
23356
|
+
if (sheetId !== this.range.sheetId || !isInside(col, row, this.range.zone)) return highlightedPositions;
|
|
23357
|
+
for (let col = this.range.zone.left; col <= this.range.zone.right; col++) highlightedPositions.push({
|
|
23358
|
+
...position,
|
|
23359
|
+
col
|
|
23360
|
+
});
|
|
23361
|
+
return highlightedPositions;
|
|
23362
|
+
}
|
|
23254
23363
|
};
|
|
23255
23364
|
|
|
23256
23365
|
//#endregion
|
|
@@ -23267,10 +23376,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23267
23376
|
range: types$1.Range(),
|
|
23268
23377
|
canResizeColumns: types$1.boolean().optional(true),
|
|
23269
23378
|
onResizeColumns: types$1.function().optional(),
|
|
23270
|
-
columnWeights: types$1.array().optional()
|
|
23379
|
+
columnWeights: types$1.array().optional(),
|
|
23380
|
+
size: types$1.object()
|
|
23271
23381
|
});
|
|
23272
|
-
canvasRef = signal(
|
|
23273
|
-
containerRef = signal(
|
|
23382
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
23383
|
+
containerRef = signal.ref();
|
|
23274
23384
|
store;
|
|
23275
23385
|
dndState = proxy({ col: void 0 });
|
|
23276
23386
|
onMouseWheel;
|
|
@@ -23282,8 +23392,8 @@ var StandaloneViewport = class extends Component {
|
|
|
23282
23392
|
useChildStoreProvider([
|
|
23283
23393
|
ViewportsStore,
|
|
23284
23394
|
HoveredIconStore,
|
|
23285
|
-
HoveredTableStore,
|
|
23286
23395
|
ClickableCellsStore,
|
|
23396
|
+
CellHoverOverlayStore,
|
|
23287
23397
|
DelayedHoveredCellStore,
|
|
23288
23398
|
CellPopoverStore
|
|
23289
23399
|
]);
|
|
@@ -23292,18 +23402,10 @@ var StandaloneViewport = class extends Component {
|
|
|
23292
23402
|
this.zoomStore = useStore(ZoomStore);
|
|
23293
23403
|
this.cellPopoverStore = useStore(CellPopoverStore);
|
|
23294
23404
|
this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
|
|
23295
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
23296
|
-
this.store.setContainerSize(this.containerWidth, this.containerHeight);
|
|
23297
|
-
});
|
|
23298
|
-
useEffect(() => {
|
|
23299
|
-
const el = this.containerRef();
|
|
23300
|
-
if (el) resizeObserver.observe(el);
|
|
23301
|
-
return () => resizeObserver.disconnect();
|
|
23302
|
-
});
|
|
23303
23405
|
useEffect(() => {
|
|
23304
23406
|
this.store.setRange(this.props.range);
|
|
23305
23407
|
if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
|
|
23306
|
-
this.store.setContainerSize(this.
|
|
23408
|
+
this.store.setContainerSize(this.contentWidth, this.props.size.height);
|
|
23307
23409
|
});
|
|
23308
23410
|
useGridDrawing({
|
|
23309
23411
|
canvasRef: this.canvasRef,
|
|
@@ -23322,14 +23424,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23322
23424
|
}
|
|
23323
23425
|
});
|
|
23324
23426
|
}
|
|
23325
|
-
get
|
|
23326
|
-
return
|
|
23327
|
-
}
|
|
23328
|
-
get containerHeight() {
|
|
23329
|
-
return Math.floor(getElBoundingRect(this.containerRef()).height / this.zoomStore.zoomLevel);
|
|
23427
|
+
get contentWidth() {
|
|
23428
|
+
return this.hasVerticalScrollBar ? this.props.size.width - this.zoomStore.scrollBarWidth : this.props.size.width;
|
|
23330
23429
|
}
|
|
23331
23430
|
get hasVerticalScrollBar() {
|
|
23332
|
-
return this.
|
|
23431
|
+
return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
|
|
23333
23432
|
}
|
|
23334
23433
|
get scrollBarContainerStyle() {
|
|
23335
23434
|
return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
|
|
@@ -23381,7 +23480,7 @@ var StandaloneViewport = class extends Component {
|
|
|
23381
23480
|
};
|
|
23382
23481
|
const onMouseMove = (ev) => {
|
|
23383
23482
|
deltaX = withZoom(this.env, ev).clientX - initialX;
|
|
23384
|
-
const weightDelta = deltaX / this.
|
|
23483
|
+
const weightDelta = deltaX / this.props.size.width * totalWeight;
|
|
23385
23484
|
this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
|
|
23386
23485
|
};
|
|
23387
23486
|
startDnd(onMouseMove, onMouseUp);
|
|
@@ -23411,8 +23510,8 @@ var Select = class extends Component {
|
|
|
23411
23510
|
name: types$1.string().optional(),
|
|
23412
23511
|
title: types$1.string().optional()
|
|
23413
23512
|
});
|
|
23414
|
-
selectRef = signal(
|
|
23415
|
-
dropdownRef = signal(
|
|
23513
|
+
selectRef = signal.ref();
|
|
23514
|
+
dropdownRef = signal.ref();
|
|
23416
23515
|
state = proxy({
|
|
23417
23516
|
isPopoverOpen: false,
|
|
23418
23517
|
hoveredValue: void 0
|
|
@@ -23522,7 +23621,7 @@ var InfoPopover = class extends Component {
|
|
|
23522
23621
|
setup() {
|
|
23523
23622
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
23524
23623
|
}
|
|
23525
|
-
infoRef = signal(
|
|
23624
|
+
infoRef = signal.ref();
|
|
23526
23625
|
get popoverProps() {
|
|
23527
23626
|
return {
|
|
23528
23627
|
anchorRect: this.props.anchorRect,
|
|
@@ -23667,8 +23766,8 @@ var CarouselFigure = class extends Component {
|
|
|
23667
23766
|
isFullScreen: types$1.boolean().optional(),
|
|
23668
23767
|
openContextMenu: types$1.function().optional()
|
|
23669
23768
|
});
|
|
23670
|
-
carouselTabsRef = signal(
|
|
23671
|
-
carouselTabsDropdownRef = signal(
|
|
23769
|
+
carouselTabsRef = signal.ref();
|
|
23770
|
+
carouselTabsDropdownRef = signal.ref();
|
|
23672
23771
|
menuState = proxy({
|
|
23673
23772
|
isOpen: false,
|
|
23674
23773
|
anchorRect: null,
|
|
@@ -23736,6 +23835,19 @@ var CarouselFigure = class extends Component {
|
|
|
23736
23835
|
} else cssProperties["background-color"] = backgroundColor;
|
|
23737
23836
|
return cssPropertiesToCss(cssProperties);
|
|
23738
23837
|
}
|
|
23838
|
+
get contentStyle() {
|
|
23839
|
+
return cssPropertiesToCss(this.rectToCss(this.carouselLayout.contentRect));
|
|
23840
|
+
}
|
|
23841
|
+
get headerStyle() {
|
|
23842
|
+
return cssPropertiesToCss({
|
|
23843
|
+
...this.rectToCss(this.carouselLayout.headerRect),
|
|
23844
|
+
"line-height": String(this.carouselLayout.headerLineHeight)
|
|
23845
|
+
});
|
|
23846
|
+
}
|
|
23847
|
+
get separatorStyle() {
|
|
23848
|
+
const separatorRect = this.carouselLayout.separatorRect;
|
|
23849
|
+
return separatorRect ? cssPropertiesToCss(this.rectToCss(separatorRect)) : "";
|
|
23850
|
+
}
|
|
23739
23851
|
get title() {
|
|
23740
23852
|
return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
|
|
23741
23853
|
}
|
|
@@ -23745,6 +23857,14 @@ var CarouselFigure = class extends Component {
|
|
|
23745
23857
|
...this.carousel.title
|
|
23746
23858
|
})));
|
|
23747
23859
|
}
|
|
23860
|
+
rectToCss(rect) {
|
|
23861
|
+
return {
|
|
23862
|
+
top: `${rect.y}px`,
|
|
23863
|
+
left: `${rect.x}px`,
|
|
23864
|
+
width: `${rect.width}px`,
|
|
23865
|
+
height: `${rect.height}px`
|
|
23866
|
+
};
|
|
23867
|
+
}
|
|
23748
23868
|
updateTabsVisibility() {
|
|
23749
23869
|
const tabsContainerEl = this.carouselTabsRef();
|
|
23750
23870
|
const dropDownEl = this.carouselTabsDropdownRef();
|
|
@@ -23836,6 +23956,13 @@ var CarouselFigure = class extends Component {
|
|
|
23836
23956
|
get canResizeDataViewColumns() {
|
|
23837
23957
|
return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
23838
23958
|
}
|
|
23959
|
+
get carouselLayout() {
|
|
23960
|
+
return getCarouselLayout({
|
|
23961
|
+
...this.props.figureUI,
|
|
23962
|
+
x: 0,
|
|
23963
|
+
y: 0
|
|
23964
|
+
}, this.carousel, this.selectedCarouselItem);
|
|
23965
|
+
}
|
|
23839
23966
|
};
|
|
23840
23967
|
|
|
23841
23968
|
//#endregion
|
|
@@ -23876,7 +24003,7 @@ var ImageFigure = class extends Component {
|
|
|
23876
24003
|
figureUI: types$1.FigureUI(),
|
|
23877
24004
|
openContextMenu: types$1.function()
|
|
23878
24005
|
});
|
|
23879
|
-
menuButtonRef = signal(
|
|
24006
|
+
menuButtonRef = signal.ref();
|
|
23880
24007
|
showMenu(ev) {
|
|
23881
24008
|
if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
|
|
23882
24009
|
figureId: this.props.figureUI.id,
|
|
@@ -23904,7 +24031,8 @@ figureRegistry.add("chart", {
|
|
|
23904
24031
|
menuBuilder: getChartMenuActions,
|
|
23905
24032
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23906
24033
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23907
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24034
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24035
|
+
isThemeDependant: true
|
|
23908
24036
|
});
|
|
23909
24037
|
figureRegistry.add("image", {
|
|
23910
24038
|
Component: ImageFigure,
|
|
@@ -23913,14 +24041,16 @@ figureRegistry.add("image", {
|
|
|
23913
24041
|
borderWidth: () => 0,
|
|
23914
24042
|
hasShadow: () => false,
|
|
23915
24043
|
isRounded: () => false,
|
|
23916
|
-
menuBuilder: getImageMenuActions
|
|
24044
|
+
menuBuilder: getImageMenuActions,
|
|
24045
|
+
isThemeDependant: false
|
|
23917
24046
|
});
|
|
23918
24047
|
figureRegistry.add("carousel", {
|
|
23919
24048
|
Component: CarouselFigure,
|
|
23920
24049
|
menuBuilder: getCarouselMenuActions,
|
|
23921
24050
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23922
24051
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23923
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24052
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24053
|
+
isThemeDependant: true
|
|
23924
24054
|
});
|
|
23925
24055
|
|
|
23926
24056
|
//#endregion
|
|
@@ -23942,10 +24072,13 @@ var FigureComponent = class extends Component {
|
|
|
23942
24072
|
anchorRect: null,
|
|
23943
24073
|
menuItems: []
|
|
23944
24074
|
});
|
|
23945
|
-
figureRef = signal(
|
|
24075
|
+
figureRef = signal.ref();
|
|
23946
24076
|
get isSelected() {
|
|
23947
24077
|
return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
|
|
23948
24078
|
}
|
|
24079
|
+
get isThemeDependant() {
|
|
24080
|
+
return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
|
|
24081
|
+
}
|
|
23949
24082
|
get figureRegistry() {
|
|
23950
24083
|
return figureRegistry;
|
|
23951
24084
|
}
|
|
@@ -29544,6 +29677,35 @@ async function interactivePasteFromOS(env, target, parsedClipboardContent, paste
|
|
|
29544
29677
|
}));
|
|
29545
29678
|
}
|
|
29546
29679
|
|
|
29680
|
+
//#endregion
|
|
29681
|
+
//#region src/helpers/figures/charts/chart_data_source_helpers.ts
|
|
29682
|
+
function getUnboundRange(getters, zone) {
|
|
29683
|
+
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29684
|
+
}
|
|
29685
|
+
function isDatasetTitled(getters, zone) {
|
|
29686
|
+
const sheetId = getters.getActiveSheetId();
|
|
29687
|
+
const cell = getters.getEvaluatedCell({
|
|
29688
|
+
sheetId,
|
|
29689
|
+
col: zone.left,
|
|
29690
|
+
row: zone.top
|
|
29691
|
+
});
|
|
29692
|
+
return !["number", "empty"].includes(cell.type);
|
|
29693
|
+
}
|
|
29694
|
+
function dataset(zone, getters, id = "0") {
|
|
29695
|
+
return {
|
|
29696
|
+
dataRange: getUnboundRange(getters, zone),
|
|
29697
|
+
dataSetId: id
|
|
29698
|
+
};
|
|
29699
|
+
}
|
|
29700
|
+
function rangeSource(dataSets, dataSetsHaveTitle, labelRange) {
|
|
29701
|
+
return {
|
|
29702
|
+
type: "range",
|
|
29703
|
+
dataSets,
|
|
29704
|
+
dataSetsHaveTitle,
|
|
29705
|
+
labelRange
|
|
29706
|
+
};
|
|
29707
|
+
}
|
|
29708
|
+
|
|
29547
29709
|
//#endregion
|
|
29548
29710
|
//#region src/helpers/figures/charts/smart_chart_engine.ts
|
|
29549
29711
|
const DEFAULT_BAR_CHART_CONFIG = {
|
|
@@ -29574,9 +29736,6 @@ const DEFAULT_LINE_CHART_CONFIG = {
|
|
|
29574
29736
|
labelsAsText: false,
|
|
29575
29737
|
humanize: true
|
|
29576
29738
|
};
|
|
29577
|
-
function getUnboundRange(getters, zone) {
|
|
29578
|
-
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29579
|
-
}
|
|
29580
29739
|
function detectColumnType(cells) {
|
|
29581
29740
|
if (!cells.length) return "empty";
|
|
29582
29741
|
const counts = {
|
|
@@ -29623,14 +29782,6 @@ function getCellStats(getters, zone) {
|
|
|
29623
29782
|
totalCount: values.length
|
|
29624
29783
|
};
|
|
29625
29784
|
}
|
|
29626
|
-
function isDatasetTitled(getters, column) {
|
|
29627
|
-
const titleCell = getters.getEvaluatedCell({
|
|
29628
|
-
sheetId: getters.getActiveSheetId(),
|
|
29629
|
-
col: column.zone.left,
|
|
29630
|
-
row: column.zone.top
|
|
29631
|
-
});
|
|
29632
|
-
return !["number", "empty"].includes(titleCell.type);
|
|
29633
|
-
}
|
|
29634
29785
|
/**
|
|
29635
29786
|
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
29636
29787
|
* - If the column contains a single cell, create a scorecard.
|
|
@@ -29642,8 +29793,7 @@ function isDatasetTitled(getters, column) {
|
|
|
29642
29793
|
function buildSingleColumnChart(column, getters) {
|
|
29643
29794
|
const { type, zone } = column;
|
|
29644
29795
|
const sheetId = getters.getActiveSheetId();
|
|
29645
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
29646
|
-
const dataRange = getUnboundRange(getters, zone);
|
|
29796
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
|
|
29647
29797
|
const titleCell = getters.getEvaluatedCell({
|
|
29648
29798
|
sheetId,
|
|
29649
29799
|
col: zone.left,
|
|
@@ -29654,14 +29804,7 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29654
29804
|
case "percentage": return {
|
|
29655
29805
|
type: "pie",
|
|
29656
29806
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29657
|
-
dataSource:
|
|
29658
|
-
type: "range",
|
|
29659
|
-
dataSets: [{
|
|
29660
|
-
dataRange,
|
|
29661
|
-
dataSetId: "0"
|
|
29662
|
-
}],
|
|
29663
|
-
dataSetsHaveTitle
|
|
29664
|
-
},
|
|
29807
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29665
29808
|
dataSetStyles: {},
|
|
29666
29809
|
legendPosition: "none"
|
|
29667
29810
|
};
|
|
@@ -29672,45 +29815,28 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29672
29815
|
return {
|
|
29673
29816
|
type: "pie",
|
|
29674
29817
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
29675
|
-
dataSource:
|
|
29676
|
-
type: "range",
|
|
29677
|
-
dataSets: [{
|
|
29678
|
-
dataRange,
|
|
29679
|
-
dataSetId: "0"
|
|
29680
|
-
}],
|
|
29681
|
-
labelRange: dataRange,
|
|
29682
|
-
dataSetsHaveTitle: hasUniqueTitle
|
|
29683
|
-
},
|
|
29818
|
+
dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
|
|
29684
29819
|
dataSetStyles: {},
|
|
29685
29820
|
aggregated: true,
|
|
29686
29821
|
legendPosition: "top"
|
|
29687
29822
|
};
|
|
29688
|
-
case "date":
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
29697
|
-
|
|
29698
|
-
|
|
29699
|
-
|
|
29700
|
-
verticalGroupBy: "month_number"
|
|
29701
|
-
};
|
|
29823
|
+
case "date": {
|
|
29824
|
+
const dataRange = getUnboundRange(getters, zone);
|
|
29825
|
+
return {
|
|
29826
|
+
type: "calendar",
|
|
29827
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29828
|
+
dataSource: rangeSource([], dataSetsHaveTitle, dataRange),
|
|
29829
|
+
dataSetStyles: {},
|
|
29830
|
+
legendPosition: "left",
|
|
29831
|
+
horizontalGroupBy: "day_of_week",
|
|
29832
|
+
verticalGroupBy: "month_number"
|
|
29833
|
+
};
|
|
29834
|
+
}
|
|
29702
29835
|
}
|
|
29703
29836
|
return {
|
|
29704
29837
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29705
29838
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29706
|
-
dataSource:
|
|
29707
|
-
type: "range",
|
|
29708
|
-
dataSets: [{
|
|
29709
|
-
dataRange,
|
|
29710
|
-
dataSetId: "0"
|
|
29711
|
-
}],
|
|
29712
|
-
dataSetsHaveTitle
|
|
29713
|
-
},
|
|
29839
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29714
29840
|
dataSetStyles: {}
|
|
29715
29841
|
};
|
|
29716
29842
|
}
|
|
@@ -29728,15 +29854,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29728
29854
|
if (columns[1].type === "percentage") return {
|
|
29729
29855
|
type: "pie",
|
|
29730
29856
|
title: {},
|
|
29731
|
-
dataSource:
|
|
29732
|
-
type: "range",
|
|
29733
|
-
dataSets: [{
|
|
29734
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29735
|
-
dataSetId: "0"
|
|
29736
|
-
}],
|
|
29737
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29738
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29739
|
-
},
|
|
29857
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29740
29858
|
dataSetStyles: {},
|
|
29741
29859
|
aggregated: true,
|
|
29742
29860
|
legendPosition: "none"
|
|
@@ -29744,15 +29862,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29744
29862
|
if (columns[0].type === "number" && columns[1].type === "number") return {
|
|
29745
29863
|
type: "scatter",
|
|
29746
29864
|
title: {},
|
|
29747
|
-
dataSource:
|
|
29748
|
-
type: "range",
|
|
29749
|
-
dataSets: [{
|
|
29750
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29751
|
-
dataSetId: "0"
|
|
29752
|
-
}],
|
|
29753
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29754
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29755
|
-
},
|
|
29865
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29756
29866
|
dataSetStyles: {},
|
|
29757
29867
|
labelsAsText: false,
|
|
29758
29868
|
legendPosition: "none"
|
|
@@ -29760,49 +29870,25 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29760
29870
|
if (columns[0].type === "date" && columns[1].type === "number") return {
|
|
29761
29871
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29762
29872
|
type: "line",
|
|
29763
|
-
dataSource:
|
|
29764
|
-
type: "range",
|
|
29765
|
-
dataSets: [{
|
|
29766
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29767
|
-
dataSetId: "0"
|
|
29768
|
-
}],
|
|
29769
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29770
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
|
|
29771
|
-
},
|
|
29873
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29772
29874
|
dataSetStyles: {}
|
|
29773
29875
|
};
|
|
29774
29876
|
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
29775
29877
|
const textColumn = columns[0];
|
|
29776
29878
|
const numberColumn = columns[1];
|
|
29777
29879
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
29778
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
29880
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
|
|
29779
29881
|
if (uniqueCount !== totalCount) return {
|
|
29780
29882
|
type: "treemap",
|
|
29781
29883
|
title: {},
|
|
29782
|
-
dataSource:
|
|
29783
|
-
type: "range",
|
|
29784
|
-
dataSets: [{
|
|
29785
|
-
dataRange: getUnboundRange(getters, textColumn.zone),
|
|
29786
|
-
dataSetId: "0"
|
|
29787
|
-
}],
|
|
29788
|
-
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
29789
|
-
dataSetsHaveTitle
|
|
29790
|
-
},
|
|
29884
|
+
dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
|
|
29791
29885
|
dataSetStyles: {},
|
|
29792
29886
|
legendPosition: "none"
|
|
29793
29887
|
};
|
|
29794
29888
|
}
|
|
29795
29889
|
return {
|
|
29796
29890
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29797
|
-
dataSource:
|
|
29798
|
-
type: "range",
|
|
29799
|
-
dataSets: [{
|
|
29800
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29801
|
-
dataSetId: "0"
|
|
29802
|
-
}],
|
|
29803
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29804
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29805
|
-
},
|
|
29891
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29806
29892
|
dataSetStyles: {}
|
|
29807
29893
|
};
|
|
29808
29894
|
}
|
|
@@ -29816,42 +29902,26 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29816
29902
|
*/
|
|
29817
29903
|
function buildMultiColumnChart(columns, getters) {
|
|
29818
29904
|
if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
|
|
29819
|
-
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
29905
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
|
|
29820
29906
|
const lastColumn = columns[columns.length - 1];
|
|
29821
29907
|
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
29822
29908
|
if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
|
|
29823
|
-
const dataSets = columnsExceptLast.map(({ zone }, i) => (
|
|
29824
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29825
|
-
dataSetId: i.toString()
|
|
29826
|
-
}));
|
|
29909
|
+
const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29827
29910
|
return {
|
|
29828
29911
|
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
29829
29912
|
title: {},
|
|
29830
|
-
dataSource:
|
|
29831
|
-
type: "range",
|
|
29832
|
-
dataSets,
|
|
29833
|
-
dataSetsHaveTitle,
|
|
29834
|
-
labelRange: getUnboundRange(getters, lastColumn.zone)
|
|
29835
|
-
},
|
|
29913
|
+
dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
|
|
29836
29914
|
dataSetStyles: {},
|
|
29837
29915
|
legendPosition: "none"
|
|
29838
29916
|
};
|
|
29839
29917
|
}
|
|
29840
29918
|
const firstColumn = columns[0];
|
|
29841
29919
|
const columnsExceptFirst = columns.slice(1);
|
|
29842
|
-
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => (
|
|
29843
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29844
|
-
dataSetId: i.toString()
|
|
29845
|
-
}));
|
|
29920
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29846
29921
|
if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
|
|
29847
29922
|
type: "pie",
|
|
29848
29923
|
title: {},
|
|
29849
|
-
dataSource:
|
|
29850
|
-
type: "range",
|
|
29851
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29852
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29853
|
-
dataSetsHaveTitle
|
|
29854
|
-
},
|
|
29924
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29855
29925
|
dataSetStyles: {},
|
|
29856
29926
|
aggregated: false,
|
|
29857
29927
|
legendPosition: "top"
|
|
@@ -29859,23 +29929,13 @@ function buildMultiColumnChart(columns, getters) {
|
|
|
29859
29929
|
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
|
|
29860
29930
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29861
29931
|
type: "line",
|
|
29862
|
-
dataSource:
|
|
29863
|
-
type: "range",
|
|
29864
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29865
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29866
|
-
dataSetsHaveTitle
|
|
29867
|
-
},
|
|
29932
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29868
29933
|
dataSetStyles: {},
|
|
29869
29934
|
legendPosition: "top"
|
|
29870
29935
|
};
|
|
29871
29936
|
return {
|
|
29872
29937
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29873
|
-
dataSource:
|
|
29874
|
-
type: "range",
|
|
29875
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29876
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29877
|
-
dataSetsHaveTitle
|
|
29878
|
-
},
|
|
29938
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29879
29939
|
dataSetStyles: {},
|
|
29880
29940
|
legendPosition: "top"
|
|
29881
29941
|
};
|
|
@@ -29902,17 +29962,10 @@ function buildScorecard(zone, getters) {
|
|
|
29902
29962
|
function getSmartChartDefinition(zones, getters) {
|
|
29903
29963
|
const columns = categorizeColumns(zones, getters);
|
|
29904
29964
|
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
29905
|
-
const dataSets = columns.map(({ zone }, i) => (
|
|
29906
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29907
|
-
dataSetId: i.toString()
|
|
29908
|
-
}));
|
|
29965
|
+
const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29909
29966
|
return {
|
|
29910
29967
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29911
|
-
dataSource:
|
|
29912
|
-
type: "range",
|
|
29913
|
-
dataSets,
|
|
29914
|
-
dataSetsHaveTitle: false
|
|
29915
|
-
}
|
|
29968
|
+
dataSource: rangeSource(dataSets, false)
|
|
29916
29969
|
};
|
|
29917
29970
|
}
|
|
29918
29971
|
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
@@ -31600,7 +31653,7 @@ var SelectionInput = class extends Component {
|
|
|
31600
31653
|
dragAndDrop = useDragAndDropListItems();
|
|
31601
31654
|
focusedInputRef = signal.ref(HTMLInputElement);
|
|
31602
31655
|
unfocusedInputRef = signal.ref(HTMLInputElement);
|
|
31603
|
-
selectionRef = signal(
|
|
31656
|
+
selectionRef = signal.ref();
|
|
31604
31657
|
DOMFocusableElementStore;
|
|
31605
31658
|
store;
|
|
31606
31659
|
get ranges() {
|
|
@@ -32107,7 +32160,7 @@ var ColorPickerWidget = class extends Component {
|
|
|
32107
32160
|
dropdownMaxHeight: types$1.Pixel().optional(),
|
|
32108
32161
|
class: types$1.string().optional()
|
|
32109
32162
|
});
|
|
32110
|
-
colorPickerButtonRef = signal(
|
|
32163
|
+
colorPickerButtonRef = signal.ref();
|
|
32111
32164
|
get iconStyle() {
|
|
32112
32165
|
return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
|
|
32113
32166
|
}
|
|
@@ -32135,8 +32188,8 @@ var NumberEditor = class extends Component {
|
|
|
32135
32188
|
});
|
|
32136
32189
|
dropdown = proxy({ isOpen: false });
|
|
32137
32190
|
inputRef = signal.ref(HTMLInputElement);
|
|
32138
|
-
rootEditorRef = signal(
|
|
32139
|
-
valueListRef = signal(
|
|
32191
|
+
rootEditorRef = signal.ref();
|
|
32192
|
+
valueListRef = signal.ref();
|
|
32140
32193
|
DOMFocusableElementStore;
|
|
32141
32194
|
setup() {
|
|
32142
32195
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -32442,7 +32495,7 @@ var ChartTypePickerPopover = class extends Component {
|
|
|
32442
32495
|
});
|
|
32443
32496
|
categories = chartCategories;
|
|
32444
32497
|
chartTypeByCategories = {};
|
|
32445
|
-
popoverRef = signal(
|
|
32498
|
+
popoverRef = signal.ref();
|
|
32446
32499
|
setup() {
|
|
32447
32500
|
useListener(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
|
|
32448
32501
|
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
@@ -32467,7 +32520,7 @@ var CogWheelMenu = class extends Component {
|
|
|
32467
32520
|
static template = "o-spreadsheet-CogWheelMenu";
|
|
32468
32521
|
static components = { MenuPopover };
|
|
32469
32522
|
props = useProps({ items: types$1.array(types$1.ActionSpec()) });
|
|
32470
|
-
buttonRef = signal(
|
|
32523
|
+
buttonRef = signal.ref();
|
|
32471
32524
|
menuState = proxy({
|
|
32472
32525
|
isOpen: false,
|
|
32473
32526
|
anchorRect: null,
|
|
@@ -32511,8 +32564,8 @@ var CarouselPanel = class extends Component {
|
|
|
32511
32564
|
});
|
|
32512
32565
|
DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
|
|
32513
32566
|
dragAndDrop = useDragAndDropListItems();
|
|
32514
|
-
previewListRef = signal(
|
|
32515
|
-
addChartButton = signal(
|
|
32567
|
+
previewListRef = signal.ref();
|
|
32568
|
+
addChartButton = signal.ref(HTMLButtonElement);
|
|
32516
32569
|
state = proxy({
|
|
32517
32570
|
popoverProps: void 0,
|
|
32518
32571
|
currentRange: void 0
|
|
@@ -32956,7 +33009,7 @@ var BarConfigPanel = class extends GenericChartConfigPanel {
|
|
|
32956
33009
|
var Collapse = class extends Component {
|
|
32957
33010
|
static template = "o-spreadsheet-Collapse";
|
|
32958
33011
|
props = useProps({ isCollapsed: types$1.boolean() });
|
|
32959
|
-
contentRef = signal(
|
|
33012
|
+
contentRef = signal.ref();
|
|
32960
33013
|
setup() {
|
|
32961
33014
|
onMounted(() => {
|
|
32962
33015
|
if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
|
|
@@ -33009,7 +33062,7 @@ var ChartAnnotation = class extends Component {
|
|
|
33009
33062
|
TextInput
|
|
33010
33063
|
};
|
|
33011
33064
|
props = useProps(chartSidePanelPropsDefinition);
|
|
33012
|
-
editorRef = signal(
|
|
33065
|
+
editorRef = signal.ref();
|
|
33013
33066
|
state = proxy({
|
|
33014
33067
|
annotationTextInput: this.props.definition.annotationText || "",
|
|
33015
33068
|
isOverflowing: false
|
|
@@ -33331,7 +33384,7 @@ var RoundColorPicker = class extends Component {
|
|
|
33331
33384
|
onColorPicked: types$1.function(),
|
|
33332
33385
|
disableNoColor: types$1.boolean().optional()
|
|
33333
33386
|
});
|
|
33334
|
-
colorPickerButtonRef = signal(
|
|
33387
|
+
colorPickerButtonRef = signal.ref();
|
|
33335
33388
|
state;
|
|
33336
33389
|
setup() {
|
|
33337
33390
|
this.state = proxy({ pickerOpened: false });
|
|
@@ -36173,6 +36226,7 @@ var helpers_index_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
36173
36226
|
getDefinedAxis: () => getDefinedAxis,
|
|
36174
36227
|
getPieColors: () => getPieColors,
|
|
36175
36228
|
isTrendLineAxis: () => isTrendLineAxis,
|
|
36229
|
+
limitChartConfigDataPoints: () => limitChartConfigDataPoints,
|
|
36176
36230
|
shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
|
|
36177
36231
|
toExcelDataset: () => toExcelDataset,
|
|
36178
36232
|
toExcelLabelRange: () => toExcelLabelRange,
|
|
@@ -36943,7 +36997,7 @@ var ColorScalePicker = class extends Component {
|
|
|
36943
36997
|
popoverProps: void 0,
|
|
36944
36998
|
popoverStyle: ""
|
|
36945
36999
|
});
|
|
36946
|
-
popoverRef = signal(
|
|
37000
|
+
popoverRef = signal.ref(HTMLTableElement);
|
|
36947
37001
|
setup() {
|
|
36948
37002
|
useExternalListener(window, "click", this.closePopover);
|
|
36949
37003
|
}
|
|
@@ -37192,7 +37246,7 @@ var TextValueProvider = class extends Component {
|
|
|
37192
37246
|
onValueSelected: types$1.function(),
|
|
37193
37247
|
onValueHovered: types$1.function()
|
|
37194
37248
|
});
|
|
37195
|
-
autoCompleteListRef = signal(
|
|
37249
|
+
autoCompleteListRef = signal.ref();
|
|
37196
37250
|
setup() {
|
|
37197
37251
|
useEffect(() => {
|
|
37198
37252
|
const selectedIndex = this.props.selectedIndex;
|
|
@@ -37483,7 +37537,7 @@ var SpeechBubble = class extends Component {
|
|
|
37483
37537
|
anchorRect: types$1.Rect()
|
|
37484
37538
|
});
|
|
37485
37539
|
spreadsheetRect = useSpreadsheetRect();
|
|
37486
|
-
bubbleRef = signal(
|
|
37540
|
+
bubbleRef = signal.ref();
|
|
37487
37541
|
setup() {
|
|
37488
37542
|
useEffect(() => {
|
|
37489
37543
|
const el = this.bubbleRef();
|
|
@@ -37528,8 +37582,8 @@ var Composer = class extends Component {
|
|
|
37528
37582
|
showAssistant: types$1.boolean().optional(true)
|
|
37529
37583
|
});
|
|
37530
37584
|
DOMFocusableElementStore;
|
|
37531
|
-
composerRef = signal(
|
|
37532
|
-
containerRef = signal(
|
|
37585
|
+
composerRef = signal.ref();
|
|
37586
|
+
containerRef = signal.ref();
|
|
37533
37587
|
contentHelper = new ContentEditableHelper(this.composerRef());
|
|
37534
37588
|
composerState = proxy({
|
|
37535
37589
|
positionStart: 0,
|
|
@@ -40031,7 +40085,7 @@ var ChartTypePicker = class extends Component {
|
|
|
40031
40085
|
chartId: types$1.UID(),
|
|
40032
40086
|
chartPanelStore: types$1.Store()
|
|
40033
40087
|
});
|
|
40034
|
-
selectRef = signal(
|
|
40088
|
+
selectRef = signal.ref();
|
|
40035
40089
|
state = proxy({ popoverProps: void 0 });
|
|
40036
40090
|
getSupportedChartTypes() {
|
|
40037
40091
|
let supportedTypes;
|
|
@@ -40599,7 +40653,7 @@ var ColumnStatsPanel = class extends Component {
|
|
|
40599
40653
|
highlightPositions: []
|
|
40600
40654
|
});
|
|
40601
40655
|
store;
|
|
40602
|
-
chartCanvasRef = signal(
|
|
40656
|
+
chartCanvasRef = signal.ref(HTMLCanvasElement);
|
|
40603
40657
|
chart;
|
|
40604
40658
|
setup() {
|
|
40605
40659
|
this.store = useStore(ColumnStatisticsStore);
|
|
@@ -42027,7 +42081,7 @@ var ConditionalFormatPreview = class extends Component {
|
|
|
42027
42081
|
class: types$1.string()
|
|
42028
42082
|
});
|
|
42029
42083
|
icons = ICONS;
|
|
42030
|
-
cfPreviewRef = signal(
|
|
42084
|
+
cfPreviewRef = signal.ref();
|
|
42031
42085
|
setup() {
|
|
42032
42086
|
useHighlightsOnHover(this.cfPreviewRef, this);
|
|
42033
42087
|
}
|
|
@@ -42084,7 +42138,7 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42084
42138
|
static components = { ConditionalFormatPreview };
|
|
42085
42139
|
props = useProps({ onCloseSidePanel: types$1.function() });
|
|
42086
42140
|
dragAndDrop = useDragAndDropListItems();
|
|
42087
|
-
cfListRef = signal(
|
|
42141
|
+
cfListRef = signal.ref();
|
|
42088
42142
|
get conditionalFormats() {
|
|
42089
42143
|
return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
|
|
42090
42144
|
}
|
|
@@ -42144,6 +42198,1477 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42144
42198
|
}
|
|
42145
42199
|
};
|
|
42146
42200
|
|
|
42201
|
+
//#endregion
|
|
42202
|
+
//#region src/stores/chart_drag_store.ts
|
|
42203
|
+
/**
|
|
42204
|
+
* Holds the id of the figure that a dragged chart suggestion is currently
|
|
42205
|
+
* hovering over, so it can be highlighted reactively instead of through direct DOM manipulation.
|
|
42206
|
+
*/
|
|
42207
|
+
var ChartDragStore = class {
|
|
42208
|
+
mutators = ["setHighlightedFigure"];
|
|
42209
|
+
highlightedFigureId = void 0;
|
|
42210
|
+
setHighlightedFigure(figureId) {
|
|
42211
|
+
this.highlightedFigureId = figureId;
|
|
42212
|
+
}
|
|
42213
|
+
};
|
|
42214
|
+
|
|
42215
|
+
//#endregion
|
|
42216
|
+
//#region src/components/helpers/chart_drag_and_drop.ts
|
|
42217
|
+
function getDefaultChartFigureSize(type) {
|
|
42218
|
+
if (type === "scorecard") return {
|
|
42219
|
+
width: 213,
|
|
42220
|
+
height: 101
|
|
42221
|
+
};
|
|
42222
|
+
return {
|
|
42223
|
+
width: 536,
|
|
42224
|
+
height: 335
|
|
42225
|
+
};
|
|
42226
|
+
}
|
|
42227
|
+
function getCarouselOverlappingChart(figureUI, otherFigures, matchTags) {
|
|
42228
|
+
if (figureUI.tag !== "chart") return;
|
|
42229
|
+
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
42230
|
+
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
42231
|
+
let bestMatch;
|
|
42232
|
+
let smallestDistance = Infinity;
|
|
42233
|
+
for (const figure of otherFigures) {
|
|
42234
|
+
if (!matchTags.includes(figure.tag)) continue;
|
|
42235
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
42236
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
42237
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
42238
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
42239
|
+
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
42240
|
+
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
42241
|
+
smallestDistance = squaredDistance;
|
|
42242
|
+
bestMatch = figure;
|
|
42243
|
+
}
|
|
42244
|
+
}
|
|
42245
|
+
return bestMatch;
|
|
42246
|
+
}
|
|
42247
|
+
/**
|
|
42248
|
+
* Start dragging a floating preview of the given chart definition, following the mouse.
|
|
42249
|
+
* On drop, creates the chart on the grid (or inside a carousel if dropped on one).
|
|
42250
|
+
*/
|
|
42251
|
+
function startChartDragAndDrop(env, definition, ev) {
|
|
42252
|
+
const zoom = env.getStore(ZoomStore).zoomLevel;
|
|
42253
|
+
const gridPosition = gridOverlayPosition(zoom);
|
|
42254
|
+
const spreadsheet = document.querySelector(".o-spreadsheet");
|
|
42255
|
+
if (!spreadsheet) return;
|
|
42256
|
+
const startX = ev.clientX / zoom;
|
|
42257
|
+
const startY = ev.clientY / zoom;
|
|
42258
|
+
const { width, height } = getDefaultChartFigureSize(definition.type);
|
|
42259
|
+
const figureWidth = width * zoom;
|
|
42260
|
+
const figureHeight = height * zoom;
|
|
42261
|
+
const getters = env.model.getters;
|
|
42262
|
+
const sheetId = getters.getActiveSheetId();
|
|
42263
|
+
let container = null;
|
|
42264
|
+
let destroyChart = void 0;
|
|
42265
|
+
const chartDragStore = env.getStore(ChartDragStore);
|
|
42266
|
+
const previousCursor = document.body.style.cursor;
|
|
42267
|
+
document.body.style.cursor = "grabbing";
|
|
42268
|
+
/** Grid coordinates (in sheet pixels) of a mouse position, or undefined if outside the grid. */
|
|
42269
|
+
const getGridPosition = (clientX, clientY) => {
|
|
42270
|
+
if (clientX > gridPosition.x + gridPosition.width || clientY > gridPosition.y + gridPosition.height) return;
|
|
42271
|
+
const { scrollX, scrollY } = env.getStore(ViewportsStore).activeSheetScrollInfo;
|
|
42272
|
+
return {
|
|
42273
|
+
x: Math.max(0, (clientX - gridPosition.x) / zoom + scrollX),
|
|
42274
|
+
y: Math.max(0, (clientY - gridPosition.y) / zoom + scrollY)
|
|
42275
|
+
};
|
|
42276
|
+
};
|
|
42277
|
+
/** Carousel or standalone chart the dragged chart is dropped onto, if any. */
|
|
42278
|
+
const getOverlappingFigure = (clientX, clientY) => {
|
|
42279
|
+
const position = getGridPosition(clientX, clientY);
|
|
42280
|
+
if (!position) return;
|
|
42281
|
+
const figureUI = {
|
|
42282
|
+
tag: "chart",
|
|
42283
|
+
...position,
|
|
42284
|
+
width,
|
|
42285
|
+
height
|
|
42286
|
+
};
|
|
42287
|
+
const otherFigures = env.getStore(ViewportsStore).visibleFigures;
|
|
42288
|
+
return getCarouselOverlappingChart(figureUI, otherFigures, ["carousel", "chart"]);
|
|
42289
|
+
};
|
|
42290
|
+
const halfWidth = figureWidth / 2;
|
|
42291
|
+
const halfHeight = figureHeight / 2;
|
|
42292
|
+
const onMouseMove = (e) => {
|
|
42293
|
+
if (Math.abs(e.clientX - startX) <= 5 && Math.abs(e.clientY - startY) <= 5) return;
|
|
42294
|
+
if (container === null) {
|
|
42295
|
+
container = document.createElement("div");
|
|
42296
|
+
container.className = "o-chart-drag-preview os-theme-dependant position-fixed border pe-none";
|
|
42297
|
+
container.style.width = `${width}px`;
|
|
42298
|
+
container.style.height = `${height}px`;
|
|
42299
|
+
container.style.zoom = `${zoom}`;
|
|
42300
|
+
const canvas = document.createElement("canvas");
|
|
42301
|
+
canvas.className = "w-100 h-100";
|
|
42302
|
+
container.appendChild(canvas);
|
|
42303
|
+
spreadsheet.appendChild(container);
|
|
42304
|
+
destroyChart = drawChartOnCanvas(canvas, SpreadsheetChart.fromStrDefinition(getters, sheetId, definition).getRuntime(getters, "newChart"), {
|
|
42305
|
+
width,
|
|
42306
|
+
height
|
|
42307
|
+
}, definition.type, zoom);
|
|
42308
|
+
}
|
|
42309
|
+
container.style.left = `${Math.max(gridPosition.x, (e.clientX - halfWidth) / zoom)}px`;
|
|
42310
|
+
container.style.top = `${Math.max(gridPosition.y, (e.clientY - halfHeight) / zoom)}px`;
|
|
42311
|
+
const overlappingFigure = getOverlappingFigure(e.clientX - halfWidth, e.clientY - halfHeight);
|
|
42312
|
+
container.style.opacity = overlappingFigure?.id ? "0.6" : "0.9";
|
|
42313
|
+
chartDragStore.setHighlightedFigure(overlappingFigure?.id);
|
|
42314
|
+
};
|
|
42315
|
+
const onMouseUp = (mouseEvent) => {
|
|
42316
|
+
chartDragStore.setHighlightedFigure(void 0);
|
|
42317
|
+
if (container !== null) {
|
|
42318
|
+
spreadsheet.removeChild(container);
|
|
42319
|
+
container = null;
|
|
42320
|
+
}
|
|
42321
|
+
destroyChart?.();
|
|
42322
|
+
document.body.style.cursor = previousCursor;
|
|
42323
|
+
let position = getGridPosition(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42324
|
+
if (Math.abs(mouseEvent.clientX / zoom - startX) <= 5 && Math.abs(mouseEvent.clientY / zoom - startY) <= 5) position = {
|
|
42325
|
+
x: 0,
|
|
42326
|
+
y: 0
|
|
42327
|
+
};
|
|
42328
|
+
else if (!position || position.x + halfWidth > gridPosition.width) return;
|
|
42329
|
+
const { col, row, offset } = env.getStore(ViewportsStore).viewports.getPositionAnchorOffset(sheetId, position);
|
|
42330
|
+
const payload = {
|
|
42331
|
+
chartId: UuidGenerator.smallUuid(),
|
|
42332
|
+
figureId: UuidGenerator.smallUuid(),
|
|
42333
|
+
sheetId,
|
|
42334
|
+
size: {
|
|
42335
|
+
width,
|
|
42336
|
+
height
|
|
42337
|
+
},
|
|
42338
|
+
definition,
|
|
42339
|
+
col,
|
|
42340
|
+
row,
|
|
42341
|
+
offset
|
|
42342
|
+
};
|
|
42343
|
+
const overlappingFigure = getOverlappingFigure(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42344
|
+
if (overlappingFigure?.tag === "carousel") env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
|
|
42345
|
+
sheetId,
|
|
42346
|
+
figureId: overlappingFigure.id,
|
|
42347
|
+
newChartId: UuidGenerator.smallUuid(),
|
|
42348
|
+
chartDefinition: definition
|
|
42349
|
+
});
|
|
42350
|
+
else if (overlappingFigure?.tag === "chart") env.model.dispatch("CREATE_CHART_AND_MERGE_INTO_CAROUSEL", {
|
|
42351
|
+
chartId: payload.chartId,
|
|
42352
|
+
figureId: payload.figureId,
|
|
42353
|
+
sheetId: payload.sheetId,
|
|
42354
|
+
definition: payload.definition,
|
|
42355
|
+
baseFigureId: overlappingFigure.id
|
|
42356
|
+
});
|
|
42357
|
+
else env.model.dispatch("CREATE_CHART", payload);
|
|
42358
|
+
};
|
|
42359
|
+
startDnd(onMouseMove, onMouseUp);
|
|
42360
|
+
}
|
|
42361
|
+
|
|
42362
|
+
//#endregion
|
|
42363
|
+
//#region src/components/side_panel/data_analysis/chart_suggestion_preview.ts
|
|
42364
|
+
const PREVIEW_BUBBLE_RADIUS_RATIO = .3;
|
|
42365
|
+
const MIN_PREVIEW_BUBBLE_RADIUS = 1;
|
|
42366
|
+
var ChartSuggestionPreview = class extends Component {
|
|
42367
|
+
static template = "o-spreadsheet-ChartSuggestionPreview";
|
|
42368
|
+
props = useProps({
|
|
42369
|
+
definition: types$1.ChartDefinition(),
|
|
42370
|
+
description: types$1.string(),
|
|
42371
|
+
onPointerDown: types$1.function()
|
|
42372
|
+
});
|
|
42373
|
+
chartCanvasRef = signal.ref(HTMLCanvasElement);
|
|
42374
|
+
chart;
|
|
42375
|
+
setup() {
|
|
42376
|
+
onMounted(() => registerChartJSExtensions());
|
|
42377
|
+
onWillUnmount(() => this.destroyChart());
|
|
42378
|
+
useLayoutEffect(() => {
|
|
42379
|
+
this.updateChart();
|
|
42380
|
+
}, () => [this.props.definition]);
|
|
42381
|
+
}
|
|
42382
|
+
getChartConfiguration() {
|
|
42383
|
+
const getters = this.env.model.getters;
|
|
42384
|
+
const activeSheetId = getters.getActiveSheetId();
|
|
42385
|
+
const runtime = SpreadsheetChart.fromStrDefinition(getters, activeSheetId, this.props.definition).getRuntime(getters, "myChart");
|
|
42386
|
+
if (!("chartJsConfig" in runtime)) return null;
|
|
42387
|
+
let config = runtime.chartJsConfig;
|
|
42388
|
+
const existingScales = config.options?.scales ?? {};
|
|
42389
|
+
config = {
|
|
42390
|
+
...config,
|
|
42391
|
+
data: this.props.definition.type === "bubble" ? {
|
|
42392
|
+
...config.data,
|
|
42393
|
+
datasets: (config.data?.datasets || []).map((dataset) => ({
|
|
42394
|
+
...dataset,
|
|
42395
|
+
pointRadius: this.scaleBubblePointRadius(dataset.pointRadius),
|
|
42396
|
+
pointHoverRadius: this.scaleBubblePointRadius(dataset.pointHoverRadius)
|
|
42397
|
+
}))
|
|
42398
|
+
} : config.data,
|
|
42399
|
+
options: {
|
|
42400
|
+
...config.options,
|
|
42401
|
+
plugins: {
|
|
42402
|
+
...config.options?.plugins,
|
|
42403
|
+
legend: { display: false },
|
|
42404
|
+
title: { display: false },
|
|
42405
|
+
tooltip: { enabled: false }
|
|
42406
|
+
},
|
|
42407
|
+
events: [],
|
|
42408
|
+
animation: false,
|
|
42409
|
+
scales: this.getScalesCongif(existingScales)
|
|
42410
|
+
}
|
|
42411
|
+
};
|
|
42412
|
+
return limitChartConfigDataPoints(config);
|
|
42413
|
+
}
|
|
42414
|
+
getScalesCongif(scales) {
|
|
42415
|
+
if (scales.x) scales.x = {
|
|
42416
|
+
...scales.x,
|
|
42417
|
+
ticks: { display: false },
|
|
42418
|
+
border: { display: false }
|
|
42419
|
+
};
|
|
42420
|
+
if (scales.y) scales.y = {
|
|
42421
|
+
...scales.y,
|
|
42422
|
+
ticks: { display: false },
|
|
42423
|
+
border: { display: false }
|
|
42424
|
+
};
|
|
42425
|
+
if (scales.r) scales.r = {
|
|
42426
|
+
...scales.r,
|
|
42427
|
+
ticks: { display: false },
|
|
42428
|
+
pointLabels: { display: false }
|
|
42429
|
+
};
|
|
42430
|
+
return scales;
|
|
42431
|
+
}
|
|
42432
|
+
scaleBubblePointRadius(pointRadius) {
|
|
42433
|
+
if (Array.isArray(pointRadius)) return pointRadius.map((radius) => radius <= 0 ? radius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, radius * PREVIEW_BUBBLE_RADIUS_RATIO));
|
|
42434
|
+
if (typeof pointRadius === "number") return pointRadius <= 0 ? pointRadius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, pointRadius * PREVIEW_BUBBLE_RADIUS_RATIO);
|
|
42435
|
+
return pointRadius ?? MIN_PREVIEW_BUBBLE_RADIUS;
|
|
42436
|
+
}
|
|
42437
|
+
updateChart() {
|
|
42438
|
+
this.destroyChart();
|
|
42439
|
+
const config = this.getChartConfiguration();
|
|
42440
|
+
if (!config) {
|
|
42441
|
+
this.drawNativeChart();
|
|
42442
|
+
return;
|
|
42443
|
+
}
|
|
42444
|
+
const ctx = this.chartCanvasRef()?.getContext("2d");
|
|
42445
|
+
if (!ctx || !globalThis.Chart) return;
|
|
42446
|
+
this.chart = new globalThis.Chart(ctx, config);
|
|
42447
|
+
}
|
|
42448
|
+
drawNativeChart() {
|
|
42449
|
+
const canvas = this.chartCanvasRef();
|
|
42450
|
+
if (!canvas) return;
|
|
42451
|
+
const getters = this.env.model.getters;
|
|
42452
|
+
let runtime = SpreadsheetChart.fromStrDefinition(getters, getters.getActiveSheetId(), this.props.definition).getRuntime(getters, "myChart");
|
|
42453
|
+
const { type } = this.props.definition;
|
|
42454
|
+
if (type === "scorecard") {
|
|
42455
|
+
const rect = canvas.getBoundingClientRect();
|
|
42456
|
+
runtime = {
|
|
42457
|
+
...runtime,
|
|
42458
|
+
keyValueStyle: {
|
|
42459
|
+
...runtime.keyValueStyle,
|
|
42460
|
+
fontSize: 16
|
|
42461
|
+
}
|
|
42462
|
+
};
|
|
42463
|
+
drawScoreChart(getScorecardConfiguration({
|
|
42464
|
+
width: rect.width || 130,
|
|
42465
|
+
height: rect.height || 120
|
|
42466
|
+
}, runtime), canvas);
|
|
42467
|
+
} else if (type === "gauge") {
|
|
42468
|
+
let gaugeRuntime = runtime;
|
|
42469
|
+
if (gaugeRuntime.title) gaugeRuntime = {
|
|
42470
|
+
...gaugeRuntime,
|
|
42471
|
+
title: { text: "" }
|
|
42472
|
+
};
|
|
42473
|
+
drawGaugeChart(canvas, gaugeRuntime, 1, void 0, { labelFontSize: 8 });
|
|
42474
|
+
}
|
|
42475
|
+
}
|
|
42476
|
+
destroyChart() {
|
|
42477
|
+
this.chart?.destroy();
|
|
42478
|
+
this.chart = void 0;
|
|
42479
|
+
}
|
|
42480
|
+
};
|
|
42481
|
+
|
|
42482
|
+
//#endregion
|
|
42483
|
+
//#region src/helpers/data_analysis.ts
|
|
42484
|
+
function analyzeColumns(zones, getters) {
|
|
42485
|
+
return getZonesByColumns(zones).map((zone) => analyzeColumn(zone, getters));
|
|
42486
|
+
}
|
|
42487
|
+
function analyzeColumn(zone, getters) {
|
|
42488
|
+
const sheetId = getters.getActiveSheetId();
|
|
42489
|
+
const cells = getters.getEvaluatedCellsInZone(sheetId, zone).filter((c) => c.type !== "empty");
|
|
42490
|
+
if (!cells.length) return {
|
|
42491
|
+
zone,
|
|
42492
|
+
type: "empty",
|
|
42493
|
+
hasHeader: false,
|
|
42494
|
+
rowCount: 0,
|
|
42495
|
+
uniqueCount: 0,
|
|
42496
|
+
uniqueRatio: 0,
|
|
42497
|
+
nonEmpty: []
|
|
42498
|
+
};
|
|
42499
|
+
const firstCell = cells[0];
|
|
42500
|
+
const rest = cells.slice(1);
|
|
42501
|
+
const hasHeader = firstCell.type === "text" && rest.some((c) => c.type !== "text");
|
|
42502
|
+
const dataCells = hasHeader ? rest : cells;
|
|
42503
|
+
const numericValues = dataCells.filter((c) => c.type === "number").map((c) => c.value);
|
|
42504
|
+
const allVals = dataCells.map((c) => String(c.value ?? ""));
|
|
42505
|
+
const uniqueCount = new Set(allVals).size;
|
|
42506
|
+
return {
|
|
42507
|
+
zone,
|
|
42508
|
+
type: computeColumnType(dataCells),
|
|
42509
|
+
header: hasHeader ? firstCell.value : void 0,
|
|
42510
|
+
hasHeader,
|
|
42511
|
+
rowCount: dataCells.length,
|
|
42512
|
+
uniqueCount,
|
|
42513
|
+
uniqueRatio: allVals.length > 0 ? uniqueCount / allVals.length : 0,
|
|
42514
|
+
maxValue: numericValues.length ? numericValues.reduce((max, v) => v > max ? v : max, numericValues[0]) : void 0,
|
|
42515
|
+
nonEmpty: dataCells
|
|
42516
|
+
};
|
|
42517
|
+
}
|
|
42518
|
+
function computeColumnType(cells) {
|
|
42519
|
+
if (cells.length === 0) return "empty";
|
|
42520
|
+
if (cells.every((c) => c.type === "error")) return "error";
|
|
42521
|
+
cells = cells.filter((c) => c.type !== "error");
|
|
42522
|
+
if (cells.every((c) => c.type === "boolean")) return "boolean";
|
|
42523
|
+
if (cells.every((c) => c.type === "number" && !!c.format && isDateTimeFormat(c.format))) return "date";
|
|
42524
|
+
else if (cells.every((c) => c.type === "number")) {
|
|
42525
|
+
if (cells.every((c) => c.format?.includes("%"))) return "percentage";
|
|
42526
|
+
return "number";
|
|
42527
|
+
} else {
|
|
42528
|
+
const textVals = cells.filter((c) => c.type === "text").map((c) => c.value);
|
|
42529
|
+
if (textVals.length > 0) {
|
|
42530
|
+
const unique = new Set(textVals).size;
|
|
42531
|
+
return unique / textVals.length < .75 && unique <= 20 ? "categorical" : "label";
|
|
42532
|
+
}
|
|
42533
|
+
}
|
|
42534
|
+
return "empty";
|
|
42535
|
+
}
|
|
42536
|
+
|
|
42537
|
+
//#endregion
|
|
42538
|
+
//#region src/helpers/figures/charts/charts_suggestions_constants.ts
|
|
42539
|
+
/** Above this many rows, shape-based charts (radar, scatter, pyramid) become unreadable. */
|
|
42540
|
+
const MAX_ROWS_FOR_SHAPE_CHART = 10;
|
|
42541
|
+
function barChart(titleText, source, opts = {}) {
|
|
42542
|
+
return {
|
|
42543
|
+
...opts,
|
|
42544
|
+
type: "bar",
|
|
42545
|
+
title: { text: titleText },
|
|
42546
|
+
dataSource: source,
|
|
42547
|
+
dataSetStyles: {},
|
|
42548
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42549
|
+
stacked: opts.stacked ?? false,
|
|
42550
|
+
humanize: true,
|
|
42551
|
+
aggregated: opts.aggregated ?? false
|
|
42552
|
+
};
|
|
42553
|
+
}
|
|
42554
|
+
function lineChart(titleText, source, opts = {}) {
|
|
42555
|
+
return {
|
|
42556
|
+
...opts,
|
|
42557
|
+
type: "line",
|
|
42558
|
+
title: { text: titleText },
|
|
42559
|
+
dataSource: source,
|
|
42560
|
+
dataSetStyles: {},
|
|
42561
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42562
|
+
stacked: opts.stacked ?? false,
|
|
42563
|
+
cumulative: opts.cumulative ?? false,
|
|
42564
|
+
labelsAsText: false,
|
|
42565
|
+
humanize: true
|
|
42566
|
+
};
|
|
42567
|
+
}
|
|
42568
|
+
function pieChart(titleText, source, opts = {}) {
|
|
42569
|
+
return {
|
|
42570
|
+
...opts,
|
|
42571
|
+
type: "pie",
|
|
42572
|
+
title: { text: titleText },
|
|
42573
|
+
dataSource: source,
|
|
42574
|
+
dataSetStyles: {},
|
|
42575
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42576
|
+
humanize: true
|
|
42577
|
+
};
|
|
42578
|
+
}
|
|
42579
|
+
function radarChart(titleText, source, opts = {}) {
|
|
42580
|
+
return {
|
|
42581
|
+
...opts,
|
|
42582
|
+
type: "radar",
|
|
42583
|
+
title: { text: titleText },
|
|
42584
|
+
dataSource: source,
|
|
42585
|
+
dataSetStyles: {},
|
|
42586
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42587
|
+
stacked: false,
|
|
42588
|
+
humanize: true
|
|
42589
|
+
};
|
|
42590
|
+
}
|
|
42591
|
+
function sunburstChart(titleText, source) {
|
|
42592
|
+
return {
|
|
42593
|
+
type: "sunburst",
|
|
42594
|
+
title: { text: titleText },
|
|
42595
|
+
dataSource: source,
|
|
42596
|
+
dataSetStyles: {},
|
|
42597
|
+
legendPosition: "none"
|
|
42598
|
+
};
|
|
42599
|
+
}
|
|
42600
|
+
function treemapChart(titleText, source) {
|
|
42601
|
+
return {
|
|
42602
|
+
type: "treemap",
|
|
42603
|
+
title: { text: titleText },
|
|
42604
|
+
dataSource: source,
|
|
42605
|
+
dataSetStyles: {},
|
|
42606
|
+
legendPosition: "none"
|
|
42607
|
+
};
|
|
42608
|
+
}
|
|
42609
|
+
function calendarChart(titleText, source) {
|
|
42610
|
+
return {
|
|
42611
|
+
type: "calendar",
|
|
42612
|
+
title: { text: titleText },
|
|
42613
|
+
dataSource: source,
|
|
42614
|
+
dataSetStyles: {},
|
|
42615
|
+
legendPosition: "none",
|
|
42616
|
+
horizontalGroupBy: "month_number",
|
|
42617
|
+
verticalGroupBy: "day_of_week",
|
|
42618
|
+
humanize: true
|
|
42619
|
+
};
|
|
42620
|
+
}
|
|
42621
|
+
function scatterChart(titleText, source) {
|
|
42622
|
+
return {
|
|
42623
|
+
type: "scatter",
|
|
42624
|
+
title: { text: titleText },
|
|
42625
|
+
dataSource: source,
|
|
42626
|
+
dataSetStyles: {},
|
|
42627
|
+
legendPosition: "none",
|
|
42628
|
+
labelsAsText: false,
|
|
42629
|
+
humanize: true
|
|
42630
|
+
};
|
|
42631
|
+
}
|
|
42632
|
+
function pyramidChart(titleText, source, opts = {}) {
|
|
42633
|
+
return {
|
|
42634
|
+
type: "pyramid",
|
|
42635
|
+
title: { text: titleText },
|
|
42636
|
+
dataSource: source,
|
|
42637
|
+
dataSetStyles: {},
|
|
42638
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42639
|
+
stacked: false,
|
|
42640
|
+
horizontal: true,
|
|
42641
|
+
humanize: true
|
|
42642
|
+
};
|
|
42643
|
+
}
|
|
42644
|
+
function comboChart(titleText, source, opts = {}) {
|
|
42645
|
+
return {
|
|
42646
|
+
...opts,
|
|
42647
|
+
type: "combo",
|
|
42648
|
+
title: { text: titleText },
|
|
42649
|
+
dataSource: source,
|
|
42650
|
+
dataSetStyles: {},
|
|
42651
|
+
legendPosition: "top",
|
|
42652
|
+
humanize: true
|
|
42653
|
+
};
|
|
42654
|
+
}
|
|
42655
|
+
function bubbleChart(titleText, xRange, yRanges, sizeRange, labelRange, dataSetsHaveTitle) {
|
|
42656
|
+
return {
|
|
42657
|
+
type: "bubble",
|
|
42658
|
+
title: { text: titleText },
|
|
42659
|
+
humanize: true,
|
|
42660
|
+
dataSetsHaveTitle,
|
|
42661
|
+
yRanges,
|
|
42662
|
+
xRange,
|
|
42663
|
+
sizeRange,
|
|
42664
|
+
labelRange,
|
|
42665
|
+
labelsAsText: false,
|
|
42666
|
+
legendPosition: "none",
|
|
42667
|
+
bubbleColor: { color: "multiple" },
|
|
42668
|
+
verticalAxisPosition: "left"
|
|
42669
|
+
};
|
|
42670
|
+
}
|
|
42671
|
+
function scorecardChart(titleText, keyValue, opts = {}) {
|
|
42672
|
+
return {
|
|
42673
|
+
...opts,
|
|
42674
|
+
type: "scorecard",
|
|
42675
|
+
title: { text: titleText },
|
|
42676
|
+
keyValue,
|
|
42677
|
+
baselineMode: opts.baselineMode ?? "difference",
|
|
42678
|
+
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
42679
|
+
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
42680
|
+
humanize: opts.humanize ?? true
|
|
42681
|
+
};
|
|
42682
|
+
}
|
|
42683
|
+
function gaugeChart(titleText, dataRange, rangeMin, rangeMax) {
|
|
42684
|
+
return {
|
|
42685
|
+
type: "gauge",
|
|
42686
|
+
title: { text: titleText },
|
|
42687
|
+
dataRange,
|
|
42688
|
+
sectionRule: {
|
|
42689
|
+
colors: {
|
|
42690
|
+
lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
|
|
42691
|
+
middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
|
|
42692
|
+
upperColor: DEFAULT_GAUGE_UPPER_COLOR
|
|
42693
|
+
},
|
|
42694
|
+
rangeMin,
|
|
42695
|
+
rangeMax,
|
|
42696
|
+
lowerInflectionPoint: {
|
|
42697
|
+
type: "percentage",
|
|
42698
|
+
value: "33",
|
|
42699
|
+
operator: "<="
|
|
42700
|
+
},
|
|
42701
|
+
upperInflectionPoint: {
|
|
42702
|
+
type: "percentage",
|
|
42703
|
+
value: "66",
|
|
42704
|
+
operator: "<="
|
|
42705
|
+
}
|
|
42706
|
+
},
|
|
42707
|
+
humanize: true
|
|
42708
|
+
};
|
|
42709
|
+
}
|
|
42710
|
+
/** Pattern A — Single numeric column */
|
|
42711
|
+
const SINGLE_NUMBER_COLUMN_SUGGESTIONS = [
|
|
42712
|
+
{
|
|
42713
|
+
description: _t("Highlights the most recent value compared to the previous one."),
|
|
42714
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42715
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42716
|
+
baseline: ctx.prevCellXC,
|
|
42717
|
+
baselineMode: "difference"
|
|
42718
|
+
})
|
|
42719
|
+
},
|
|
42720
|
+
{
|
|
42721
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
42722
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42723
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42724
|
+
},
|
|
42725
|
+
{
|
|
42726
|
+
description: _t("Compares individual values side-by-side."),
|
|
42727
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42728
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42729
|
+
},
|
|
42730
|
+
{
|
|
42731
|
+
description: _t("Shows the evolution of all values over the range."),
|
|
42732
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42733
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42734
|
+
},
|
|
42735
|
+
{
|
|
42736
|
+
description: _t("Emphasizes total accumulation over the range."),
|
|
42737
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42738
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42739
|
+
fillArea: true,
|
|
42740
|
+
cumulative: true
|
|
42741
|
+
})
|
|
42742
|
+
}
|
|
42743
|
+
];
|
|
42744
|
+
/** Pattern B — Single percentage column */
|
|
42745
|
+
const SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS = [
|
|
42746
|
+
{
|
|
42747
|
+
description: _t("Shows the last percentage value with its baseline."),
|
|
42748
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42749
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42750
|
+
baseline: ctx.prevCellXC,
|
|
42751
|
+
baselineMode: "percentage"
|
|
42752
|
+
})
|
|
42753
|
+
},
|
|
42754
|
+
{
|
|
42755
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42756
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42757
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, "0", ctx.isAboveOne ? "100" : "1")
|
|
42758
|
+
},
|
|
42759
|
+
{
|
|
42760
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42761
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42762
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42763
|
+
},
|
|
42764
|
+
{
|
|
42765
|
+
description: _t("Shows completion against total."),
|
|
42766
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42767
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { isDoughnut: true })
|
|
42768
|
+
},
|
|
42769
|
+
{
|
|
42770
|
+
description: _t("Compares all percentage values side-by-side."),
|
|
42771
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42772
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42773
|
+
}
|
|
42774
|
+
];
|
|
42775
|
+
/** Pattern C — Single date column */
|
|
42776
|
+
const SINGLE_DATE_COLUMN_SUGGESTIONS = [{
|
|
42777
|
+
description: _t("Shows the last date value."),
|
|
42778
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42779
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC)
|
|
42780
|
+
}];
|
|
42781
|
+
/** Pattern D — Single categorical column */
|
|
42782
|
+
const SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS = [
|
|
42783
|
+
{
|
|
42784
|
+
description: _t("Shows the share of each category."),
|
|
42785
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42786
|
+
aggregated: true,
|
|
42787
|
+
legendPosition: "top"
|
|
42788
|
+
})
|
|
42789
|
+
},
|
|
42790
|
+
{
|
|
42791
|
+
description: _t("Same as pie, cleaner proportional look."),
|
|
42792
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42793
|
+
aggregated: true,
|
|
42794
|
+
isDoughnut: true,
|
|
42795
|
+
legendPosition: "top"
|
|
42796
|
+
})
|
|
42797
|
+
},
|
|
42798
|
+
{
|
|
42799
|
+
description: _t("Absolute count per category."),
|
|
42800
|
+
build: (ctx) => barChart(ctx.title, {
|
|
42801
|
+
...ctx.source,
|
|
42802
|
+
labelRange: ctx.range
|
|
42803
|
+
}, {
|
|
42804
|
+
legendPosition: "none",
|
|
42805
|
+
aggregated: true
|
|
42806
|
+
})
|
|
42807
|
+
}
|
|
42808
|
+
];
|
|
42809
|
+
/** Pattern E — Single label column */
|
|
42810
|
+
const SINGLE_LABEL_COLUMN_SUGGESTIONS = [{
|
|
42811
|
+
description: _t("Displays a key performance indicator."),
|
|
42812
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42813
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42814
|
+
baselineMode: "text",
|
|
42815
|
+
humanize: false
|
|
42816
|
+
})
|
|
42817
|
+
}];
|
|
42818
|
+
/** Pattern F — Categorical + Number */
|
|
42819
|
+
const CATEGORICAL_VS_NUMBER_SUGGESTIONS = [
|
|
42820
|
+
{
|
|
42821
|
+
description: _t("Classic category-vs-value comparison."),
|
|
42822
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { aggregated: true })
|
|
42823
|
+
},
|
|
42824
|
+
{
|
|
42825
|
+
description: _t("Better when category labels are long."),
|
|
42826
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42827
|
+
horizontal: true,
|
|
42828
|
+
aggregated: true
|
|
42829
|
+
})
|
|
42830
|
+
},
|
|
42831
|
+
{
|
|
42832
|
+
description: _t("Share of total per category."),
|
|
42833
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42834
|
+
legendPosition: "top",
|
|
42835
|
+
aggregated: true
|
|
42836
|
+
})
|
|
42837
|
+
},
|
|
42838
|
+
{
|
|
42839
|
+
description: _t("Proportional area — good for many categories."),
|
|
42840
|
+
build: (ctx) => treemapChart(ctx.title, ctx.treemapSource)
|
|
42841
|
+
}
|
|
42842
|
+
];
|
|
42843
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
42844
|
+
const DATE_VS_SERIES_SUGGESTIONS = [
|
|
42845
|
+
{
|
|
42846
|
+
description: _t("Best for visualizing time-series trends."),
|
|
42847
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42848
|
+
},
|
|
42849
|
+
{
|
|
42850
|
+
description: _t("Emphasizes total volume over time."),
|
|
42851
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42852
|
+
fillArea: true,
|
|
42853
|
+
cumulative: true
|
|
42854
|
+
})
|
|
42855
|
+
},
|
|
42856
|
+
{
|
|
42857
|
+
description: _t("Period-by-period comparison."),
|
|
42858
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42859
|
+
},
|
|
42860
|
+
{
|
|
42861
|
+
description: _t("Shows intensity variation across days of the year."),
|
|
42862
|
+
isApplicable: ({ isPercentage }) => !isPercentage,
|
|
42863
|
+
build: (ctx) => calendarChart(ctx.title, ctx.source)
|
|
42864
|
+
}
|
|
42865
|
+
];
|
|
42866
|
+
/** Pattern H — Number + Number */
|
|
42867
|
+
const NUMBER_VS_NUMBER_SUGGESTIONS = [
|
|
42868
|
+
{
|
|
42869
|
+
description: _t("Highlights the second metric compared to the first one."),
|
|
42870
|
+
isApplicable: ({ rowCount1, rowCount2 }) => rowCount1 === 1 && rowCount2 === 1,
|
|
42871
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42872
|
+
baseline: ctx.prevCellXC,
|
|
42873
|
+
baselineMode: "difference"
|
|
42874
|
+
})
|
|
42875
|
+
},
|
|
42876
|
+
{
|
|
42877
|
+
description: _t("Side-by-side comparison of two numeric series."),
|
|
42878
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42879
|
+
},
|
|
42880
|
+
{
|
|
42881
|
+
description: _t("Reveals correlation between two numeric variables."),
|
|
42882
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42883
|
+
build: (ctx) => scatterChart(ctx.title, ctx.source2)
|
|
42884
|
+
},
|
|
42885
|
+
{
|
|
42886
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
42887
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42888
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth)
|
|
42889
|
+
},
|
|
42890
|
+
{
|
|
42891
|
+
description: _t("When both metrics contribute to a total."),
|
|
42892
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42893
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceBoth, {
|
|
42894
|
+
stacked: true,
|
|
42895
|
+
fillArea: true,
|
|
42896
|
+
legendPosition: "top"
|
|
42897
|
+
})
|
|
42898
|
+
},
|
|
42899
|
+
{
|
|
42900
|
+
description: _t("Shape-based comparison when rows represent named entities."),
|
|
42901
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2 && rowCount1 <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42902
|
+
build: (ctx) => radarChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42903
|
+
}
|
|
42904
|
+
];
|
|
42905
|
+
/** Pattern I — Categorical + Percentage */
|
|
42906
|
+
const CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS = [
|
|
42907
|
+
{
|
|
42908
|
+
description: _t("Vertical comparison of rates per category."),
|
|
42909
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42910
|
+
},
|
|
42911
|
+
{
|
|
42912
|
+
description: _t("Progress-bar style per category."),
|
|
42913
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42914
|
+
},
|
|
42915
|
+
{
|
|
42916
|
+
description: _t("Share of total percentage across categories."),
|
|
42917
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42918
|
+
},
|
|
42919
|
+
{
|
|
42920
|
+
description: _t("Comparison of completion rates across categories."),
|
|
42921
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42922
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42923
|
+
}
|
|
42924
|
+
];
|
|
42925
|
+
/** Pattern K — Label + Number */
|
|
42926
|
+
const LABEL_VS_NUMBER_SUGGESTIONS = [
|
|
42927
|
+
{
|
|
42928
|
+
description: _t("Highlights the most recent value for the named entity."),
|
|
42929
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42930
|
+
build: (ctx) => scorecardChart("", ctx.lastCellXC, {
|
|
42931
|
+
humanize: false,
|
|
42932
|
+
baselineMode: "text",
|
|
42933
|
+
baseline: ctx.prevCellXC
|
|
42934
|
+
})
|
|
42935
|
+
},
|
|
42936
|
+
{
|
|
42937
|
+
description: _t("Vertical comparison across named items."),
|
|
42938
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42939
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42940
|
+
},
|
|
42941
|
+
{
|
|
42942
|
+
description: _t("Works well for named entities with long labels."),
|
|
42943
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42944
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42945
|
+
},
|
|
42946
|
+
{
|
|
42947
|
+
description: _t("Share of total per category."),
|
|
42948
|
+
isApplicable: ({ rowCount }) => rowCount > 1 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42949
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42950
|
+
legendPosition: "top",
|
|
42951
|
+
aggregated: true
|
|
42952
|
+
})
|
|
42953
|
+
},
|
|
42954
|
+
{
|
|
42955
|
+
description: _t("Shape-based comparison across labeled items."),
|
|
42956
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42957
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42958
|
+
}
|
|
42959
|
+
];
|
|
42960
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
42961
|
+
const CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42962
|
+
{
|
|
42963
|
+
description: _t("Side-by-side comparison across categories for each series."),
|
|
42964
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42965
|
+
},
|
|
42966
|
+
{
|
|
42967
|
+
description: _t("Shows composition and total per category."),
|
|
42968
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42969
|
+
stacked: true,
|
|
42970
|
+
legendPosition: "top"
|
|
42971
|
+
})
|
|
42972
|
+
},
|
|
42973
|
+
{
|
|
42974
|
+
description: _t("Trend per series across categories."),
|
|
42975
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42976
|
+
},
|
|
42977
|
+
{
|
|
42978
|
+
description: _t("Volume and composition across categories."),
|
|
42979
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42980
|
+
stacked: true,
|
|
42981
|
+
fillArea: true,
|
|
42982
|
+
legendPosition: "top"
|
|
42983
|
+
})
|
|
42984
|
+
},
|
|
42985
|
+
{
|
|
42986
|
+
description: _t("Shape comparison across metrics (best for ≤ 10 rows)."),
|
|
42987
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42988
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42989
|
+
}
|
|
42990
|
+
];
|
|
42991
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
42992
|
+
const DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42993
|
+
{
|
|
42994
|
+
description: _t("Trend comparison across multiple metrics over time."),
|
|
42995
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42996
|
+
},
|
|
42997
|
+
{
|
|
42998
|
+
description: _t("Volume composition over time."),
|
|
42999
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
43000
|
+
stacked: true,
|
|
43001
|
+
fillArea: true,
|
|
43002
|
+
legendPosition: "top"
|
|
43003
|
+
})
|
|
43004
|
+
},
|
|
43005
|
+
{
|
|
43006
|
+
description: _t("Bar for the primary metric, line for the others."),
|
|
43007
|
+
build: (ctx) => comboChart(ctx.title, ctx.source)
|
|
43008
|
+
},
|
|
43009
|
+
{
|
|
43010
|
+
description: _t("Period-by-period grouped comparison."),
|
|
43011
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43012
|
+
}
|
|
43013
|
+
];
|
|
43014
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43015
|
+
const MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS = [
|
|
43016
|
+
{
|
|
43017
|
+
description: _t("Two-level hierarchy weighted by value."),
|
|
43018
|
+
build: (ctx) => sunburstChart(ctx.title, ctx.hierarchySource)
|
|
43019
|
+
},
|
|
43020
|
+
{
|
|
43021
|
+
description: _t("Proportional nested area weighted by value."),
|
|
43022
|
+
build: (ctx) => treemapChart(ctx.title, ctx.hierarchySource)
|
|
43023
|
+
},
|
|
43024
|
+
{
|
|
43025
|
+
description: _t("One series per inner category, grouped by outer category."),
|
|
43026
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, { legendPosition: "top" })
|
|
43027
|
+
},
|
|
43028
|
+
{
|
|
43029
|
+
description: _t("Contribution of inner categories per outer category."),
|
|
43030
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, {
|
|
43031
|
+
stacked: true,
|
|
43032
|
+
legendPosition: "top"
|
|
43033
|
+
})
|
|
43034
|
+
}
|
|
43035
|
+
];
|
|
43036
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43037
|
+
const CATEGORICAL_DATE_NUMBER_SUGGESTIONS = [
|
|
43038
|
+
{
|
|
43039
|
+
description: _t("Trend of values over time."),
|
|
43040
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, { legendPosition: "top" })
|
|
43041
|
+
},
|
|
43042
|
+
{
|
|
43043
|
+
description: _t("Volume contribution over time."),
|
|
43044
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, {
|
|
43045
|
+
stacked: true,
|
|
43046
|
+
fillArea: true,
|
|
43047
|
+
legendPosition: "top"
|
|
43048
|
+
})
|
|
43049
|
+
},
|
|
43050
|
+
{
|
|
43051
|
+
description: _t("Period × category side-by-side comparison."),
|
|
43052
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByCat, { legendPosition: "top" })
|
|
43053
|
+
},
|
|
43054
|
+
{
|
|
43055
|
+
description: _t("Composition per period over time."),
|
|
43056
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByDate, {
|
|
43057
|
+
stacked: true,
|
|
43058
|
+
legendPosition: "top"
|
|
43059
|
+
})
|
|
43060
|
+
}
|
|
43061
|
+
];
|
|
43062
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43063
|
+
const LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
43064
|
+
{
|
|
43065
|
+
description: _t("Side-by-side per entity — works well for long labels."),
|
|
43066
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43067
|
+
horizontal: true,
|
|
43068
|
+
legendPosition: "top"
|
|
43069
|
+
})
|
|
43070
|
+
},
|
|
43071
|
+
{
|
|
43072
|
+
description: _t("Grouped bars per entity for direct metric comparison."),
|
|
43073
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43074
|
+
},
|
|
43075
|
+
{
|
|
43076
|
+
description: _t("Shape/profile comparison across metrics for each entity."),
|
|
43077
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43078
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43079
|
+
},
|
|
43080
|
+
{
|
|
43081
|
+
description: _t("Correlation between the two numeric metrics across entities."),
|
|
43082
|
+
isApplicable: ({ rowCount, numColsCount }) => rowCount > 2 && numColsCount === 2,
|
|
43083
|
+
build: (ctx) => scatterChart(ctx.title, ctx.scatterSource)
|
|
43084
|
+
},
|
|
43085
|
+
{
|
|
43086
|
+
description: _t("Three metrics in one view: X position, Y position and bubble size."),
|
|
43087
|
+
isApplicable: ({ numColsCount }) => numColsCount === 3,
|
|
43088
|
+
build: (ctx) => {
|
|
43089
|
+
const bubble = ctx.bubble;
|
|
43090
|
+
return bubbleChart(ctx.title, bubble.xRange, bubble.yRanges, bubble.sizeRange, bubble.labelRange, bubble.hasTitle);
|
|
43091
|
+
}
|
|
43092
|
+
}
|
|
43093
|
+
];
|
|
43094
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43095
|
+
const CATEGORICAL_TWO_NUMBERS_SUGGESTIONS = [
|
|
43096
|
+
{
|
|
43097
|
+
description: _t("Natural fit for symmetric or opposing values per category."),
|
|
43098
|
+
isApplicable: ({ isPyramid }) => isPyramid,
|
|
43099
|
+
build: (ctx) => pyramidChart(ctx.title, ctx.sourceBoth)
|
|
43100
|
+
},
|
|
43101
|
+
{
|
|
43102
|
+
description: _t("Side-by-side comparison of both metrics per category."),
|
|
43103
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43104
|
+
legendPosition: "top",
|
|
43105
|
+
aggregated: true
|
|
43106
|
+
})
|
|
43107
|
+
},
|
|
43108
|
+
{
|
|
43109
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
43110
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth, { aggregated: true })
|
|
43111
|
+
},
|
|
43112
|
+
{
|
|
43113
|
+
description: _t("Shows total and composition per category."),
|
|
43114
|
+
isApplicable: ({ isPyramid }) => !isPyramid,
|
|
43115
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43116
|
+
stacked: true,
|
|
43117
|
+
legendPosition: "top",
|
|
43118
|
+
aggregated: true
|
|
43119
|
+
})
|
|
43120
|
+
}
|
|
43121
|
+
];
|
|
43122
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43123
|
+
const MANY_NUMBERS_SUGGESTIONS = [
|
|
43124
|
+
{
|
|
43125
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
43126
|
+
isApplicable: ({ colsLength, rowCount }) => colsLength === 3 && rowCount === 1,
|
|
43127
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.secondCellXC}`)
|
|
43128
|
+
},
|
|
43129
|
+
{
|
|
43130
|
+
description: _t("Trend comparison across all metrics."),
|
|
43131
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
43132
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43133
|
+
},
|
|
43134
|
+
{
|
|
43135
|
+
description: _t("Overall shape/profile across all metrics."),
|
|
43136
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43137
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43138
|
+
},
|
|
43139
|
+
{
|
|
43140
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43141
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43142
|
+
},
|
|
43143
|
+
{
|
|
43144
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43145
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43146
|
+
legendPosition: "top",
|
|
43147
|
+
stacked: true
|
|
43148
|
+
})
|
|
43149
|
+
}
|
|
43150
|
+
];
|
|
43151
|
+
|
|
43152
|
+
//#endregion
|
|
43153
|
+
//#region src/helpers/figures/charts/chart_suggestion_engine.ts
|
|
43154
|
+
const PYRAMID_HEADER_KEYWORD_PATTERNS = [
|
|
43155
|
+
"male",
|
|
43156
|
+
"female",
|
|
43157
|
+
"man",
|
|
43158
|
+
"woman",
|
|
43159
|
+
"boy",
|
|
43160
|
+
"girl",
|
|
43161
|
+
"before",
|
|
43162
|
+
"after",
|
|
43163
|
+
"previous",
|
|
43164
|
+
"current",
|
|
43165
|
+
"positive",
|
|
43166
|
+
"negative",
|
|
43167
|
+
"gain",
|
|
43168
|
+
"loss",
|
|
43169
|
+
"income",
|
|
43170
|
+
"expense",
|
|
43171
|
+
"revenue",
|
|
43172
|
+
"cost"
|
|
43173
|
+
].map((keyword) => new RegExp(`\\b${keyword}\\b`, "i"));
|
|
43174
|
+
function matchesColumnType(spec, type) {
|
|
43175
|
+
return Array.isArray(spec) ? spec.includes(type) : spec === type;
|
|
43176
|
+
}
|
|
43177
|
+
function matchesShape(pattern, shape) {
|
|
43178
|
+
const { leadingTypes, extension } = pattern;
|
|
43179
|
+
const expectedLength = leadingTypes.length + (extension?.minSize ?? 0);
|
|
43180
|
+
if (extension ? shape.length < expectedLength : shape.length !== expectedLength) return false;
|
|
43181
|
+
return shape.every((type, i) => {
|
|
43182
|
+
const spec = i < leadingTypes.length ? leadingTypes[i] : extension?.type;
|
|
43183
|
+
return spec !== void 0 && matchesColumnType(spec, type);
|
|
43184
|
+
});
|
|
43185
|
+
}
|
|
43186
|
+
function isPyramidLike(numCol1, numCol2) {
|
|
43187
|
+
const h1 = numCol1.header ?? "";
|
|
43188
|
+
const h2 = numCol2.header ?? "";
|
|
43189
|
+
return PYRAMID_HEADER_KEYWORD_PATTERNS.some((re) => re.test(h1) || re.test(h2));
|
|
43190
|
+
}
|
|
43191
|
+
/** Returns the first and last cell XC coordinates, and the second-to-last cell XC coordinate if it exists. */
|
|
43192
|
+
function interestingCellsXc(col) {
|
|
43193
|
+
if (!col) return {
|
|
43194
|
+
firstCellXC: "",
|
|
43195
|
+
lastCellXC: "",
|
|
43196
|
+
prevCellXC: void 0
|
|
43197
|
+
};
|
|
43198
|
+
const firstCellPosition = col.nonEmpty.at(0)?.position;
|
|
43199
|
+
const firstCellXC = firstCellPosition ? toXC(firstCellPosition?.col, firstCellPosition?.row) : "";
|
|
43200
|
+
const lastCellPosition = col.nonEmpty.at(-1)?.position;
|
|
43201
|
+
const lastCellXC = lastCellPosition ? toXC(lastCellPosition?.col, lastCellPosition?.row) : "";
|
|
43202
|
+
const prevCellPosition = col.nonEmpty.at(-2)?.position;
|
|
43203
|
+
return {
|
|
43204
|
+
firstCellXC,
|
|
43205
|
+
lastCellXC,
|
|
43206
|
+
prevCellXC: prevCellPosition ? toXC(prevCellPosition?.col, prevCellPosition?.row) : void 0
|
|
43207
|
+
};
|
|
43208
|
+
}
|
|
43209
|
+
/** Pattern A — Single numeric column */
|
|
43210
|
+
function buildSingleNumberContext([col], getters) {
|
|
43211
|
+
const title = col.header ?? _t("Value");
|
|
43212
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43213
|
+
return {
|
|
43214
|
+
title,
|
|
43215
|
+
source: rangeSource([dataset(col.zone, getters)], col.hasHeader),
|
|
43216
|
+
rowCount: col.rowCount,
|
|
43217
|
+
firstCellXC,
|
|
43218
|
+
lastCellXC,
|
|
43219
|
+
prevCellXC
|
|
43220
|
+
};
|
|
43221
|
+
}
|
|
43222
|
+
/** Pattern B — Single percentage column */
|
|
43223
|
+
function buildSinglePercentageContext([col], getters) {
|
|
43224
|
+
const hasTitle = col.hasHeader;
|
|
43225
|
+
const title = col.header ?? _t("Rate");
|
|
43226
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43227
|
+
const source = rangeSource([dataset(col.zone, getters)], hasTitle);
|
|
43228
|
+
const isAboveOne = (col.maxValue ?? 0) > 1;
|
|
43229
|
+
return {
|
|
43230
|
+
title,
|
|
43231
|
+
source,
|
|
43232
|
+
rowCount: col.rowCount,
|
|
43233
|
+
firstCellXC,
|
|
43234
|
+
lastCellXC,
|
|
43235
|
+
prevCellXC,
|
|
43236
|
+
isAboveOne
|
|
43237
|
+
};
|
|
43238
|
+
}
|
|
43239
|
+
/** Pattern C — Single date column */
|
|
43240
|
+
function buildSingleDateContext([col]) {
|
|
43241
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43242
|
+
return {
|
|
43243
|
+
title: col.header ?? _t("Date"),
|
|
43244
|
+
lastCellXC,
|
|
43245
|
+
rowCount: col.rowCount
|
|
43246
|
+
};
|
|
43247
|
+
}
|
|
43248
|
+
/** Pattern D — Single categorical column */
|
|
43249
|
+
function buildSingleCategoricalContext([col], getters) {
|
|
43250
|
+
const hasTitle = col.hasHeader;
|
|
43251
|
+
const title = col.header ?? _t("Category");
|
|
43252
|
+
const range = getUnboundRange(getters, col.zone);
|
|
43253
|
+
return {
|
|
43254
|
+
title,
|
|
43255
|
+
source: rangeSource([dataset(col.zone, getters)], hasTitle, range),
|
|
43256
|
+
range
|
|
43257
|
+
};
|
|
43258
|
+
}
|
|
43259
|
+
/** Pattern E — Single label column */
|
|
43260
|
+
function buildSingleLabelContext([col]) {
|
|
43261
|
+
const title = col.header ?? _t("Label");
|
|
43262
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43263
|
+
return {
|
|
43264
|
+
title,
|
|
43265
|
+
lastCellXC,
|
|
43266
|
+
rowCount: col.rowCount
|
|
43267
|
+
};
|
|
43268
|
+
}
|
|
43269
|
+
/** Pattern F — Categorical + Number */
|
|
43270
|
+
function buildCategoricalVsNumberContext([catCol, numCol], getters) {
|
|
43271
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43272
|
+
const hasTitle = numCol.hasHeader;
|
|
43273
|
+
return {
|
|
43274
|
+
title: numCol.header ? _t("%(numberHeader)s by %(categoryHeader)s", {
|
|
43275
|
+
numberHeader: numCol.header,
|
|
43276
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43277
|
+
}) : _t("By Category"),
|
|
43278
|
+
source: rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange),
|
|
43279
|
+
treemapSource: rangeSource([dataset(catCol.zone, getters)], hasTitle, getUnboundRange(getters, numCol.zone))
|
|
43280
|
+
};
|
|
43281
|
+
}
|
|
43282
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
43283
|
+
function buildDateVsSeriesContext([dateCol, seriesCol], getters) {
|
|
43284
|
+
const isPercentage = seriesCol.type === "percentage";
|
|
43285
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43286
|
+
const hasTitle = isDatasetTitled(getters, dateCol.zone);
|
|
43287
|
+
return {
|
|
43288
|
+
title: seriesCol.header ? _t("%(seriesHeader)s over time", { seriesHeader: seriesCol.header }) : isPercentage ? _t("Rate over Time") : _t("Over Time"),
|
|
43289
|
+
source: rangeSource([dataset(seriesCol.zone, getters)], hasTitle, labelRange),
|
|
43290
|
+
isPercentage
|
|
43291
|
+
};
|
|
43292
|
+
}
|
|
43293
|
+
/** Pattern H — Number + Number */
|
|
43294
|
+
function buildNumberVsNumberContext([col1, col2], getters) {
|
|
43295
|
+
const title = col1.header && col2.header ? _t("%(col2Header)s vs %(col1Header)s", {
|
|
43296
|
+
col2Header: col2.header,
|
|
43297
|
+
col1Header: col1.header
|
|
43298
|
+
}) : _t("Correlation");
|
|
43299
|
+
const hasTitle = col1.hasHeader || col2.hasHeader;
|
|
43300
|
+
const source2 = rangeSource([dataset(col2.zone, getters)], col2.hasHeader, getUnboundRange(getters, col1.zone));
|
|
43301
|
+
const sourceBoth = rangeSource([dataset(col1.zone, getters, "0"), dataset(col2.zone, getters, "1")], hasTitle);
|
|
43302
|
+
const { lastCellXC } = interestingCellsXc(col2);
|
|
43303
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(col1);
|
|
43304
|
+
return {
|
|
43305
|
+
title,
|
|
43306
|
+
source2,
|
|
43307
|
+
sourceBoth,
|
|
43308
|
+
rowCount1: col1.rowCount,
|
|
43309
|
+
rowCount2: col2.rowCount,
|
|
43310
|
+
lastCellXC,
|
|
43311
|
+
prevCellXC
|
|
43312
|
+
};
|
|
43313
|
+
}
|
|
43314
|
+
/** Pattern I — Categorical + Percentage */
|
|
43315
|
+
function buildCategoricalVsPercentageContext([catCol, pctCol], getters) {
|
|
43316
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43317
|
+
const hasTitle = pctCol.hasHeader;
|
|
43318
|
+
return {
|
|
43319
|
+
title: pctCol.header ? _t("%(percentageHeader)s by %(categoryHeader)s", {
|
|
43320
|
+
percentageHeader: pctCol.header,
|
|
43321
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43322
|
+
}) : _t("Rates by Category"),
|
|
43323
|
+
source: rangeSource([dataset(pctCol.zone, getters)], hasTitle, labelRange),
|
|
43324
|
+
rowCount: catCol.rowCount
|
|
43325
|
+
};
|
|
43326
|
+
}
|
|
43327
|
+
/** Pattern K — Label + Number */
|
|
43328
|
+
function buildLabelVsNumberContext([labelCol, numCol], getters) {
|
|
43329
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43330
|
+
const hasTitle = numCol.hasHeader;
|
|
43331
|
+
const title = numCol.header ? _t("%(numberHeader)s by %(labelHeader)s", {
|
|
43332
|
+
numberHeader: numCol.header,
|
|
43333
|
+
labelHeader: labelCol.header ?? _t("Name")
|
|
43334
|
+
}) : _t("By Name");
|
|
43335
|
+
const source = rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange);
|
|
43336
|
+
const { lastCellXC } = interestingCellsXc(numCol);
|
|
43337
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(labelCol);
|
|
43338
|
+
return {
|
|
43339
|
+
title,
|
|
43340
|
+
source,
|
|
43341
|
+
rowCount: labelCol.rowCount,
|
|
43342
|
+
lastCellXC,
|
|
43343
|
+
prevCellXC
|
|
43344
|
+
};
|
|
43345
|
+
}
|
|
43346
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
43347
|
+
function buildCategoricalVsMultipleNumbersContext([catCol, ...numCols], getters) {
|
|
43348
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43349
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43350
|
+
return {
|
|
43351
|
+
title: catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Multi-series"),
|
|
43352
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange),
|
|
43353
|
+
rowCount: catCol.rowCount
|
|
43354
|
+
};
|
|
43355
|
+
}
|
|
43356
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
43357
|
+
function buildDateVsMultipleNumbersContext([dateCol, ...numCols], getters) {
|
|
43358
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43359
|
+
const hasTitle = numCols.some((c) => c.hasHeader) || isDatasetTitled(getters, dateCol.zone);
|
|
43360
|
+
return {
|
|
43361
|
+
title: numCols.length === 1 && numCols[0].header ? _t("%(header)s over time", { header: numCols[0].header }) : _t("Multi-series over Time"),
|
|
43362
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange)
|
|
43363
|
+
};
|
|
43364
|
+
}
|
|
43365
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43366
|
+
function buildMultipleCategoricalsVsNumberContext([cat1, cat2, numCol], getters) {
|
|
43367
|
+
const title = numCol.header ? _t("%(numHeader)s by %(cat1Header)s and %(cat2Header)s", {
|
|
43368
|
+
numHeader: numCol.header,
|
|
43369
|
+
cat1Header: cat1.header ?? _t("Level 1"),
|
|
43370
|
+
cat2Header: cat2.header ?? _t("Level 2")
|
|
43371
|
+
}) : _t("Two-level hierarchy");
|
|
43372
|
+
const hasTitle = numCol.hasHeader;
|
|
43373
|
+
return {
|
|
43374
|
+
title,
|
|
43375
|
+
hierarchySource: rangeSource([dataset(cat1.zone, getters, "0"), dataset(cat2.zone, getters, "1")], hasTitle, getUnboundRange(getters, numCol.zone)),
|
|
43376
|
+
barSource: rangeSource([dataset(numCol.zone, getters)], hasTitle, getUnboundRange(getters, cat1.zone))
|
|
43377
|
+
};
|
|
43378
|
+
}
|
|
43379
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43380
|
+
function buildCategoricalDateNumberContext([catCol, dateCol, numCol], getters) {
|
|
43381
|
+
const dateRange = getUnboundRange(getters, dateCol.zone);
|
|
43382
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43383
|
+
const hasTitle = numCol.hasHeader;
|
|
43384
|
+
return {
|
|
43385
|
+
title: numCol.header ? _t("%(numHeader)s by %(catHeader)s over %(dateHeader)s", {
|
|
43386
|
+
numHeader: numCol.header,
|
|
43387
|
+
catHeader: catCol.header ?? _t("Category"),
|
|
43388
|
+
dateHeader: dateCol.header ?? _t("Time")
|
|
43389
|
+
}) : _t("Multi-series over Time"),
|
|
43390
|
+
sourceByDate: rangeSource([dataset(numCol.zone, getters)], hasTitle, dateRange),
|
|
43391
|
+
sourceByCat: rangeSource([dataset(numCol.zone, getters)], hasTitle, catRange)
|
|
43392
|
+
};
|
|
43393
|
+
}
|
|
43394
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43395
|
+
function buildLabelVsMultipleNumbersContext([labelCol, ...numCols], getters) {
|
|
43396
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43397
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43398
|
+
const title = labelCol.header ? _t("By %(header)s", { header: labelCol.header }) : _t("Profile Comparison");
|
|
43399
|
+
const source = rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange);
|
|
43400
|
+
const scatterSource = rangeSource([dataset(numCols[1].zone, getters)], numCols[1].hasHeader, getUnboundRange(getters, numCols[0].zone));
|
|
43401
|
+
const bubble = numCols.length === 3 ? {
|
|
43402
|
+
xRange: getUnboundRange(getters, numCols[0].zone),
|
|
43403
|
+
yRanges: [getUnboundRange(getters, numCols[1].zone)],
|
|
43404
|
+
sizeRange: getUnboundRange(getters, numCols[2].zone),
|
|
43405
|
+
labelRange,
|
|
43406
|
+
hasTitle
|
|
43407
|
+
} : void 0;
|
|
43408
|
+
return {
|
|
43409
|
+
title,
|
|
43410
|
+
source,
|
|
43411
|
+
rowCount: labelCol.rowCount,
|
|
43412
|
+
numColsCount: numCols.length,
|
|
43413
|
+
scatterSource,
|
|
43414
|
+
bubble
|
|
43415
|
+
};
|
|
43416
|
+
}
|
|
43417
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43418
|
+
function buildCategoricalTwoNumbersContext([catCol, numCol1, numCol2], getters) {
|
|
43419
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43420
|
+
const hasTitle = numCol1.hasHeader || numCol2.hasHeader;
|
|
43421
|
+
const title = numCol1.header && numCol2.header ? _t("%(num1Header)s vs %(num2Header)s by %(catHeader)s", {
|
|
43422
|
+
num1Header: numCol1.header,
|
|
43423
|
+
num2Header: numCol2.header,
|
|
43424
|
+
catHeader: catCol.header ?? _t("Category")
|
|
43425
|
+
}) : catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Category Comparison");
|
|
43426
|
+
const isPyramid = isPyramidLike(numCol1, numCol2);
|
|
43427
|
+
return {
|
|
43428
|
+
title,
|
|
43429
|
+
sourceBoth: rangeSource([dataset(numCol1.zone, getters, "0"), dataset(numCol2.zone, getters, "1")], hasTitle, catRange),
|
|
43430
|
+
isPyramid
|
|
43431
|
+
};
|
|
43432
|
+
}
|
|
43433
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43434
|
+
function buildManyNumbersContext(cols, getters) {
|
|
43435
|
+
const title = cols.every((c) => c.hasHeader) ? cols.map((c) => c.header).join(" / ") : _t("KPI Overview");
|
|
43436
|
+
const hasTitle = cols.some((c) => c.hasHeader);
|
|
43437
|
+
const source = rangeSource(cols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle);
|
|
43438
|
+
const { lastCellXC } = interestingCellsXc(cols[2]);
|
|
43439
|
+
const { lastCellXC: firstCellXC } = interestingCellsXc(cols[0]);
|
|
43440
|
+
const { lastCellXC: secondCellXC } = interestingCellsXc(cols[1]);
|
|
43441
|
+
return {
|
|
43442
|
+
title,
|
|
43443
|
+
source,
|
|
43444
|
+
colsLength: cols.length,
|
|
43445
|
+
rowCount: cols[0].rowCount,
|
|
43446
|
+
lastCellXC,
|
|
43447
|
+
firstCellXC,
|
|
43448
|
+
secondCellXC
|
|
43449
|
+
};
|
|
43450
|
+
}
|
|
43451
|
+
const NUMBER_OR_PERCENTAGE = ["number", "percentage"];
|
|
43452
|
+
const EXACT_PATTERNS = [
|
|
43453
|
+
{
|
|
43454
|
+
pattern: { leadingTypes: ["number"] },
|
|
43455
|
+
buildContext: buildSingleNumberContext,
|
|
43456
|
+
suggestions: SINGLE_NUMBER_COLUMN_SUGGESTIONS
|
|
43457
|
+
},
|
|
43458
|
+
{
|
|
43459
|
+
pattern: { leadingTypes: ["percentage"] },
|
|
43460
|
+
buildContext: buildSinglePercentageContext,
|
|
43461
|
+
suggestions: SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS
|
|
43462
|
+
},
|
|
43463
|
+
{
|
|
43464
|
+
pattern: { leadingTypes: ["date"] },
|
|
43465
|
+
buildContext: buildSingleDateContext,
|
|
43466
|
+
suggestions: SINGLE_DATE_COLUMN_SUGGESTIONS
|
|
43467
|
+
},
|
|
43468
|
+
{
|
|
43469
|
+
pattern: { leadingTypes: ["categorical"] },
|
|
43470
|
+
buildContext: buildSingleCategoricalContext,
|
|
43471
|
+
suggestions: SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS
|
|
43472
|
+
},
|
|
43473
|
+
{
|
|
43474
|
+
pattern: { leadingTypes: ["label"] },
|
|
43475
|
+
buildContext: buildSingleLabelContext,
|
|
43476
|
+
suggestions: SINGLE_LABEL_COLUMN_SUGGESTIONS
|
|
43477
|
+
},
|
|
43478
|
+
{
|
|
43479
|
+
pattern: { leadingTypes: ["categorical", "number"] },
|
|
43480
|
+
buildContext: buildCategoricalVsNumberContext,
|
|
43481
|
+
suggestions: CATEGORICAL_VS_NUMBER_SUGGESTIONS
|
|
43482
|
+
},
|
|
43483
|
+
{
|
|
43484
|
+
pattern: { leadingTypes: ["date", NUMBER_OR_PERCENTAGE] },
|
|
43485
|
+
buildContext: buildDateVsSeriesContext,
|
|
43486
|
+
suggestions: DATE_VS_SERIES_SUGGESTIONS
|
|
43487
|
+
},
|
|
43488
|
+
{
|
|
43489
|
+
pattern: { leadingTypes: ["number", "number"] },
|
|
43490
|
+
buildContext: buildNumberVsNumberContext,
|
|
43491
|
+
suggestions: NUMBER_VS_NUMBER_SUGGESTIONS
|
|
43492
|
+
},
|
|
43493
|
+
{
|
|
43494
|
+
pattern: { leadingTypes: ["categorical", "percentage"] },
|
|
43495
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43496
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43497
|
+
},
|
|
43498
|
+
{
|
|
43499
|
+
pattern: { leadingTypes: ["label", "number"] },
|
|
43500
|
+
buildContext: buildLabelVsNumberContext,
|
|
43501
|
+
suggestions: LABEL_VS_NUMBER_SUGGESTIONS
|
|
43502
|
+
},
|
|
43503
|
+
{
|
|
43504
|
+
pattern: { leadingTypes: ["label", "percentage"] },
|
|
43505
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43506
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43507
|
+
},
|
|
43508
|
+
{
|
|
43509
|
+
pattern: { leadingTypes: [
|
|
43510
|
+
"categorical",
|
|
43511
|
+
"categorical",
|
|
43512
|
+
"number"
|
|
43513
|
+
] },
|
|
43514
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43515
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43516
|
+
},
|
|
43517
|
+
{
|
|
43518
|
+
pattern: { leadingTypes: [
|
|
43519
|
+
"categorical",
|
|
43520
|
+
"label",
|
|
43521
|
+
"number"
|
|
43522
|
+
] },
|
|
43523
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43524
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43525
|
+
},
|
|
43526
|
+
{
|
|
43527
|
+
pattern: { leadingTypes: [
|
|
43528
|
+
"categorical",
|
|
43529
|
+
"date",
|
|
43530
|
+
NUMBER_OR_PERCENTAGE
|
|
43531
|
+
] },
|
|
43532
|
+
buildContext: buildCategoricalDateNumberContext,
|
|
43533
|
+
suggestions: CATEGORICAL_DATE_NUMBER_SUGGESTIONS
|
|
43534
|
+
},
|
|
43535
|
+
{
|
|
43536
|
+
pattern: { leadingTypes: [
|
|
43537
|
+
"categorical",
|
|
43538
|
+
NUMBER_OR_PERCENTAGE,
|
|
43539
|
+
NUMBER_OR_PERCENTAGE
|
|
43540
|
+
] },
|
|
43541
|
+
buildContext: buildCategoricalTwoNumbersContext,
|
|
43542
|
+
suggestions: CATEGORICAL_TWO_NUMBERS_SUGGESTIONS
|
|
43543
|
+
}
|
|
43544
|
+
];
|
|
43545
|
+
const EXTENDABLE_PATTERNS = [
|
|
43546
|
+
{
|
|
43547
|
+
pattern: {
|
|
43548
|
+
leadingTypes: [],
|
|
43549
|
+
extension: {
|
|
43550
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43551
|
+
minSize: 3
|
|
43552
|
+
}
|
|
43553
|
+
},
|
|
43554
|
+
buildContext: buildManyNumbersContext,
|
|
43555
|
+
suggestions: MANY_NUMBERS_SUGGESTIONS
|
|
43556
|
+
},
|
|
43557
|
+
{
|
|
43558
|
+
pattern: {
|
|
43559
|
+
leadingTypes: ["label"],
|
|
43560
|
+
extension: {
|
|
43561
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43562
|
+
minSize: 2
|
|
43563
|
+
}
|
|
43564
|
+
},
|
|
43565
|
+
buildContext: buildLabelVsMultipleNumbersContext,
|
|
43566
|
+
suggestions: LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43567
|
+
},
|
|
43568
|
+
{
|
|
43569
|
+
pattern: {
|
|
43570
|
+
leadingTypes: ["categorical"],
|
|
43571
|
+
extension: {
|
|
43572
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43573
|
+
minSize: 2
|
|
43574
|
+
}
|
|
43575
|
+
},
|
|
43576
|
+
buildContext: buildCategoricalVsMultipleNumbersContext,
|
|
43577
|
+
suggestions: CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43578
|
+
},
|
|
43579
|
+
{
|
|
43580
|
+
pattern: {
|
|
43581
|
+
leadingTypes: ["date"],
|
|
43582
|
+
extension: {
|
|
43583
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43584
|
+
minSize: 2
|
|
43585
|
+
}
|
|
43586
|
+
},
|
|
43587
|
+
buildContext: buildDateVsMultipleNumbersContext,
|
|
43588
|
+
suggestions: DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43589
|
+
}
|
|
43590
|
+
];
|
|
43591
|
+
function getChartSuggestions(zones, getters) {
|
|
43592
|
+
const cols = analyzeColumns(zones, getters);
|
|
43593
|
+
if (cols.some((c) => c.type === "error")) return [];
|
|
43594
|
+
const nonEmpty = cols.filter((c) => c.type !== "empty");
|
|
43595
|
+
if (!nonEmpty.length) return [];
|
|
43596
|
+
const shape = nonEmpty.map((c) => c.type);
|
|
43597
|
+
const rule = EXACT_PATTERNS.find((rule) => matchesShape(rule.pattern, shape)) ?? EXTENDABLE_PATTERNS.find((rule) => matchesShape(rule.pattern, shape));
|
|
43598
|
+
if (!rule) return [];
|
|
43599
|
+
const ctx = rule.buildContext(nonEmpty, getters);
|
|
43600
|
+
return rule.suggestions.filter((suggestion) => suggestion.isApplicable?.(ctx) ?? true).map((suggestion) => ({
|
|
43601
|
+
description: suggestion.description.toString(),
|
|
43602
|
+
definition: suggestion.build(ctx)
|
|
43603
|
+
}));
|
|
43604
|
+
}
|
|
43605
|
+
|
|
43606
|
+
//#endregion
|
|
43607
|
+
//#region src/components/side_panel/data_analysis/data_analysis_store.ts
|
|
43608
|
+
var DataAnalysisStore = class extends SpreadsheetStore {
|
|
43609
|
+
mutators = [];
|
|
43610
|
+
hasData = false;
|
|
43611
|
+
chartSuggestions = [];
|
|
43612
|
+
isDirty = false;
|
|
43613
|
+
ranges;
|
|
43614
|
+
constructor(get) {
|
|
43615
|
+
super(get);
|
|
43616
|
+
this.model.selection.observe(this, { handleEvent: () => this.refresh() });
|
|
43617
|
+
this.onDispose(() => {
|
|
43618
|
+
this.model.selection.unobserve(this);
|
|
43619
|
+
});
|
|
43620
|
+
this.refresh();
|
|
43621
|
+
}
|
|
43622
|
+
handle(cmd) {
|
|
43623
|
+
if (invalidateEvaluationCommands.has(cmd.type) || cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd)) this.isDirty = true;
|
|
43624
|
+
switch (cmd.type) {
|
|
43625
|
+
case "HIDE_COLUMNS_ROWS":
|
|
43626
|
+
case "UNHIDE_COLUMNS_ROWS":
|
|
43627
|
+
case "GROUP_HEADERS":
|
|
43628
|
+
case "UNGROUP_HEADERS":
|
|
43629
|
+
case "ACTIVATE_SHEET":
|
|
43630
|
+
case "ACTIVATE_NEXT_SHEET":
|
|
43631
|
+
case "ACTIVATE_PREVIOUS_SHEET":
|
|
43632
|
+
case "EVALUATE_CELLS":
|
|
43633
|
+
case "SET_FORMATTING":
|
|
43634
|
+
case "CLEAR_FORMATTING":
|
|
43635
|
+
this.isDirty = true;
|
|
43636
|
+
break;
|
|
43637
|
+
}
|
|
43638
|
+
}
|
|
43639
|
+
finalize() {
|
|
43640
|
+
if (this.isDirty) {
|
|
43641
|
+
this.refresh();
|
|
43642
|
+
this.isDirty = false;
|
|
43643
|
+
}
|
|
43644
|
+
}
|
|
43645
|
+
refresh() {
|
|
43646
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
43647
|
+
const zones = this.getters.getSelectedZones();
|
|
43648
|
+
this.ranges = zones.map(zoneToXc);
|
|
43649
|
+
this.hasData = zones.some((zone) => this.getters.getEvaluatedCellsInZone(sheetId, zone).some((cell) => cell.type !== "empty"));
|
|
43650
|
+
const suggestions = this.hasData ? getChartSuggestions(zones, this.getters) : [];
|
|
43651
|
+
this.chartSuggestions = suggestions;
|
|
43652
|
+
}
|
|
43653
|
+
};
|
|
43654
|
+
|
|
43655
|
+
//#endregion
|
|
43656
|
+
//#region src/components/side_panel/data_analysis/data_analysis_panel.ts
|
|
43657
|
+
var DataAnalysisPanel = class extends Component {
|
|
43658
|
+
static template = "o-spreadsheet-DataAnalysisPanel";
|
|
43659
|
+
static components = {
|
|
43660
|
+
Section,
|
|
43661
|
+
ChartSuggestionPreview
|
|
43662
|
+
};
|
|
43663
|
+
store;
|
|
43664
|
+
setup() {
|
|
43665
|
+
this.store = useLocalStore(DataAnalysisStore);
|
|
43666
|
+
}
|
|
43667
|
+
onStartChartSuggestionDrag(definition, ev) {
|
|
43668
|
+
startChartDragAndDrop(this.env, definition, ev);
|
|
43669
|
+
}
|
|
43670
|
+
};
|
|
43671
|
+
|
|
42147
43672
|
//#endregion
|
|
42148
43673
|
//#region src/registries/data_source_registry.ts
|
|
42149
43674
|
const unusedDataSourceRegistry = new Registry();
|
|
@@ -42220,7 +43745,7 @@ var DataSourcesCleanup = class extends Component {
|
|
|
42220
43745
|
var DataValidationPreview = class extends Component {
|
|
42221
43746
|
static template = "o-spreadsheet-DataValidationPreview";
|
|
42222
43747
|
props = useProps({ rule: types$1.DataValidationRule() });
|
|
42223
|
-
dvPreviewRef = signal(
|
|
43748
|
+
dvPreviewRef = signal.ref();
|
|
42224
43749
|
setup() {
|
|
42225
43750
|
useHighlightsOnHover(this.dvPreviewRef, this);
|
|
42226
43751
|
}
|
|
@@ -43719,7 +45244,7 @@ var NamedRangePreview = class extends Component {
|
|
|
43719
45244
|
};
|
|
43720
45245
|
props = useProps({ namedRange: types$1.NamedRange() });
|
|
43721
45246
|
state = proxy({});
|
|
43722
|
-
namedRangePreviewRef = signal(
|
|
45247
|
+
namedRangePreviewRef = signal.ref();
|
|
43723
45248
|
setup() {
|
|
43724
45249
|
useHighlightsOnHover(this.namedRangePreviewRef, this);
|
|
43725
45250
|
}
|
|
@@ -44210,7 +45735,7 @@ var FilterMenuValueItem = class extends Component {
|
|
|
44210
45735
|
onClick: types$1.function(),
|
|
44211
45736
|
scrolledTo: types$1.or([types$1.literal("top"), types$1.literal("bottom")]).optional()
|
|
44212
45737
|
});
|
|
44213
|
-
itemRef = signal(
|
|
45738
|
+
itemRef = signal.ref();
|
|
44214
45739
|
setup() {
|
|
44215
45740
|
onWillPatch(() => {
|
|
44216
45741
|
if (this.props.scrolledTo) this.scrollListToSelectedValue();
|
|
@@ -44450,7 +45975,7 @@ var PivotFilterEditor = class extends Component {
|
|
|
44450
45975
|
onFiltersUpdated: types$1.function()
|
|
44451
45976
|
});
|
|
44452
45977
|
state;
|
|
44453
|
-
buttonFilter = signal(
|
|
45978
|
+
buttonFilter = signal.ref();
|
|
44454
45979
|
popover;
|
|
44455
45980
|
setup() {
|
|
44456
45981
|
useExternalListener(window, "click", (ev) => {
|
|
@@ -44579,11 +46104,11 @@ var AddDimensionButton = class extends Component {
|
|
|
44579
46104
|
onFieldPicked: types$1.function(),
|
|
44580
46105
|
fields: types$1.array()
|
|
44581
46106
|
});
|
|
44582
|
-
buttonRef = signal(
|
|
46107
|
+
buttonRef = signal.ref(HTMLButtonElement);
|
|
44583
46108
|
popover = proxy({ isOpen: false });
|
|
44584
46109
|
search = proxy({ input: "" });
|
|
44585
46110
|
autoComplete;
|
|
44586
|
-
autofocusRef = signal(
|
|
46111
|
+
autofocusRef = signal.ref(HTMLInputElement);
|
|
44587
46112
|
setup() {
|
|
44588
46113
|
this.autoComplete = useLocalStore(AutoCompleteStore);
|
|
44589
46114
|
this.autoComplete.useProvider(this.getProvider());
|
|
@@ -44992,7 +46517,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
44992
46517
|
getScrollableContainerEl: types$1.function().optional(),
|
|
44993
46518
|
pivotId: types$1.UID()
|
|
44994
46519
|
});
|
|
44995
|
-
dimensionsRef = signal(
|
|
46520
|
+
dimensionsRef = signal.ref();
|
|
44996
46521
|
dragAndDrop = useDragAndDropListItems();
|
|
44997
46522
|
AGGREGATORS = AGGREGATORS;
|
|
44998
46523
|
isDateOrDatetimeField = isDateOrDatetimeField;
|
|
@@ -45063,16 +46588,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
45063
46588
|
});
|
|
45064
46589
|
}
|
|
45065
46590
|
getDimensionElementsRects() {
|
|
45066
|
-
return Array.from(this.dimensionsRef().children).map((el) =>
|
|
45067
|
-
const style = getComputedStyle(el);
|
|
45068
|
-
const rect = el.getBoundingClientRect();
|
|
45069
|
-
return {
|
|
45070
|
-
x: rect.x,
|
|
45071
|
-
y: rect.y,
|
|
45072
|
-
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
45073
|
-
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
45074
|
-
};
|
|
45075
|
-
});
|
|
46591
|
+
return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
|
|
45076
46592
|
}
|
|
45077
46593
|
removeDimension(dimension) {
|
|
45078
46594
|
const { columns, rows } = this.props.definition;
|
|
@@ -45637,7 +47153,7 @@ var PivotSpreadsheetSidePanel = class extends Component {
|
|
|
45637
47153
|
});
|
|
45638
47154
|
store;
|
|
45639
47155
|
state;
|
|
45640
|
-
pivotSidePanelRef = signal(
|
|
47156
|
+
pivotSidePanelRef = signal.ref();
|
|
45641
47157
|
setup() {
|
|
45642
47158
|
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
|
|
45643
47159
|
this.state = proxy({
|
|
@@ -46877,7 +48393,7 @@ var TableStylePreview = class extends Component {
|
|
|
46877
48393
|
selected: types$1.boolean().optional(),
|
|
46878
48394
|
onClick: types$1.function().optional()
|
|
46879
48395
|
});
|
|
46880
|
-
canvasRef = signal(
|
|
48396
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
46881
48397
|
menu = proxy({
|
|
46882
48398
|
isOpen: false,
|
|
46883
48399
|
anchorRect: null,
|
|
@@ -46971,7 +48487,7 @@ var TableStylesPopover = class extends Component {
|
|
|
46971
48487
|
tableStyles: types$1.RecordOf(),
|
|
46972
48488
|
type: types$1.or([types$1.literal("table"), types$1.literal("pivot")])
|
|
46973
48489
|
});
|
|
46974
|
-
tableStyleListRef = signal(
|
|
48490
|
+
tableStyleListRef = signal.ref();
|
|
46975
48491
|
state = proxy({ selectedCategory: this.initialSelectedCategory });
|
|
46976
48492
|
setup() {
|
|
46977
48493
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
@@ -48342,6 +49858,10 @@ sidePanelRegistry.add("ColumnStats", {
|
|
|
48342
49858
|
title: _t("Column statistics"),
|
|
48343
49859
|
Body: ColumnStatsPanel
|
|
48344
49860
|
});
|
|
49861
|
+
sidePanelRegistry.add("DataAnalysisPanel", {
|
|
49862
|
+
title: _t("Data analysis"),
|
|
49863
|
+
Body: DataAnalysisPanel
|
|
49864
|
+
});
|
|
48345
49865
|
sidePanelRegistry.add("TableSidePanel", {
|
|
48346
49866
|
title: _t("Edit table"),
|
|
48347
49867
|
Body: TablePanel,
|
|
@@ -48449,6 +49969,7 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48449
49969
|
"changePanelSize",
|
|
48450
49970
|
"resetPanelSize",
|
|
48451
49971
|
"togglePinPanel",
|
|
49972
|
+
"togglePinnedSidePanel",
|
|
48452
49973
|
"closeMainPanel",
|
|
48453
49974
|
"changeSpreadsheetWidth",
|
|
48454
49975
|
"toggleCollapsePanel"
|
|
@@ -48600,6 +50121,19 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48600
50121
|
this.secondaryPanel = void 0;
|
|
48601
50122
|
}
|
|
48602
50123
|
}
|
|
50124
|
+
togglePinnedSidePanel(componentTag, panelProps = {}) {
|
|
50125
|
+
if (this.mainPanel?.componentTag === componentTag && this.isMainPanelOpen) {
|
|
50126
|
+
this.closeMainPanel();
|
|
50127
|
+
return;
|
|
50128
|
+
}
|
|
50129
|
+
if (this.secondaryPanel?.componentTag === componentTag && this.isSecondaryPanelOpen) {
|
|
50130
|
+
this.secondaryPanel?.currentPanelProps.onCloseSidePanel?.();
|
|
50131
|
+
this.secondaryPanel = void 0;
|
|
50132
|
+
return;
|
|
50133
|
+
}
|
|
50134
|
+
this.open(componentTag, panelProps);
|
|
50135
|
+
if (!this.mainPanel?.isPinned) this.togglePinPanel();
|
|
50136
|
+
}
|
|
48603
50137
|
toggleCollapsePanel(panel) {
|
|
48604
50138
|
const panelInfo = this[panel];
|
|
48605
50139
|
if (!panelInfo) return;
|
|
@@ -48978,6 +50512,219 @@ var MenuItemRegistry = class extends Registry {
|
|
|
48978
50512
|
}
|
|
48979
50513
|
};
|
|
48980
50514
|
|
|
50515
|
+
//#endregion
|
|
50516
|
+
//#region src/helpers/sort_interactive.ts
|
|
50517
|
+
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
50518
|
+
let multiColumns = zone.right > zone.left;
|
|
50519
|
+
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
50520
|
+
multiColumns = false;
|
|
50521
|
+
let table;
|
|
50522
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
50523
|
+
table = [];
|
|
50524
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
50525
|
+
const merge = env.model.getters.getMerge({
|
|
50526
|
+
sheetId,
|
|
50527
|
+
col,
|
|
50528
|
+
row
|
|
50529
|
+
});
|
|
50530
|
+
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
50531
|
+
}
|
|
50532
|
+
if (table.length >= 2) {
|
|
50533
|
+
multiColumns = true;
|
|
50534
|
+
break;
|
|
50535
|
+
}
|
|
50536
|
+
}
|
|
50537
|
+
}
|
|
50538
|
+
if (multiColumns) {
|
|
50539
|
+
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50540
|
+
return;
|
|
50541
|
+
}
|
|
50542
|
+
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
50543
|
+
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50544
|
+
else env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
|
|
50545
|
+
}
|
|
50546
|
+
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
50547
|
+
const result = env.model.dispatch("SORT_CELLS", {
|
|
50548
|
+
sheetId,
|
|
50549
|
+
col: anchor.col,
|
|
50550
|
+
row: anchor.row,
|
|
50551
|
+
zone,
|
|
50552
|
+
sortDirection,
|
|
50553
|
+
sortOptions
|
|
50554
|
+
});
|
|
50555
|
+
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
50556
|
+
const { col, row } = anchor;
|
|
50557
|
+
env.model.selection.selectZone({
|
|
50558
|
+
cell: {
|
|
50559
|
+
col,
|
|
50560
|
+
row
|
|
50561
|
+
},
|
|
50562
|
+
zone
|
|
50563
|
+
});
|
|
50564
|
+
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
50565
|
+
}
|
|
50566
|
+
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
50567
|
+
const { col, row } = anchor;
|
|
50568
|
+
env.model.selection.selectZone({
|
|
50569
|
+
cell: {
|
|
50570
|
+
col,
|
|
50571
|
+
row
|
|
50572
|
+
},
|
|
50573
|
+
zone
|
|
50574
|
+
});
|
|
50575
|
+
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
50576
|
+
}
|
|
50577
|
+
}
|
|
50578
|
+
|
|
50579
|
+
//#endregion
|
|
50580
|
+
//#region src/stores/trim_whitespace_store.ts
|
|
50581
|
+
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
50582
|
+
mutators = ["trimWhitespace"];
|
|
50583
|
+
notificationStore = this.get(NotificationStore);
|
|
50584
|
+
trimWhitespace() {
|
|
50585
|
+
this.model.dispatch("TRIM_WHITESPACE");
|
|
50586
|
+
}
|
|
50587
|
+
handle(cmd) {
|
|
50588
|
+
switch (cmd.type) {
|
|
50589
|
+
case "TRIM_WHITESPACE":
|
|
50590
|
+
this._trimWhitespace();
|
|
50591
|
+
break;
|
|
50592
|
+
}
|
|
50593
|
+
}
|
|
50594
|
+
_trimWhitespace() {
|
|
50595
|
+
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
50596
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
50597
|
+
let count = 0;
|
|
50598
|
+
for (const { col, row } of zones.map(positions).flat()) {
|
|
50599
|
+
const cell = this.getters.getCell({
|
|
50600
|
+
col,
|
|
50601
|
+
row,
|
|
50602
|
+
sheetId
|
|
50603
|
+
});
|
|
50604
|
+
if (!cell) continue;
|
|
50605
|
+
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
50606
|
+
const trimmedContent = trimContent(currentContent);
|
|
50607
|
+
if (trimmedContent !== currentContent) {
|
|
50608
|
+
count += 1;
|
|
50609
|
+
this.model.dispatch("UPDATE_CELL", {
|
|
50610
|
+
sheetId,
|
|
50611
|
+
col,
|
|
50612
|
+
row,
|
|
50613
|
+
content: trimmedContent
|
|
50614
|
+
});
|
|
50615
|
+
}
|
|
50616
|
+
}
|
|
50617
|
+
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
50618
|
+
this.notificationStore.notifyUser({
|
|
50619
|
+
type: "info",
|
|
50620
|
+
text,
|
|
50621
|
+
sticky: false
|
|
50622
|
+
});
|
|
50623
|
+
}
|
|
50624
|
+
};
|
|
50625
|
+
|
|
50626
|
+
//#endregion
|
|
50627
|
+
//#region src/actions/data_actions.ts
|
|
50628
|
+
const sortRange = {
|
|
50629
|
+
name: _t("Sort range"),
|
|
50630
|
+
isVisible: IS_ONLY_ONE_RANGE,
|
|
50631
|
+
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
50632
|
+
};
|
|
50633
|
+
const sortAscending = {
|
|
50634
|
+
name: _t("Ascending (A ⟶ Z)"),
|
|
50635
|
+
execute: (env) => {
|
|
50636
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50637
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
50638
|
+
},
|
|
50639
|
+
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
50640
|
+
};
|
|
50641
|
+
const dataCleanup = {
|
|
50642
|
+
name: _t("Data cleanup"),
|
|
50643
|
+
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
50644
|
+
};
|
|
50645
|
+
const removeDuplicates = {
|
|
50646
|
+
name: _t("Remove duplicates"),
|
|
50647
|
+
execute: (env) => {
|
|
50648
|
+
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
50649
|
+
env.openSidePanel("RemoveDuplicates", {});
|
|
50650
|
+
},
|
|
50651
|
+
isEnabled: (env) => !env.isSmall
|
|
50652
|
+
};
|
|
50653
|
+
const trimWhitespace = {
|
|
50654
|
+
name: _t("Trim whitespace"),
|
|
50655
|
+
execute: (env) => {
|
|
50656
|
+
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
50657
|
+
}
|
|
50658
|
+
};
|
|
50659
|
+
const cleanupDataSources = {
|
|
50660
|
+
name: _t("Remove unused data sources"),
|
|
50661
|
+
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
50662
|
+
isEnabled: (env) => !env.isSmall
|
|
50663
|
+
};
|
|
50664
|
+
const sortDescending = {
|
|
50665
|
+
name: _t("Descending (Z ⟶ A)"),
|
|
50666
|
+
execute: (env) => {
|
|
50667
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50668
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
50669
|
+
},
|
|
50670
|
+
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
50671
|
+
};
|
|
50672
|
+
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
50673
|
+
const createRemoveFilterTool = {
|
|
50674
|
+
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
50675
|
+
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
50676
|
+
};
|
|
50677
|
+
const splitToColumns = {
|
|
50678
|
+
name: _t("Split text to columns"),
|
|
50679
|
+
sequence: 1,
|
|
50680
|
+
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
50681
|
+
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
50682
|
+
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
50683
|
+
};
|
|
50684
|
+
const dataAnalysis = {
|
|
50685
|
+
name: _t("Data analysis"),
|
|
50686
|
+
execute: (env) => {
|
|
50687
|
+
env.getStore(SidePanelStore).togglePinnedSidePanel("DataAnalysisPanel");
|
|
50688
|
+
},
|
|
50689
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS",
|
|
50690
|
+
isEnabled: (env) => !env.isSmall
|
|
50691
|
+
};
|
|
50692
|
+
const columnStatistics = {
|
|
50693
|
+
name: _t("Column statistics"),
|
|
50694
|
+
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
50695
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
50696
|
+
};
|
|
50697
|
+
const reinsertDynamicPivotMenu = {
|
|
50698
|
+
id: "reinsert_dynamic_pivot",
|
|
50699
|
+
name: _t("Re-insert dynamic pivot"),
|
|
50700
|
+
sequence: 60,
|
|
50701
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50702
|
+
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
50703
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50704
|
+
};
|
|
50705
|
+
const reinsertStaticPivotMenu = {
|
|
50706
|
+
id: "reinsert_static_pivot",
|
|
50707
|
+
name: _t("Re-insert static pivot"),
|
|
50708
|
+
sequence: 70,
|
|
50709
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50710
|
+
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
50711
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50712
|
+
};
|
|
50713
|
+
const calculationMode = {
|
|
50714
|
+
name: _t("Calculation"),
|
|
50715
|
+
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
50716
|
+
};
|
|
50717
|
+
const automaticCalculation = {
|
|
50718
|
+
name: _t("Automatic"),
|
|
50719
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
50720
|
+
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
50721
|
+
};
|
|
50722
|
+
const manualCalculation = {
|
|
50723
|
+
name: _t("Manual"),
|
|
50724
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
50725
|
+
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
50726
|
+
};
|
|
50727
|
+
|
|
48981
50728
|
//#endregion
|
|
48982
50729
|
//#region src/helpers/ui/merge_interactive.ts
|
|
48983
50730
|
const AddMergeInteractiveContent = {
|
|
@@ -49192,6 +50939,10 @@ cellMenuRegistry.add("cut", {
|
|
|
49192
50939
|
}).addChild("paste_format_only", ["paste_special"], {
|
|
49193
50940
|
...pasteSpecialFormat,
|
|
49194
50941
|
sequence: 20
|
|
50942
|
+
}).add("data_analysis", {
|
|
50943
|
+
...dataAnalysis,
|
|
50944
|
+
sequence: 50,
|
|
50945
|
+
separator: true
|
|
49195
50946
|
}).add("add_row_before", {
|
|
49196
50947
|
...cellInsertRowsBefore,
|
|
49197
50948
|
sequence: 70
|
|
@@ -49299,211 +51050,6 @@ cellMenuRegistry.add("cut", {
|
|
|
49299
51050
|
sequence: 30
|
|
49300
51051
|
});
|
|
49301
51052
|
|
|
49302
|
-
//#endregion
|
|
49303
|
-
//#region src/helpers/sort_interactive.ts
|
|
49304
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
49305
|
-
let multiColumns = zone.right > zone.left;
|
|
49306
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
49307
|
-
multiColumns = false;
|
|
49308
|
-
let table;
|
|
49309
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
49310
|
-
table = [];
|
|
49311
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
49312
|
-
const merge = env.model.getters.getMerge({
|
|
49313
|
-
sheetId,
|
|
49314
|
-
col,
|
|
49315
|
-
row
|
|
49316
|
-
});
|
|
49317
|
-
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
49318
|
-
}
|
|
49319
|
-
if (table.length >= 2) {
|
|
49320
|
-
multiColumns = true;
|
|
49321
|
-
break;
|
|
49322
|
-
}
|
|
49323
|
-
}
|
|
49324
|
-
}
|
|
49325
|
-
if (multiColumns) {
|
|
49326
|
-
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49327
|
-
return;
|
|
49328
|
-
}
|
|
49329
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
49330
|
-
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49331
|
-
else env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
|
|
49332
|
-
}
|
|
49333
|
-
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
49334
|
-
const result = env.model.dispatch("SORT_CELLS", {
|
|
49335
|
-
sheetId,
|
|
49336
|
-
col: anchor.col,
|
|
49337
|
-
row: anchor.row,
|
|
49338
|
-
zone,
|
|
49339
|
-
sortDirection,
|
|
49340
|
-
sortOptions
|
|
49341
|
-
});
|
|
49342
|
-
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
49343
|
-
const { col, row } = anchor;
|
|
49344
|
-
env.model.selection.selectZone({
|
|
49345
|
-
cell: {
|
|
49346
|
-
col,
|
|
49347
|
-
row
|
|
49348
|
-
},
|
|
49349
|
-
zone
|
|
49350
|
-
});
|
|
49351
|
-
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
49352
|
-
}
|
|
49353
|
-
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
49354
|
-
const { col, row } = anchor;
|
|
49355
|
-
env.model.selection.selectZone({
|
|
49356
|
-
cell: {
|
|
49357
|
-
col,
|
|
49358
|
-
row
|
|
49359
|
-
},
|
|
49360
|
-
zone
|
|
49361
|
-
});
|
|
49362
|
-
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
49363
|
-
}
|
|
49364
|
-
}
|
|
49365
|
-
|
|
49366
|
-
//#endregion
|
|
49367
|
-
//#region src/stores/trim_whitespace_store.ts
|
|
49368
|
-
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
49369
|
-
mutators = ["trimWhitespace"];
|
|
49370
|
-
notificationStore = this.get(NotificationStore);
|
|
49371
|
-
trimWhitespace() {
|
|
49372
|
-
this.model.dispatch("TRIM_WHITESPACE");
|
|
49373
|
-
}
|
|
49374
|
-
handle(cmd) {
|
|
49375
|
-
switch (cmd.type) {
|
|
49376
|
-
case "TRIM_WHITESPACE":
|
|
49377
|
-
this._trimWhitespace();
|
|
49378
|
-
break;
|
|
49379
|
-
}
|
|
49380
|
-
}
|
|
49381
|
-
_trimWhitespace() {
|
|
49382
|
-
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
49383
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
49384
|
-
let count = 0;
|
|
49385
|
-
for (const { col, row } of zones.map(positions).flat()) {
|
|
49386
|
-
const cell = this.getters.getCell({
|
|
49387
|
-
col,
|
|
49388
|
-
row,
|
|
49389
|
-
sheetId
|
|
49390
|
-
});
|
|
49391
|
-
if (!cell) continue;
|
|
49392
|
-
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
49393
|
-
const trimmedContent = trimContent(currentContent);
|
|
49394
|
-
if (trimmedContent !== currentContent) {
|
|
49395
|
-
count += 1;
|
|
49396
|
-
this.model.dispatch("UPDATE_CELL", {
|
|
49397
|
-
sheetId,
|
|
49398
|
-
col,
|
|
49399
|
-
row,
|
|
49400
|
-
content: trimmedContent
|
|
49401
|
-
});
|
|
49402
|
-
}
|
|
49403
|
-
}
|
|
49404
|
-
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
49405
|
-
this.notificationStore.notifyUser({
|
|
49406
|
-
type: "info",
|
|
49407
|
-
text,
|
|
49408
|
-
sticky: false
|
|
49409
|
-
});
|
|
49410
|
-
}
|
|
49411
|
-
};
|
|
49412
|
-
|
|
49413
|
-
//#endregion
|
|
49414
|
-
//#region src/actions/data_actions.ts
|
|
49415
|
-
const sortRange = {
|
|
49416
|
-
name: _t("Sort range"),
|
|
49417
|
-
isVisible: IS_ONLY_ONE_RANGE,
|
|
49418
|
-
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
49419
|
-
};
|
|
49420
|
-
const sortAscending = {
|
|
49421
|
-
name: _t("Ascending (A ⟶ Z)"),
|
|
49422
|
-
execute: (env) => {
|
|
49423
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49424
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
49425
|
-
},
|
|
49426
|
-
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
49427
|
-
};
|
|
49428
|
-
const dataCleanup = {
|
|
49429
|
-
name: _t("Data cleanup"),
|
|
49430
|
-
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
49431
|
-
};
|
|
49432
|
-
const removeDuplicates = {
|
|
49433
|
-
name: _t("Remove duplicates"),
|
|
49434
|
-
execute: (env) => {
|
|
49435
|
-
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
49436
|
-
env.openSidePanel("RemoveDuplicates", {});
|
|
49437
|
-
},
|
|
49438
|
-
isEnabled: (env) => !env.isSmall
|
|
49439
|
-
};
|
|
49440
|
-
const trimWhitespace = {
|
|
49441
|
-
name: _t("Trim whitespace"),
|
|
49442
|
-
execute: (env) => {
|
|
49443
|
-
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
49444
|
-
}
|
|
49445
|
-
};
|
|
49446
|
-
const cleanupDataSources = {
|
|
49447
|
-
name: _t("Remove unused data sources"),
|
|
49448
|
-
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
49449
|
-
isEnabled: (env) => !env.isSmall
|
|
49450
|
-
};
|
|
49451
|
-
const sortDescending = {
|
|
49452
|
-
name: _t("Descending (Z ⟶ A)"),
|
|
49453
|
-
execute: (env) => {
|
|
49454
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49455
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
49456
|
-
},
|
|
49457
|
-
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
49458
|
-
};
|
|
49459
|
-
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
49460
|
-
const createRemoveFilterTool = {
|
|
49461
|
-
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
49462
|
-
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
49463
|
-
};
|
|
49464
|
-
const splitToColumns = {
|
|
49465
|
-
name: _t("Split text to columns"),
|
|
49466
|
-
sequence: 1,
|
|
49467
|
-
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
49468
|
-
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
49469
|
-
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
49470
|
-
};
|
|
49471
|
-
const columnStatistics = {
|
|
49472
|
-
name: _t("Column statistics"),
|
|
49473
|
-
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
49474
|
-
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
49475
|
-
};
|
|
49476
|
-
const reinsertDynamicPivotMenu = {
|
|
49477
|
-
id: "reinsert_dynamic_pivot",
|
|
49478
|
-
name: _t("Re-insert dynamic pivot"),
|
|
49479
|
-
sequence: 60,
|
|
49480
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49481
|
-
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
49482
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49483
|
-
};
|
|
49484
|
-
const reinsertStaticPivotMenu = {
|
|
49485
|
-
id: "reinsert_static_pivot",
|
|
49486
|
-
name: _t("Re-insert static pivot"),
|
|
49487
|
-
sequence: 70,
|
|
49488
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49489
|
-
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
49490
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49491
|
-
};
|
|
49492
|
-
const calculationMode = {
|
|
49493
|
-
name: _t("Calculation"),
|
|
49494
|
-
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
49495
|
-
};
|
|
49496
|
-
const automaticCalculation = {
|
|
49497
|
-
name: _t("Automatic"),
|
|
49498
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
49499
|
-
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
49500
|
-
};
|
|
49501
|
-
const manualCalculation = {
|
|
49502
|
-
name: _t("Manual"),
|
|
49503
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
49504
|
-
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
49505
|
-
};
|
|
49506
|
-
|
|
49507
51053
|
//#endregion
|
|
49508
51054
|
//#region src/registries/menus/col_menu_registry.ts
|
|
49509
51055
|
const colMenuRegistry = new MenuItemRegistry();
|
|
@@ -49537,6 +51083,9 @@ colMenuRegistry.add("cut", {
|
|
|
49537
51083
|
}).addChild("sort_descending", ["sort_columns"], {
|
|
49538
51084
|
...sortDescending,
|
|
49539
51085
|
sequence: 20
|
|
51086
|
+
}).add("data_analysis", {
|
|
51087
|
+
...dataAnalysis,
|
|
51088
|
+
sequence: 55
|
|
49540
51089
|
}).add("column_statistics", {
|
|
49541
51090
|
...columnStatistics,
|
|
49542
51091
|
sequence: 60,
|
|
@@ -52352,14 +53901,15 @@ var FiguresContainer = class extends Component {
|
|
|
52352
53901
|
selectedRect: void 0,
|
|
52353
53902
|
horizontalSnap: void 0,
|
|
52354
53903
|
verticalSnap: void 0,
|
|
52355
|
-
cancelDnd: void 0
|
|
52356
|
-
overlappingChartOrCarousel: void 0
|
|
53904
|
+
cancelDnd: void 0
|
|
52357
53905
|
});
|
|
52358
53906
|
viewStore;
|
|
52359
53907
|
zoomStore;
|
|
53908
|
+
chartDragStore;
|
|
52360
53909
|
setup() {
|
|
52361
53910
|
this.viewStore = useStore(ViewportsStore);
|
|
52362
53911
|
this.zoomStore = useStore(ZoomStore);
|
|
53912
|
+
this.chartDragStore = useStore(ChartDragStore);
|
|
52363
53913
|
onMounted(() => {
|
|
52364
53914
|
this.render();
|
|
52365
53915
|
});
|
|
@@ -52373,7 +53923,7 @@ var FiguresContainer = class extends Component {
|
|
|
52373
53923
|
this.dnd.selectedRect = void 0;
|
|
52374
53924
|
this.dnd.horizontalSnap = void 0;
|
|
52375
53925
|
this.dnd.verticalSnap = void 0;
|
|
52376
|
-
this.
|
|
53926
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52377
53927
|
this.dnd.cancelDnd = void 0;
|
|
52378
53928
|
}
|
|
52379
53929
|
});
|
|
@@ -52499,6 +54049,7 @@ var FiguresContainer = class extends Component {
|
|
|
52499
54049
|
maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
|
|
52500
54050
|
};
|
|
52501
54051
|
let hasStartedDnd = false;
|
|
54052
|
+
let overlappingChartOrCarousel = void 0;
|
|
52502
54053
|
const onMouseMove = (ev) => {
|
|
52503
54054
|
const currentMousePosition = {
|
|
52504
54055
|
x: ev.clientX / zoom,
|
|
@@ -52510,10 +54061,10 @@ var FiguresContainer = class extends Component {
|
|
|
52510
54061
|
hasStartedDnd = true;
|
|
52511
54062
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
|
|
52512
54063
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
52513
|
-
|
|
54064
|
+
overlappingChartOrCarousel = void 0;
|
|
52514
54065
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
52515
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel =
|
|
52516
|
-
this.
|
|
54066
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
|
|
54067
|
+
this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
|
|
52517
54068
|
if (!overlappingChartOrCarousel) {
|
|
52518
54069
|
const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
|
|
52519
54070
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
@@ -52535,7 +54086,7 @@ var FiguresContainer = class extends Component {
|
|
|
52535
54086
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
52536
54087
|
return;
|
|
52537
54088
|
}
|
|
52538
|
-
if (!
|
|
54089
|
+
if (!overlappingChartOrCarousel) {
|
|
52539
54090
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
52540
54091
|
return {
|
|
52541
54092
|
sheetId,
|
|
@@ -52545,14 +54096,14 @@ var FiguresContainer = class extends Component {
|
|
|
52545
54096
|
}) || [];
|
|
52546
54097
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
52547
54098
|
} else {
|
|
52548
|
-
const overlappingFigureId =
|
|
54099
|
+
const overlappingFigureId = overlappingChartOrCarousel.id;
|
|
52549
54100
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
52550
|
-
if (
|
|
54101
|
+
if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
52551
54102
|
sheetId,
|
|
52552
54103
|
carouselFigureId: overlappingFigureId,
|
|
52553
54104
|
chartFigureIds
|
|
52554
54105
|
});
|
|
52555
|
-
else if (
|
|
54106
|
+
else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
52556
54107
|
sheetId,
|
|
52557
54108
|
baseFigureId: overlappingFigureId,
|
|
52558
54109
|
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
@@ -52563,7 +54114,7 @@ var FiguresContainer = class extends Component {
|
|
|
52563
54114
|
this.dnd.selectedRect = void 0;
|
|
52564
54115
|
this.dnd.horizontalSnap = void 0;
|
|
52565
54116
|
this.dnd.verticalSnap = void 0;
|
|
52566
|
-
this.
|
|
54117
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52567
54118
|
};
|
|
52568
54119
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52569
54120
|
}
|
|
@@ -52619,7 +54170,6 @@ var FiguresContainer = class extends Component {
|
|
|
52619
54170
|
this.dnd.selectedRect = void 0;
|
|
52620
54171
|
this.dnd.horizontalSnap = void 0;
|
|
52621
54172
|
this.dnd.verticalSnap = void 0;
|
|
52622
|
-
this.dnd.overlappingChartOrCarousel = void 0;
|
|
52623
54173
|
};
|
|
52624
54174
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52625
54175
|
}
|
|
@@ -52629,12 +54179,12 @@ var FiguresContainer = class extends Component {
|
|
|
52629
54179
|
getFigureStyle(figureUI) {
|
|
52630
54180
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
52631
54181
|
return cssPropertiesToCss({
|
|
52632
|
-
opacity: this.
|
|
54182
|
+
opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
|
|
52633
54183
|
cursor: "grabbing"
|
|
52634
54184
|
});
|
|
52635
54185
|
}
|
|
52636
54186
|
getFigureClass(figureUI) {
|
|
52637
|
-
if (figureUI.id !== this.
|
|
54187
|
+
if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
|
|
52638
54188
|
return "o-add-to-carousel";
|
|
52639
54189
|
}
|
|
52640
54190
|
getSnap(snapLine) {
|
|
@@ -52677,26 +54227,6 @@ var FiguresContainer = class extends Component {
|
|
|
52677
54227
|
height: `100%`
|
|
52678
54228
|
});
|
|
52679
54229
|
}
|
|
52680
|
-
getOverlappingFigure(figureUI, otherFigures) {
|
|
52681
|
-
if (figureUI.tag !== "chart") return;
|
|
52682
|
-
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
52683
|
-
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
52684
|
-
let bestMatch;
|
|
52685
|
-
let smallestDistance = Infinity;
|
|
52686
|
-
for (const figure of otherFigures) {
|
|
52687
|
-
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
52688
|
-
const targetCenterX = figure.x + figure.width / 2;
|
|
52689
|
-
const targetCenterY = figure.y + figure.height / 2;
|
|
52690
|
-
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
52691
|
-
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
52692
|
-
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
52693
|
-
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
52694
|
-
smallestDistance = squaredDistance;
|
|
52695
|
-
bestMatch = figure;
|
|
52696
|
-
}
|
|
52697
|
-
}
|
|
52698
|
-
return bestMatch;
|
|
52699
|
-
}
|
|
52700
54230
|
};
|
|
52701
54231
|
|
|
52702
54232
|
//#endregion
|
|
@@ -52996,7 +54526,7 @@ var AbstractResizer = class extends Component {
|
|
|
52996
54526
|
var ColResizer = class extends AbstractResizer {
|
|
52997
54527
|
static template = "o-spreadsheet-ColResizer";
|
|
52998
54528
|
static components = { UnhideColumnHeaders };
|
|
52999
|
-
colResizerRef = signal(
|
|
54529
|
+
colResizerRef = signal.ref();
|
|
53000
54530
|
setup() {
|
|
53001
54531
|
super.setup();
|
|
53002
54532
|
this.PADDING = 15;
|
|
@@ -53130,7 +54660,7 @@ var ColResizer = class extends AbstractResizer {
|
|
|
53130
54660
|
var RowResizer = class extends AbstractResizer {
|
|
53131
54661
|
static template = "o-spreadsheet-RowResizer";
|
|
53132
54662
|
static components = { UnhideRowHeaders };
|
|
53133
|
-
rowResizerRef = signal(
|
|
54663
|
+
rowResizerRef = signal.ref();
|
|
53134
54664
|
setup() {
|
|
53135
54665
|
super.setup();
|
|
53136
54666
|
this.PADDING = 5;
|
|
@@ -53739,9 +55269,8 @@ var TableResizer = class extends Component {
|
|
|
53739
55269
|
useStore(TableResizeStore);
|
|
53740
55270
|
}
|
|
53741
55271
|
get containerStyle() {
|
|
53742
|
-
const tableZone = this.props.table.range.zone;
|
|
53743
55272
|
const sheetId = this.props.table.range.sheetId;
|
|
53744
|
-
|
|
55273
|
+
const tableZone = this.props.table.range.zone;
|
|
53745
55274
|
const bottomRight = {
|
|
53746
55275
|
...tableZone,
|
|
53747
55276
|
left: tableZone.right,
|
|
@@ -53828,8 +55357,8 @@ var Grid = class extends Component {
|
|
|
53828
55357
|
HEADER_HEIGHT = 26;
|
|
53829
55358
|
HEADER_WIDTH = 48;
|
|
53830
55359
|
menuState;
|
|
53831
|
-
gridRef = signal(
|
|
53832
|
-
canvasRef = signal(
|
|
55360
|
+
gridRef = signal.ref();
|
|
55361
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
53833
55362
|
highlightStore;
|
|
53834
55363
|
viewStore;
|
|
53835
55364
|
zoomStore;
|
|
@@ -54460,6 +55989,9 @@ var Grid = class extends Component {
|
|
|
54460
55989
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
54461
55990
|
return this.env.model.getters.getClientsToDisplay(sheetId);
|
|
54462
55991
|
}
|
|
55992
|
+
get displayTableResizer() {
|
|
55993
|
+
return this.env.model.getters.isGridSelectionActive() && !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
55994
|
+
}
|
|
54463
55995
|
};
|
|
54464
55996
|
|
|
54465
55997
|
//#endregion
|
|
@@ -54478,7 +56010,7 @@ var FullScreenFigure = class extends Component {
|
|
|
54478
56010
|
static template = "o-spreadsheet-FullScreenFigure";
|
|
54479
56011
|
static components = { ChartFigure };
|
|
54480
56012
|
fullScreenFigureStore;
|
|
54481
|
-
fullScreenFigureRef = signal(
|
|
56013
|
+
fullScreenFigureRef = signal.ref();
|
|
54482
56014
|
spreadsheetRect = useSpreadsheetRect();
|
|
54483
56015
|
figureRegistry = figureRegistry;
|
|
54484
56016
|
setup() {
|
|
@@ -54726,6 +56258,41 @@ var PivotHTMLRenderer = class extends Component {
|
|
|
54726
56258
|
}
|
|
54727
56259
|
};
|
|
54728
56260
|
|
|
56261
|
+
//#endregion
|
|
56262
|
+
//#region src/components/tables/hovered_table_store.ts
|
|
56263
|
+
var HoveredTableStore = class extends SpreadsheetStore {
|
|
56264
|
+
mutators = ["hover"];
|
|
56265
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
56266
|
+
position;
|
|
56267
|
+
constructor(get) {
|
|
56268
|
+
super(get);
|
|
56269
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
56270
|
+
cellHoverOverlayStore.register(this);
|
|
56271
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
56272
|
+
}
|
|
56273
|
+
getHighlightedPositions(hoveredPosition) {
|
|
56274
|
+
const highlightedPositions = [];
|
|
56275
|
+
const { sheetId, row } = hoveredPosition;
|
|
56276
|
+
const table = this.getters.getTable(hoveredPosition);
|
|
56277
|
+
if (!table) return highlightedPositions;
|
|
56278
|
+
const { left, right, top } = table.range.zone;
|
|
56279
|
+
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
56280
|
+
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
56281
|
+
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
56282
|
+
sheetId,
|
|
56283
|
+
col,
|
|
56284
|
+
row
|
|
56285
|
+
}).formattedValue;
|
|
56286
|
+
});
|
|
56287
|
+
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) highlightedPositions.push({
|
|
56288
|
+
sheetId,
|
|
56289
|
+
col,
|
|
56290
|
+
row
|
|
56291
|
+
});
|
|
56292
|
+
return highlightedPositions;
|
|
56293
|
+
}
|
|
56294
|
+
};
|
|
56295
|
+
|
|
54729
56296
|
//#endregion
|
|
54730
56297
|
//#region src/helpers/figures/charts/runtime/helpers_index.ts
|
|
54731
56298
|
var helpers_index_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -67828,6 +69395,12 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
67828
69395
|
const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
|
|
67829
69396
|
const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
67830
69397
|
if (figures.some((f) => f === void 0 || f.tag !== "chart") || !baseFigureId || baseFigureId.tag !== "chart") return "FigureDoesNotExist";
|
|
69398
|
+
break;
|
|
69399
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69400
|
+
const baseFigure = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69401
|
+
if (this.getters.getFigure(cmd.sheetId, cmd.figureId) || !baseFigure) return "InvalidFigureId";
|
|
69402
|
+
if (baseFigure.tag !== "chart") return "FigureDoesNotExist";
|
|
69403
|
+
break;
|
|
67831
69404
|
}
|
|
67832
69405
|
return "Success";
|
|
67833
69406
|
}
|
|
@@ -67863,6 +69436,29 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
67863
69436
|
carouselFigureId,
|
|
67864
69437
|
chartFigureIds: cmd.chartFigureIds
|
|
67865
69438
|
});
|
|
69439
|
+
break;
|
|
69440
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69441
|
+
const baseFigureToMerge = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69442
|
+
if (!baseFigureToMerge) throw new Error(`Figure ${cmd.baseFigureId} does not exists.`);
|
|
69443
|
+
this.dispatch("CREATE_CHART", {
|
|
69444
|
+
chartId: cmd.chartId,
|
|
69445
|
+
figureId: cmd.figureId,
|
|
69446
|
+
sheetId: cmd.sheetId,
|
|
69447
|
+
definition: cmd.definition,
|
|
69448
|
+
col: baseFigureToMerge.col,
|
|
69449
|
+
row: baseFigureToMerge.row,
|
|
69450
|
+
offset: baseFigureToMerge.offset,
|
|
69451
|
+
size: {
|
|
69452
|
+
width: baseFigureToMerge.width,
|
|
69453
|
+
height: baseFigureToMerge.height
|
|
69454
|
+
}
|
|
69455
|
+
});
|
|
69456
|
+
this.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
69457
|
+
sheetId: cmd.sheetId,
|
|
69458
|
+
baseFigureId: cmd.baseFigureId,
|
|
69459
|
+
chartFigureIds: [cmd.baseFigureId, cmd.figureId]
|
|
69460
|
+
});
|
|
69461
|
+
break;
|
|
67866
69462
|
}
|
|
67867
69463
|
}
|
|
67868
69464
|
getFigureUI(sheetId, figure) {
|
|
@@ -69552,6 +71148,9 @@ topbarMenuRegistry.add("file", {
|
|
|
69552
71148
|
}).addChild("split_to_columns", ["data"], {
|
|
69553
71149
|
...splitToColumns,
|
|
69554
71150
|
sequence: 20
|
|
71151
|
+
}).addChild("data_analysis", ["data"], {
|
|
71152
|
+
...dataAnalysis,
|
|
71153
|
+
sequence: 24
|
|
69555
71154
|
}).addChild("column_statistics", ["data"], {
|
|
69556
71155
|
...columnStatistics,
|
|
69557
71156
|
sequence: 25
|
|
@@ -69953,7 +71552,7 @@ var RippleEffect = class extends Component {
|
|
|
69953
71552
|
onAnimationEnd: types$1.function(),
|
|
69954
71553
|
style: types$1.string()
|
|
69955
71554
|
});
|
|
69956
|
-
rippleRef = signal(
|
|
71555
|
+
rippleRef = signal.ref();
|
|
69957
71556
|
setup() {
|
|
69958
71557
|
let animation = void 0;
|
|
69959
71558
|
onMounted(() => {
|
|
@@ -70007,7 +71606,7 @@ var Ripple = class extends Component {
|
|
|
70007
71606
|
onAnimationEnd: types$1.function().optional(() => () => {}),
|
|
70008
71607
|
class: types$1.string().optional("")
|
|
70009
71608
|
});
|
|
70010
|
-
childContainerRef = signal(
|
|
71609
|
+
childContainerRef = signal.ref();
|
|
70011
71610
|
state = proxy({ ripples: [] });
|
|
70012
71611
|
currentId = 1;
|
|
70013
71612
|
onClick(ev) {
|
|
@@ -70278,9 +71877,9 @@ var BottomBarSheet = class extends Component {
|
|
|
70278
71877
|
isEditing: false,
|
|
70279
71878
|
openedPicker: void 0
|
|
70280
71879
|
});
|
|
70281
|
-
sheetDivRef = signal(
|
|
70282
|
-
iconRef = signal(
|
|
70283
|
-
sheetNameRef = signal(
|
|
71880
|
+
sheetDivRef = signal.ref();
|
|
71881
|
+
iconRef = signal.ref();
|
|
71882
|
+
sheetNameRef = signal.ref();
|
|
70284
71883
|
editionState = "initializing";
|
|
70285
71884
|
DOMFocusableElementStore;
|
|
70286
71885
|
setup() {
|
|
@@ -70564,8 +72163,10 @@ var BottomBarStatistic = class extends Component {
|
|
|
70564
72163
|
});
|
|
70565
72164
|
state = proxy({ selectedStatisticFn: "" });
|
|
70566
72165
|
store;
|
|
72166
|
+
sidePanelStore;
|
|
70567
72167
|
setup() {
|
|
70568
72168
|
this.store = useStore(AggregateStatisticsStore);
|
|
72169
|
+
this.sidePanelStore = useStore(SidePanelStore);
|
|
70569
72170
|
onWillUpdateProps(() => {
|
|
70570
72171
|
if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
|
|
70571
72172
|
});
|
|
@@ -70605,6 +72206,12 @@ var BottomBarStatistic = class extends Component {
|
|
|
70605
72206
|
format: fnValue.format()
|
|
70606
72207
|
});
|
|
70607
72208
|
}
|
|
72209
|
+
get isDataAnalysisOpen() {
|
|
72210
|
+
return this.sidePanelStore.mainPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isMainPanelOpen || this.sidePanelStore.secondaryPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isSecondaryPanelOpen;
|
|
72211
|
+
}
|
|
72212
|
+
toggleDataAnalysisPanel() {
|
|
72213
|
+
this.sidePanelStore.togglePinnedSidePanel("DataAnalysisPanel");
|
|
72214
|
+
}
|
|
70608
72215
|
};
|
|
70609
72216
|
|
|
70610
72217
|
//#endregion
|
|
@@ -70619,8 +72226,8 @@ var BottomBar = class extends Component {
|
|
|
70619
72226
|
BottomBarSheet,
|
|
70620
72227
|
BottomBarStatistic
|
|
70621
72228
|
};
|
|
70622
|
-
bottomBarRef = signal(
|
|
70623
|
-
sheetListRef = signal(
|
|
72229
|
+
bottomBarRef = signal.ref();
|
|
72230
|
+
sheetListRef = signal.ref();
|
|
70624
72231
|
dragAndDrop = useDragAndDropListItems();
|
|
70625
72232
|
targetScroll = void 0;
|
|
70626
72233
|
state = proxy({
|
|
@@ -70833,12 +72440,13 @@ var SpreadsheetDashboard = class extends Component {
|
|
|
70833
72440
|
hoveredCell;
|
|
70834
72441
|
viewStore;
|
|
70835
72442
|
zoomStore;
|
|
70836
|
-
gridRef = signal(
|
|
70837
|
-
canvasRef = signal(
|
|
72443
|
+
gridRef = signal.ref();
|
|
72444
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
70838
72445
|
setup() {
|
|
70839
72446
|
this.hoveredCell = useStore(DelayedHoveredCellStore);
|
|
70840
72447
|
this.viewStore = useStore(ViewportsStore);
|
|
70841
72448
|
this.zoomStore = useStore(ZoomStore);
|
|
72449
|
+
useLocalStore(HoveredTableStore);
|
|
70842
72450
|
const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
|
|
70843
72451
|
useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
|
|
70844
72452
|
useGridDrawing({
|
|
@@ -71256,8 +72864,8 @@ var RibbonMenu = class extends Component {
|
|
|
71256
72864
|
static components = { Menu };
|
|
71257
72865
|
props = useProps({ onClose: types$1.function() });
|
|
71258
72866
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
71259
|
-
menuRef = signal(
|
|
71260
|
-
containerRef = signal(
|
|
72867
|
+
menuRef = signal.ref();
|
|
72868
|
+
containerRef = signal.ref();
|
|
71261
72869
|
state = proxy({
|
|
71262
72870
|
menuItems: this.rootItems,
|
|
71263
72871
|
title: _t("Menu Bar"),
|
|
@@ -71330,7 +72938,7 @@ var SmallBottomBar = class extends Component {
|
|
|
71330
72938
|
composerStore;
|
|
71331
72939
|
viewStore;
|
|
71332
72940
|
composerInterface;
|
|
71333
|
-
composerRef = signal(
|
|
72941
|
+
composerRef = signal.ref();
|
|
71334
72942
|
menuState = proxy({ isOpen: false });
|
|
71335
72943
|
setup() {
|
|
71336
72944
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -71409,6 +73017,49 @@ var SmallBottomBar = class extends Component {
|
|
|
71409
73017
|
}
|
|
71410
73018
|
};
|
|
71411
73019
|
|
|
73020
|
+
//#endregion
|
|
73021
|
+
//#region src/components/standalone_grid_canvas/carousel_data_view_print.ts
|
|
73022
|
+
var CarouselDataViewPrint = class extends Component {
|
|
73023
|
+
static template = xml``;
|
|
73024
|
+
props = useProps({
|
|
73025
|
+
range: types$1.Range(),
|
|
73026
|
+
columnWeights: types$1.array().optional(),
|
|
73027
|
+
rect: types$1.object(),
|
|
73028
|
+
rendererStore: types$1.object()
|
|
73029
|
+
});
|
|
73030
|
+
viewportsStore;
|
|
73031
|
+
gridRender;
|
|
73032
|
+
setup() {
|
|
73033
|
+
useChildStoreProvider([ViewportsStore]);
|
|
73034
|
+
const store = useLocalStore(StandaloneViewportStore, this.props.range, this.props.columnWeights);
|
|
73035
|
+
this.gridRender = useLocalStore(GridRenderer, null);
|
|
73036
|
+
this.viewportsStore = useStore(ViewportsStore);
|
|
73037
|
+
useEffect(() => {
|
|
73038
|
+
store.setContainerSize(this.props.rect.width, this.props.rect.height);
|
|
73039
|
+
});
|
|
73040
|
+
onMounted(() => this.props.rendererStore.register(this));
|
|
73041
|
+
onWillUnmount(() => this.props.rendererStore.unRegister(this));
|
|
73042
|
+
}
|
|
73043
|
+
get renderingLayers() {
|
|
73044
|
+
return ["Chart"];
|
|
73045
|
+
}
|
|
73046
|
+
drawLayer(renderingContext) {
|
|
73047
|
+
const { ctx } = renderingContext;
|
|
73048
|
+
ctx.save();
|
|
73049
|
+
ctx.translate(this.props.rect.x, this.props.rect.y);
|
|
73050
|
+
ctx.beginPath();
|
|
73051
|
+
ctx.rect(0, 0, this.props.rect.width, this.props.rect.height);
|
|
73052
|
+
ctx.clip();
|
|
73053
|
+
const subRenderingCtx = {
|
|
73054
|
+
...renderingContext,
|
|
73055
|
+
sheetId: this.viewportsStore.displayedSheetId,
|
|
73056
|
+
viewports: this.viewportsStore.viewports
|
|
73057
|
+
};
|
|
73058
|
+
this.gridRender.drawLayer(subRenderingCtx, "Background", void 0);
|
|
73059
|
+
ctx.restore();
|
|
73060
|
+
}
|
|
73061
|
+
};
|
|
73062
|
+
|
|
71412
73063
|
//#endregion
|
|
71413
73064
|
//#region src/components/standalone_grid_canvas/figure_renderer_store.ts
|
|
71414
73065
|
/** Store that draws the figures directly onto the canvas */
|
|
@@ -71434,29 +73085,25 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71434
73085
|
drawLayer(renderingCtx) {
|
|
71435
73086
|
const { viewports, ctx } = renderingCtx;
|
|
71436
73087
|
const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
|
|
71437
|
-
const
|
|
73088
|
+
const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
|
|
71438
73089
|
for (const figure of visibleFigures) {
|
|
71439
|
-
const
|
|
71440
|
-
|
|
73090
|
+
const figureRect = {
|
|
73091
|
+
...figure,
|
|
73092
|
+
x: figure.x - scrollOffset.x,
|
|
73093
|
+
y: figure.y - scrollOffset.y
|
|
73094
|
+
};
|
|
73095
|
+
const { x, y, width, height } = figureRect;
|
|
71441
73096
|
if (figure.tag === "chart") {
|
|
71442
73097
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71443
|
-
if (chartId)
|
|
71444
|
-
const chartRect = {
|
|
71445
|
-
x,
|
|
71446
|
-
y,
|
|
71447
|
-
width: figure.width,
|
|
71448
|
-
height: figure.height
|
|
71449
|
-
};
|
|
71450
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71451
|
-
}
|
|
73098
|
+
if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
|
|
71452
73099
|
} else if (figure.tag === "image") {
|
|
71453
73100
|
const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
|
|
71454
|
-
if (loadedImage) ctx.drawImage(loadedImage, x, y,
|
|
71455
|
-
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure);
|
|
73101
|
+
if (loadedImage) ctx.drawImage(loadedImage, x, y, width, height);
|
|
73102
|
+
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure, figureRect);
|
|
71456
73103
|
if (!this.getters.isDashboard()) {
|
|
71457
73104
|
ctx.strokeStyle = GRAY_400;
|
|
71458
73105
|
ctx.lineWidth = 1;
|
|
71459
|
-
ctx.strokeRect(x, y,
|
|
73106
|
+
ctx.strokeRect(x, y, width, height);
|
|
71460
73107
|
}
|
|
71461
73108
|
}
|
|
71462
73109
|
}
|
|
@@ -71474,53 +73121,37 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71474
73121
|
renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
|
|
71475
73122
|
cleanUp();
|
|
71476
73123
|
}
|
|
71477
|
-
drawCarousel(renderingCtx, figure) {
|
|
71478
|
-
const {
|
|
71479
|
-
const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
|
|
71480
|
-
const x = figure.x - offsetX;
|
|
71481
|
-
const y = figure.y - offsetY;
|
|
73124
|
+
drawCarousel(renderingCtx, figure, figureRect) {
|
|
73125
|
+
const { ctx } = renderingCtx;
|
|
71482
73126
|
const carousel = this.getters.getCarousel(figure.id);
|
|
71483
73127
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71484
73128
|
if (!carousel) return;
|
|
73129
|
+
const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
|
|
71485
73130
|
const chartDefinition = chartId ? this.getters.getChartDefinition(chartId) : void 0;
|
|
73131
|
+
ctx.save();
|
|
73132
|
+
ctx.beginPath();
|
|
73133
|
+
ctx.rect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
73134
|
+
ctx.clip();
|
|
73135
|
+
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
73136
|
+
ctx.fillRect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
71486
73137
|
const title = {
|
|
71487
73138
|
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
71488
73139
|
...carousel.title
|
|
71489
73140
|
};
|
|
71490
|
-
|
|
71491
|
-
|
|
71492
|
-
if (!title.text && chartId) {
|
|
71493
|
-
const chartRect = {
|
|
71494
|
-
x,
|
|
71495
|
-
y,
|
|
71496
|
-
width: figure.width,
|
|
71497
|
-
height: figure.height
|
|
71498
|
-
};
|
|
71499
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71500
|
-
} else if (title.text) {
|
|
71501
|
-
ctx.save();
|
|
71502
|
-
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
71503
|
-
ctx.fillRect(x, y, figure.width, headerSize);
|
|
71504
|
-
ctx.font = computeTextFont(chartStyleToCellStyle(title));
|
|
73141
|
+
if (title.text) {
|
|
73142
|
+
ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
|
|
71505
73143
|
ctx.fillStyle = title.color;
|
|
71506
|
-
ctx.
|
|
71507
|
-
|
|
71508
|
-
|
|
71509
|
-
|
|
71510
|
-
|
|
71511
|
-
|
|
71512
|
-
|
|
71513
|
-
|
|
71514
|
-
if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
|
|
71515
|
-
else if (!this.getters.isDashboard()) {
|
|
71516
|
-
ctx.strokeStyle = GRAY_400;
|
|
71517
|
-
ctx.lineWidth = 1;
|
|
71518
|
-
ctx.beginPath();
|
|
71519
|
-
ctx.moveTo(x, y + headerSize);
|
|
71520
|
-
ctx.lineTo(x + figure.width, y + headerSize);
|
|
71521
|
-
ctx.stroke();
|
|
71522
|
-
}
|
|
73144
|
+
ctx.textBaseline = "middle";
|
|
73145
|
+
const textY = Math.ceil(layout.headerRect.y + layout.headerRect.height / 2);
|
|
73146
|
+
ctx.fillText(title.text, layout.headerRect.x, textY);
|
|
73147
|
+
}
|
|
73148
|
+
const separator = layout.separatorRect;
|
|
73149
|
+
if (separator) {
|
|
73150
|
+
ctx.fillStyle = GRAY_400;
|
|
73151
|
+
ctx.fillRect(separator.x, separator.y, separator.width, separator.height);
|
|
71523
73152
|
}
|
|
73153
|
+
ctx.restore();
|
|
73154
|
+
if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
|
|
71524
73155
|
}
|
|
71525
73156
|
};
|
|
71526
73157
|
|
|
@@ -71528,12 +73159,13 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71528
73159
|
//#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
|
|
71529
73160
|
var StandaloneGridCanvas = class extends Component {
|
|
71530
73161
|
static template = "o-spreadsheet-StandaloneGridCanvas";
|
|
73162
|
+
static components = { CarouselDataViewPrint };
|
|
71531
73163
|
props = useProps({
|
|
71532
73164
|
sheetId: types$1.UID(),
|
|
71533
73165
|
zone: types$1.Zone(),
|
|
71534
73166
|
renderingCtx: types$1.object()
|
|
71535
73167
|
});
|
|
71536
|
-
canvasRef = signal(
|
|
73168
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
71537
73169
|
rendererStore;
|
|
71538
73170
|
figureRendererStore;
|
|
71539
73171
|
setup() {
|
|
@@ -71555,6 +73187,28 @@ var StandaloneGridCanvas = class extends Component {
|
|
|
71555
73187
|
this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
|
|
71556
73188
|
}));
|
|
71557
73189
|
}
|
|
73190
|
+
get carouselDataViews() {
|
|
73191
|
+
const { sheetId, viewports } = this.props.renderingCtx;
|
|
73192
|
+
return viewports.getVisibleFigures(sheetId).filter((figure) => figure.tag === "carousel").map((figure) => {
|
|
73193
|
+
const carouselItem = this.env.model.getters.getSelectedCarouselItem(figure.id);
|
|
73194
|
+
if (carouselItem?.type !== "carouselDataView" || !carouselItem.range) return;
|
|
73195
|
+
const carousel = this.env.model.getters.getCarousel(figure.id);
|
|
73196
|
+
const scroll = viewports.getViewportOffset(sheetId);
|
|
73197
|
+
const { x, y, width, height } = figure;
|
|
73198
|
+
const layout = getCarouselLayout({
|
|
73199
|
+
x: x - scroll.x,
|
|
73200
|
+
y: y - scroll.y,
|
|
73201
|
+
width,
|
|
73202
|
+
height
|
|
73203
|
+
}, carousel, carouselItem);
|
|
73204
|
+
return {
|
|
73205
|
+
range: carouselItem.range,
|
|
73206
|
+
columnWeights: carouselItem.columnWeights,
|
|
73207
|
+
rect: layout.contentRect,
|
|
73208
|
+
rendererStore: this.rendererStore
|
|
73209
|
+
};
|
|
73210
|
+
}).filter(isDefined);
|
|
73211
|
+
}
|
|
71558
73212
|
};
|
|
71559
73213
|
|
|
71560
73214
|
//#endregion
|
|
@@ -72016,7 +73670,7 @@ var NamedRangeSelector = class extends Component {
|
|
|
72016
73670
|
anchorRect: null,
|
|
72017
73671
|
menuItems: []
|
|
72018
73672
|
});
|
|
72019
|
-
namedRangeSelectorRef = signal(
|
|
73673
|
+
namedRangeSelectorRef = signal.ref();
|
|
72020
73674
|
setup() {
|
|
72021
73675
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72022
73676
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -72214,7 +73868,7 @@ var BorderEditor = class extends Component {
|
|
|
72214
73868
|
anchorRect: types$1.Rect()
|
|
72215
73869
|
});
|
|
72216
73870
|
BORDER_POSITIONS = BORDER_POSITIONS;
|
|
72217
|
-
lineStyleButtonRef = signal(
|
|
73871
|
+
lineStyleButtonRef = signal.ref();
|
|
72218
73872
|
borderStyles = borderStyles;
|
|
72219
73873
|
state = proxy({ activeTool: void 0 });
|
|
72220
73874
|
toggleDropdownTool(tool) {
|
|
@@ -72281,7 +73935,7 @@ var BorderEditorWidget = class extends Component {
|
|
|
72281
73935
|
});
|
|
72282
73936
|
topBarToolStore;
|
|
72283
73937
|
viewStore;
|
|
72284
|
-
borderEditorButtonRef = signal(
|
|
73938
|
+
borderEditorButtonRef = signal.ref();
|
|
72285
73939
|
state = proxy({
|
|
72286
73940
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
72287
73941
|
currentStyle: DEFAULT_BORDER_DESC.style,
|
|
@@ -72511,7 +74165,7 @@ var DropdownAction = class extends Component {
|
|
|
72511
74165
|
childClass: types$1.string()
|
|
72512
74166
|
});
|
|
72513
74167
|
topBarToolStore;
|
|
72514
|
-
actionRef = signal(
|
|
74168
|
+
actionRef = signal.ref();
|
|
72515
74169
|
setup() {
|
|
72516
74170
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72517
74171
|
}
|
|
@@ -72574,7 +74228,7 @@ var NumberFormatsTool = class extends Component {
|
|
|
72574
74228
|
props = useProps({ class: types$1.string() });
|
|
72575
74229
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
72576
74230
|
topBarToolStore;
|
|
72577
|
-
buttonRef = signal(
|
|
74231
|
+
buttonRef = signal.ref();
|
|
72578
74232
|
state = proxy({
|
|
72579
74233
|
anchorRect: {
|
|
72580
74234
|
x: 0,
|
|
@@ -72863,12 +74517,12 @@ var TopBar = class extends Component {
|
|
|
72863
74517
|
composerFocusStore;
|
|
72864
74518
|
fingerprints;
|
|
72865
74519
|
topBarToolStore;
|
|
72866
|
-
toolBarContainerRef = signal(
|
|
72867
|
-
toolbarRef = signal(
|
|
72868
|
-
namedRangesRef = signal(
|
|
72869
|
-
topBarTopRef = signal(
|
|
72870
|
-
moreToolsContainerRef = signal(
|
|
72871
|
-
moreToolsButtonRef = signal(
|
|
74520
|
+
toolBarContainerRef = signal.ref();
|
|
74521
|
+
toolbarRef = signal.ref();
|
|
74522
|
+
namedRangesRef = signal.ref();
|
|
74523
|
+
topBarTopRef = signal.ref();
|
|
74524
|
+
moreToolsContainerRef = signal.ref();
|
|
74525
|
+
moreToolsButtonRef = signal.ref();
|
|
72872
74526
|
spreadsheetRect = useSpreadsheetRect();
|
|
72873
74527
|
setup() {
|
|
72874
74528
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -73130,7 +74784,7 @@ var Spreadsheet = class extends Component {
|
|
|
73130
74784
|
SpreadsheetPrint
|
|
73131
74785
|
};
|
|
73132
74786
|
sidePanel;
|
|
73133
|
-
spreadsheetRef = signal(
|
|
74787
|
+
spreadsheetRef = signal.ref();
|
|
73134
74788
|
spreadsheetRect = useSpreadsheetRect();
|
|
73135
74789
|
state = proxy({ printModeEnabled: false });
|
|
73136
74790
|
_focusGrid;
|
|
@@ -79138,12 +80792,13 @@ var FilterMenu = class extends Component {
|
|
|
79138
80792
|
}
|
|
79139
80793
|
sortFilterZone(sortDirection) {
|
|
79140
80794
|
const filterPosition = this.props.filterPosition;
|
|
79141
|
-
const
|
|
80795
|
+
const table = this.table;
|
|
80796
|
+
const tableZone = table?.range.zone;
|
|
79142
80797
|
if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
|
|
79143
80798
|
const sheetId = this.viewStore.displayedSheetId;
|
|
79144
80799
|
const contentZone = {
|
|
79145
80800
|
...tableZone,
|
|
79146
|
-
top: tableZone.top +
|
|
80801
|
+
top: tableZone.top + table.config.numberOfHeaders
|
|
79147
80802
|
};
|
|
79148
80803
|
const sortAnchor = {
|
|
79149
80804
|
col: filterPosition.col,
|
|
@@ -79301,9 +80956,9 @@ var LinkEditor = class extends Component {
|
|
|
79301
80956
|
onClosed: types$1.function().optional()
|
|
79302
80957
|
});
|
|
79303
80958
|
static size = { maxHeight: 500 };
|
|
79304
|
-
urlInput = signal(
|
|
79305
|
-
suggestionListRef = signal(
|
|
79306
|
-
urlInputContainer = signal(
|
|
80959
|
+
urlInput = signal.ref(HTMLInputElement);
|
|
80960
|
+
suggestionListRef = signal.ref();
|
|
80961
|
+
urlInputContainer = signal.ref();
|
|
79307
80962
|
state;
|
|
79308
80963
|
viewStore;
|
|
79309
80964
|
setup() {
|
|
@@ -81421,7 +83076,7 @@ const ARRAYTOTEXT = {
|
|
|
81421
83076
|
else if (_format === 0) {
|
|
81422
83077
|
const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
|
|
81423
83078
|
return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
|
|
81424
|
-
return isEvaluationError(value.value) ? value.value :
|
|
83079
|
+
return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
|
|
81425
83080
|
})).join(rowSeparator) };
|
|
81426
83081
|
} else return new EvaluationError(_t("Format must be 0 or 1"));
|
|
81427
83082
|
},
|
|
@@ -87469,7 +89124,7 @@ const CONCAT = {
|
|
|
87469
89124
|
description: _t("Concatenation of two values."),
|
|
87470
89125
|
args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
|
|
87471
89126
|
compute: function(value1, value2) {
|
|
87472
|
-
return { value:
|
|
89127
|
+
return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
|
|
87473
89128
|
},
|
|
87474
89129
|
isExported: true
|
|
87475
89130
|
};
|
|
@@ -88352,7 +90007,7 @@ const CLEAN = {
|
|
|
88352
90007
|
description: _t("Remove non-printable characters from a piece of text."),
|
|
88353
90008
|
args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
|
|
88354
90009
|
compute: function(text) {
|
|
88355
|
-
const _text =
|
|
90010
|
+
const _text = toLocaleString(text, this.locale);
|
|
88356
90011
|
let cleanedStr = "";
|
|
88357
90012
|
for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
|
|
88358
90013
|
return { value: cleanedStr };
|
|
@@ -88363,7 +90018,7 @@ const CONCATENATE = {
|
|
|
88363
90018
|
description: _t("Appends strings to one another."),
|
|
88364
90019
|
args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
|
|
88365
90020
|
compute: function(...datas) {
|
|
88366
|
-
return { value: reduceAny(datas, (acc, a) => acc +
|
|
90021
|
+
return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
|
|
88367
90022
|
},
|
|
88368
90023
|
isExported: true
|
|
88369
90024
|
};
|
|
@@ -88371,7 +90026,7 @@ const EXACT = {
|
|
|
88371
90026
|
description: _t("Tests whether two strings are identical."),
|
|
88372
90027
|
args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
|
|
88373
90028
|
compute: function(string1, string2) {
|
|
88374
|
-
return { value:
|
|
90029
|
+
return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
|
|
88375
90030
|
},
|
|
88376
90031
|
isExported: true
|
|
88377
90032
|
};
|
|
@@ -88383,8 +90038,8 @@ const FIND = {
|
|
|
88383
90038
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88384
90039
|
],
|
|
88385
90040
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88386
|
-
const _searchFor =
|
|
88387
|
-
const _textToSearch =
|
|
90041
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
90042
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
88388
90043
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88389
90044
|
if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
88390
90045
|
if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
@@ -88421,8 +90076,8 @@ const JOIN = {
|
|
|
88421
90076
|
description: _t("Concatenates elements of arrays with delimiter."),
|
|
88422
90077
|
args: [arg("delimiter (string)", _t("The character or string to place between each concatenated value.")), arg("value_or_array (string, range<string>, repeating)", _t("Value to be appended using delimiter."))],
|
|
88423
90078
|
compute: function(delimiter, ...valuesOrArrays) {
|
|
88424
|
-
const _delimiter =
|
|
88425
|
-
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") +
|
|
90079
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
90080
|
+
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "") };
|
|
88426
90081
|
}
|
|
88427
90082
|
};
|
|
88428
90083
|
const LEFT = {
|
|
@@ -88431,7 +90086,7 @@ const LEFT = {
|
|
|
88431
90086
|
compute: function(text, ...args) {
|
|
88432
90087
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88433
90088
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
88434
|
-
return { value:
|
|
90089
|
+
return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
|
|
88435
90090
|
},
|
|
88436
90091
|
isExported: true
|
|
88437
90092
|
};
|
|
@@ -88439,7 +90094,7 @@ const LEN = {
|
|
|
88439
90094
|
description: _t("Length of a string."),
|
|
88440
90095
|
args: [arg("text (string)", _t("The string whose length will be returned."))],
|
|
88441
90096
|
compute: function(text) {
|
|
88442
|
-
return { value:
|
|
90097
|
+
return { value: toLocaleString(text, this.locale).length };
|
|
88443
90098
|
},
|
|
88444
90099
|
isExported: true
|
|
88445
90100
|
};
|
|
@@ -88447,7 +90102,7 @@ const LOWER = {
|
|
|
88447
90102
|
description: _t("Converts a specified string to lowercase."),
|
|
88448
90103
|
args: [arg("text (string)", _t("The string to convert to lowercase."))],
|
|
88449
90104
|
compute: function(text) {
|
|
88450
|
-
return { value:
|
|
90105
|
+
return { value: toLocaleString(text, this.locale).toLowerCase() };
|
|
88451
90106
|
},
|
|
88452
90107
|
isExported: true
|
|
88453
90108
|
};
|
|
@@ -88459,7 +90114,7 @@ const MID = {
|
|
|
88459
90114
|
arg("extract_length (number)", _t("The length of the segment to extract."))
|
|
88460
90115
|
],
|
|
88461
90116
|
compute: function(text, starting_at, extract_length) {
|
|
88462
|
-
const _text =
|
|
90117
|
+
const _text = toLocaleString(text, this.locale);
|
|
88463
90118
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
88464
90119
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
88465
90120
|
if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
@@ -88472,7 +90127,7 @@ const PROPER = {
|
|
|
88472
90127
|
description: _t("Capitalizes each word in a specified string."),
|
|
88473
90128
|
args: [arg("text_to_capitalize (string)", _t("The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase."))],
|
|
88474
90129
|
compute: function(text) {
|
|
88475
|
-
return { value:
|
|
90130
|
+
return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
|
|
88476
90131
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
88477
90132
|
}) };
|
|
88478
90133
|
},
|
|
@@ -88622,8 +90277,8 @@ const REPLACE = {
|
|
|
88622
90277
|
if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
88623
90278
|
const _length = toNumber(length, this.locale);
|
|
88624
90279
|
if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
|
|
88625
|
-
const _text =
|
|
88626
|
-
const _newText =
|
|
90280
|
+
const _text = toLocaleString(text, this.locale);
|
|
90281
|
+
const _newText = toLocaleString(newText, this.locale);
|
|
88627
90282
|
return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
|
|
88628
90283
|
},
|
|
88629
90284
|
isExported: true
|
|
@@ -88634,7 +90289,7 @@ const RIGHT = {
|
|
|
88634
90289
|
compute: function(text, ...args) {
|
|
88635
90290
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88636
90291
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
|
|
88637
|
-
const _text =
|
|
90292
|
+
const _text = toLocaleString(text, this.locale);
|
|
88638
90293
|
const stringLength = _text.length;
|
|
88639
90294
|
return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
|
|
88640
90295
|
},
|
|
@@ -88648,8 +90303,8 @@ const SEARCH = {
|
|
|
88648
90303
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88649
90304
|
],
|
|
88650
90305
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88651
|
-
const _searchFor =
|
|
88652
|
-
const _textToSearch =
|
|
90306
|
+
const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
|
|
90307
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
|
|
88653
90308
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88654
90309
|
if (_textToSearch === "") return {
|
|
88655
90310
|
value: CellErrorType.GenericError,
|
|
@@ -88679,8 +90334,8 @@ const SPLIT = {
|
|
|
88679
90334
|
arg(`remove_empty_text (boolean, default=${SPLIT_DEFAULT_REMOVE_EMPTY_TEXT})`, _t("Whether or not to remove empty text messages from the split results. The default behavior is to treat consecutive delimiters as one (if TRUE). If FALSE, empty cells values are added between consecutive delimiters."))
|
|
88680
90335
|
],
|
|
88681
90336
|
computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
|
|
88682
|
-
const _text =
|
|
88683
|
-
const _delimiter = escapeRegExp(
|
|
90337
|
+
const _text = toLocaleString(text, this.locale);
|
|
90338
|
+
const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
|
|
88684
90339
|
const _splitByEach = toBoolean(splitByEach);
|
|
88685
90340
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
88686
90341
|
if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
@@ -88702,10 +90357,10 @@ const SUBSTITUTE = {
|
|
|
88702
90357
|
compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
88703
90358
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
88704
90359
|
if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
88705
|
-
const _textToSearch =
|
|
88706
|
-
const _searchFor =
|
|
90360
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
90361
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
88707
90362
|
if (_searchFor === "") return { value: _textToSearch };
|
|
88708
|
-
const _replaceWith =
|
|
90363
|
+
const _replaceWith = toLocaleString(replaceWith, this.locale);
|
|
88709
90364
|
const reg = new RegExp(escapeRegExp(_searchFor), "g");
|
|
88710
90365
|
if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
|
|
88711
90366
|
let n = 0;
|
|
@@ -88728,10 +90383,10 @@ const TEXTJOIN = {
|
|
|
88728
90383
|
arg("texts (string, range<string>, repeating)", _t("Text item to join."))
|
|
88729
90384
|
],
|
|
88730
90385
|
compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
|
|
88731
|
-
const _delimiter =
|
|
90386
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
88732
90387
|
const _ignoreEmpty = toBoolean(ignoreEmpty);
|
|
88733
90388
|
let n = 0;
|
|
88734
|
-
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty &&
|
|
90389
|
+
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "") };
|
|
88735
90390
|
},
|
|
88736
90391
|
isExported: true
|
|
88737
90392
|
};
|
|
@@ -88784,7 +90439,7 @@ const TRIM = {
|
|
|
88784
90439
|
description: _t("Removes space characters."),
|
|
88785
90440
|
args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
|
|
88786
90441
|
compute: function(text) {
|
|
88787
|
-
return { value: trimContent(
|
|
90442
|
+
return { value: trimContent(toLocaleString(text, this.locale)) };
|
|
88788
90443
|
},
|
|
88789
90444
|
isExported: true
|
|
88790
90445
|
};
|
|
@@ -88792,7 +90447,7 @@ const UPPER = {
|
|
|
88792
90447
|
description: _t("Converts a specified string to uppercase."),
|
|
88793
90448
|
args: [arg("text (string)", _t("The string to convert to uppercase."))],
|
|
88794
90449
|
compute: function(text) {
|
|
88795
|
-
return { value:
|
|
90450
|
+
return { value: toLocaleString(text, this.locale).toUpperCase() };
|
|
88796
90451
|
},
|
|
88797
90452
|
isExported: true
|
|
88798
90453
|
};
|
|
@@ -88889,7 +90544,7 @@ const HYPERLINK = {
|
|
|
88889
90544
|
args: [arg("url (string)", _t("The full URL of the link enclosed in quotation marks.")), arg("link_label (string, optional)", _t("The text to display in the cell, enclosed in quotation marks."))],
|
|
88890
90545
|
compute: function(url, linkLabel) {
|
|
88891
90546
|
const processedUrl = toString(url).trim();
|
|
88892
|
-
const processedLabel =
|
|
90547
|
+
const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
|
|
88893
90548
|
if (processedUrl === "") return { value: processedLabel };
|
|
88894
90549
|
return { value: markdownLink(processedLabel, processedUrl) };
|
|
88895
90550
|
},
|
|
@@ -89119,7 +90774,9 @@ const helpers = {
|
|
|
89119
90774
|
computeCachedTextDimension,
|
|
89120
90775
|
createComputeFunction,
|
|
89121
90776
|
isMobileOS,
|
|
89122
|
-
drawHighlight
|
|
90777
|
+
drawHighlight,
|
|
90778
|
+
getBoundingRectWithMargins,
|
|
90779
|
+
hasInteractiveElementInEventTree
|
|
89123
90780
|
};
|
|
89124
90781
|
const links = {
|
|
89125
90782
|
isMarkdownLink,
|
|
@@ -89257,6 +90914,6 @@ const chartHelpers = {
|
|
|
89257
90914
|
//#endregion
|
|
89258
90915
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, BadExpressionError, CHART_TYPES, CellErrorType, CellValueType, CircularDependencyError, ClientDisconnectedError, ClipboardMIMEType, CommandResult, CompiledFormula, CorePlugin, CoreViewPlugin, DEFAULT_LOCALE, DEFAULT_LOCALES, DEFAULT_LOCALE_DIGIT_GROUPING, DIRECTION, DispatchResult, DivisionByZeroError, EvaluationError, InvalidReferenceError, LocalTransportService, Model, NEXT_VALUE, NotAvailableError, NumberTooLargeError, OrderedLayers, PREVIOUS_VALUE, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, SplillBlockedError, Spreadsheet, SpreadsheetPivotTable, UIPlugin, UnknownFunctionError, __info__, addFunction, addRenderingLayer, astToFormula, availableConditionalFormatOperators, availableDataValidationOperators, availableFiltersOperators, borderPositions, borderStyles, canExecuteInReadonly, categories, chartHelpers, compatibility, components, composerFocusTypes, constants, convertAstNodes, coreTypes, createAutocompleteArgumentsProvider, errorTypes, filterDateCriterionOperators, filterNumberCriterionOperators, filterTextCriterionOperators, findCellInNewZone, functionCache, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidSubtotalFormulasCommands, invalidateBordersCommands, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, isCoreCommand, isHeadersDependant, isMatrix, isPositionDependent, isRangeDependant, isSheetDependent, isTargetDependent, isZoneDependent, iterateAstNodes, links, load, lockedSheetAllowedCommands, parse, parseTokens, readonlyAllowedCommands, registries, schemeToColorScale, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
|
|
89259
90916
|
|
|
89260
|
-
__info__.version = "19.5.0-alpha.
|
|
89261
|
-
__info__.date = "2026-08-
|
|
89262
|
-
__info__.hash = "
|
|
90917
|
+
__info__.version = "19.5.0-alpha.12";
|
|
90918
|
+
__info__.date = "2026-08-19T09:47:03.774Z";
|
|
90919
|
+
__info__.hash = "68ca68e";
|