@odoo/o-spreadsheet 19.5.0-alpha.10 → 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 +2325 -659
- package/dist/o_spreadsheet.css +169 -33
- package/dist/o_spreadsheet.esm.js +2317 -651
- package/dist/o_spreadsheet.iife.js +2325 -659
- 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/function_registry.d.ts +3 -0
- 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 +5 -2
- package/dist/types/plugins/core/squisher.d.ts +2 -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, "");
|
|
@@ -5985,6 +6015,9 @@ var FunctionRegistry = class extends Registry {
|
|
|
5985
6015
|
}
|
|
5986
6016
|
};
|
|
5987
6017
|
const functionRegistry = new FunctionRegistry();
|
|
6018
|
+
/** Formulas using one of these functions are never squished: they are always exported
|
|
6019
|
+
* as a full formula string, and they don't become the base formula of the next cells. */
|
|
6020
|
+
const NonSquishableFunctionRegistry = new Registry();
|
|
5988
6021
|
|
|
5989
6022
|
//#endregion
|
|
5990
6023
|
//#region src/helpers/references.ts
|
|
@@ -9244,8 +9277,8 @@ function computeCachedTextDimension(context, text, font) {
|
|
|
9244
9277
|
function fontSizeInPixels(fontSize) {
|
|
9245
9278
|
return Math.round(fontSize * 96 / 72);
|
|
9246
9279
|
}
|
|
9247
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
9248
|
-
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}`;
|
|
9249
9282
|
}
|
|
9250
9283
|
function computeTextFontSizeInPixels(style) {
|
|
9251
9284
|
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
@@ -11879,7 +11912,7 @@ var ChartJsComponent = class extends Component {
|
|
|
11879
11912
|
chartId: types$1.string(),
|
|
11880
11913
|
isFullScreen: types$1.boolean().optional()
|
|
11881
11914
|
});
|
|
11882
|
-
canvas = signal(
|
|
11915
|
+
canvas = signal.ref(HTMLCanvasElement);
|
|
11883
11916
|
chart;
|
|
11884
11917
|
currentRuntime;
|
|
11885
11918
|
animationStore;
|
|
@@ -12602,7 +12635,7 @@ var ScorecardChart = class extends Component {
|
|
|
12602
12635
|
chartId: types$1.string(),
|
|
12603
12636
|
isFullScreen: types$1.boolean().optional()
|
|
12604
12637
|
});
|
|
12605
|
-
canvas = signal(
|
|
12638
|
+
canvas = signal.ref(HTMLCanvasElement);
|
|
12606
12639
|
zoomStore;
|
|
12607
12640
|
get runtime() {
|
|
12608
12641
|
return this.env.model.getters.getChartRuntime(this.props.chartId);
|
|
@@ -13987,6 +14020,44 @@ function getCarouselItemTitle(getters, item) {
|
|
|
13987
14020
|
const definition = getters.getChartDefinition(item.chartId);
|
|
13988
14021
|
return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
|
|
13989
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
|
+
}
|
|
13990
14061
|
|
|
13991
14062
|
//#endregion
|
|
13992
14063
|
//#region src/helpers/figures/charts/gauge_chart_rendering.ts
|
|
@@ -13998,7 +14069,7 @@ const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
|
13998
14069
|
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
13999
14070
|
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
14000
14071
|
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
14001
|
-
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
14072
|
+
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
|
|
14002
14073
|
const size = dimensions ?? getCanvasSize(canvas);
|
|
14003
14074
|
const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
14004
14075
|
canvas.width = size.width * dpr;
|
|
@@ -14011,7 +14082,7 @@ function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
|
14011
14082
|
height: size.height / zoom,
|
|
14012
14083
|
x: 0,
|
|
14013
14084
|
y: 0
|
|
14014
|
-
}, runtime, ctx);
|
|
14085
|
+
}, runtime, ctx, options);
|
|
14015
14086
|
drawBackground(ctx, config);
|
|
14016
14087
|
drawGauge(ctx, config);
|
|
14017
14088
|
drawInflectionValues(ctx, config);
|
|
@@ -14099,7 +14170,8 @@ function drawTitle(ctx, config) {
|
|
|
14099
14170
|
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
14100
14171
|
ctx.restore();
|
|
14101
14172
|
}
|
|
14102
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
14173
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
|
|
14174
|
+
const labelFontSize = options?.labelFontSize ?? 12;
|
|
14103
14175
|
const maxValue = runtime.maxValue;
|
|
14104
14176
|
const minValue = runtime.minValue;
|
|
14105
14177
|
const gaugeValue = getGaugeValue(runtime, "animated");
|
|
@@ -14117,14 +14189,14 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14117
14189
|
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
14118
14190
|
const minLabelPosition = {
|
|
14119
14191
|
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
14120
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14192
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14121
14193
|
};
|
|
14122
14194
|
const maxLabelPosition = {
|
|
14123
14195
|
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
14124
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14196
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14125
14197
|
};
|
|
14126
14198
|
const textColor = chartMutedFontColor(runtime.background);
|
|
14127
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
14199
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
|
|
14128
14200
|
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
14129
14201
|
if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
|
|
14130
14202
|
fontSize: 16,
|
|
@@ -14172,13 +14244,13 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14172
14244
|
minLabel: {
|
|
14173
14245
|
label: runtime.minValue.label,
|
|
14174
14246
|
textPosition: minLabelPosition,
|
|
14175
|
-
fontSize:
|
|
14247
|
+
fontSize: labelFontSize,
|
|
14176
14248
|
color: textColor
|
|
14177
14249
|
},
|
|
14178
14250
|
maxLabel: {
|
|
14179
14251
|
label: runtime.maxValue.label,
|
|
14180
14252
|
textPosition: maxLabelPosition,
|
|
14181
|
-
fontSize:
|
|
14253
|
+
fontSize: labelFontSize,
|
|
14182
14254
|
color: textColor
|
|
14183
14255
|
}
|
|
14184
14256
|
};
|
|
@@ -14212,27 +14284,27 @@ function getGaugeRect(boundingRect, title) {
|
|
|
14212
14284
|
*
|
|
14213
14285
|
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
14214
14286
|
*/
|
|
14215
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
14287
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
|
|
14216
14288
|
const maxValue = runtime.maxValue;
|
|
14217
14289
|
const minValue = runtime.minValue;
|
|
14218
14290
|
const gaugeCircleCenter = {
|
|
14219
14291
|
x: gaugeRect.x + gaugeRect.width / 2,
|
|
14220
14292
|
y: gaugeRect.y + gaugeRect.height
|
|
14221
14293
|
};
|
|
14222
|
-
const textStyle = { fontSize:
|
|
14294
|
+
const textStyle = { fontSize: labelFontSize };
|
|
14223
14295
|
const inflectionValues = [];
|
|
14224
14296
|
const inflectionValuesTextRects = [];
|
|
14225
14297
|
for (const inflectionValue of runtime.inflectionValues) {
|
|
14226
14298
|
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
14227
14299
|
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
14228
14300
|
const angle = Math.PI - Math.PI * percentage;
|
|
14229
|
-
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2,
|
|
14230
|
-
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;
|
|
14231
14303
|
inflectionValuesTextRects.push(textRect);
|
|
14232
14304
|
inflectionValues.push({
|
|
14233
14305
|
rotation: angle,
|
|
14234
14306
|
label: inflectionValue.label,
|
|
14235
|
-
fontSize:
|
|
14307
|
+
fontSize: labelFontSize,
|
|
14236
14308
|
color: textColor,
|
|
14237
14309
|
offset
|
|
14238
14310
|
});
|
|
@@ -14352,6 +14424,44 @@ const CHART_COMMON_OPTIONS = {
|
|
|
14352
14424
|
"mouseup"
|
|
14353
14425
|
]
|
|
14354
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
|
+
}
|
|
14355
14465
|
async function chartToImageUrl(runtime, figure, type) {
|
|
14356
14466
|
try {
|
|
14357
14467
|
const canvas = createRenderingSurface(figure.width, figure.height);
|
|
@@ -14399,7 +14509,7 @@ async function canvasToObjectUrl(canvas) {
|
|
|
14399
14509
|
*
|
|
14400
14510
|
* @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
|
|
14401
14511
|
*/
|
|
14402
|
-
function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
14512
|
+
function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
|
|
14403
14513
|
if ("chartJsConfig" in runtime) {
|
|
14404
14514
|
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
14405
14515
|
const extensionsLoaded = areChartJSExtensionsLoaded();
|
|
@@ -14415,8 +14525,8 @@ function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
|
14415
14525
|
chart.destroy();
|
|
14416
14526
|
if (!extensionsLoaded) unregisterChartJsExtensions();
|
|
14417
14527
|
};
|
|
14418
|
-
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
|
|
14419
|
-
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);
|
|
14420
14530
|
return () => {};
|
|
14421
14531
|
}
|
|
14422
14532
|
|
|
@@ -14954,7 +15064,7 @@ var ZoomableChartStore = class extends SpreadsheetStore {
|
|
|
14954
15064
|
var ZoomableChartJsComponent = class extends ChartJsComponent {
|
|
14955
15065
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
14956
15066
|
store;
|
|
14957
|
-
masterChartCanvas = signal(
|
|
15067
|
+
masterChartCanvas = signal.ref(HTMLCanvasElement);
|
|
14958
15068
|
masterChart;
|
|
14959
15069
|
mode;
|
|
14960
15070
|
hasLinearScale;
|
|
@@ -15708,7 +15818,7 @@ var GaugeChartComponent = class extends Component {
|
|
|
15708
15818
|
chartId: types$1.string(),
|
|
15709
15819
|
isFullScreen: types$1.boolean().optional()
|
|
15710
15820
|
});
|
|
15711
|
-
canvas = signal(
|
|
15821
|
+
canvas = signal.ref(HTMLCanvasElement);
|
|
15712
15822
|
animationStore;
|
|
15713
15823
|
zoomStore;
|
|
15714
15824
|
get runtime() {
|
|
@@ -15945,7 +16055,7 @@ var Menu = class extends Component {
|
|
|
15945
16055
|
onKeyDown: types$1.function().optional(),
|
|
15946
16056
|
disableKeyboardNavigation: types$1.boolean().optional()
|
|
15947
16057
|
});
|
|
15948
|
-
menuRef = signal(
|
|
16058
|
+
menuRef = signal.ref();
|
|
15949
16059
|
setup() {
|
|
15950
16060
|
useLayoutEffect(() => {
|
|
15951
16061
|
const menuEl = this.menuRef();
|
|
@@ -16065,8 +16175,8 @@ var Popover = class extends Component {
|
|
|
16065
16175
|
zIndex: types$1.number().optional(),
|
|
16066
16176
|
class: types$1.string().optional()
|
|
16067
16177
|
});
|
|
16068
|
-
popoverRef = signal(
|
|
16069
|
-
popoverContentRef = signal(
|
|
16178
|
+
popoverRef = signal.ref();
|
|
16179
|
+
popoverContentRef = signal.ref();
|
|
16070
16180
|
currentPosition = void 0;
|
|
16071
16181
|
currentDisplayValue = void 0;
|
|
16072
16182
|
spreadsheetRect = useSpreadsheetRect();
|
|
@@ -16254,7 +16364,7 @@ var MenuPopover = class MenuPopover extends Component {
|
|
|
16254
16364
|
isHoveringChild: false
|
|
16255
16365
|
});
|
|
16256
16366
|
state = proxy({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
|
|
16257
|
-
menuRef = signal(
|
|
16367
|
+
menuRef = signal.ref();
|
|
16258
16368
|
openingTimeOut = useTimeOut();
|
|
16259
16369
|
setup() {
|
|
16260
16370
|
const domFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -16543,43 +16653,29 @@ var PositionMap = class {
|
|
|
16543
16653
|
};
|
|
16544
16654
|
|
|
16545
16655
|
//#endregion
|
|
16546
|
-
//#region src/
|
|
16547
|
-
var
|
|
16548
|
-
mutators = [
|
|
16549
|
-
|
|
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 = [];
|
|
16550
16665
|
overlayColors = new PositionMap();
|
|
16551
|
-
hover(
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
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
|
+
}
|
|
16555
16673
|
}
|
|
16556
|
-
|
|
16557
|
-
this.
|
|
16674
|
+
register(highlightProvider) {
|
|
16675
|
+
this.providers.push(highlightProvider);
|
|
16558
16676
|
}
|
|
16559
|
-
|
|
16560
|
-
this.
|
|
16561
|
-
if (!this.position) return;
|
|
16562
|
-
const { sheetId, col, row } = this.position;
|
|
16563
|
-
const table = this.getters.getTable({
|
|
16564
|
-
sheetId,
|
|
16565
|
-
col,
|
|
16566
|
-
row
|
|
16567
|
-
});
|
|
16568
|
-
if (!table) return;
|
|
16569
|
-
const { left, right, top } = table.range.zone;
|
|
16570
|
-
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
16571
|
-
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
16572
|
-
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
16573
|
-
sheetId,
|
|
16574
|
-
col,
|
|
16575
|
-
row
|
|
16576
|
-
}).formattedValue;
|
|
16577
|
-
});
|
|
16578
|
-
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
|
|
16579
|
-
sheetId,
|
|
16580
|
-
col,
|
|
16581
|
-
row
|
|
16582
|
-
}, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16677
|
+
unRegister(highlightProvider) {
|
|
16678
|
+
this.providers = this.providers.filter((h) => h !== highlightProvider);
|
|
16583
16679
|
}
|
|
16584
16680
|
};
|
|
16585
16681
|
|
|
@@ -16591,9 +16687,9 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16591
16687
|
position: types$1.CellPosition(),
|
|
16592
16688
|
sortDirection: types$1.or([types$1.SortDirection, types$1.literal("none")])
|
|
16593
16689
|
});
|
|
16594
|
-
|
|
16690
|
+
hoveredCellOverlayStore;
|
|
16595
16691
|
setup() {
|
|
16596
|
-
this.
|
|
16692
|
+
this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
|
|
16597
16693
|
}
|
|
16598
16694
|
get style() {
|
|
16599
16695
|
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
|
|
@@ -16613,7 +16709,7 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16613
16709
|
}
|
|
16614
16710
|
}
|
|
16615
16711
|
getBackgroundColor(cellStyle) {
|
|
16616
|
-
const overlayColor = this.
|
|
16712
|
+
const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
|
|
16617
16713
|
if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
|
|
16618
16714
|
return cellStyle.fillColor || "#FFFFFF";
|
|
16619
16715
|
}
|
|
@@ -21352,7 +21448,7 @@ var HoveredIconStore = class extends SpreadsheetStore {
|
|
|
21352
21448
|
//#region src/components/grid_overlay/grid_overlay.ts
|
|
21353
21449
|
function useCellHovered(env, gridRef) {
|
|
21354
21450
|
const delayedHoveredCell = useStore(DelayedHoveredCellStore);
|
|
21355
|
-
const
|
|
21451
|
+
const cellHoverOverlay = useStore(CellHoverOverlayStore);
|
|
21356
21452
|
const viewStore = useStore(ViewportsStore);
|
|
21357
21453
|
const hoveredPosition = {
|
|
21358
21454
|
col: void 0,
|
|
@@ -21396,7 +21492,9 @@ function useCellHovered(env, gridRef) {
|
|
|
21396
21492
|
if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
|
|
21397
21493
|
({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
|
|
21398
21494
|
lastMoved = Date.now();
|
|
21399
|
-
|
|
21495
|
+
const position = getPosition();
|
|
21496
|
+
if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
|
|
21497
|
+
else cellHoverOverlay.hover(getPosition());
|
|
21400
21498
|
}
|
|
21401
21499
|
}
|
|
21402
21500
|
function recompute() {
|
|
@@ -21407,7 +21505,7 @@ function useCellHovered(env, gridRef) {
|
|
|
21407
21505
|
const zoomedMouseEvent = withZoom(env, e);
|
|
21408
21506
|
const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
|
|
21409
21507
|
const gridRect = getElBoundingRect(gridRef());
|
|
21410
|
-
if (y
|
|
21508
|
+
if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
|
|
21411
21509
|
else return pause();
|
|
21412
21510
|
}
|
|
21413
21511
|
useListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
|
|
@@ -21444,7 +21542,7 @@ var GridOverlay = class extends Component {
|
|
|
21444
21542
|
gridOverlayDimensions: types$1.string(),
|
|
21445
21543
|
hasFooter: types$1.boolean().optional(() => true)
|
|
21446
21544
|
});
|
|
21447
|
-
gridOverlayRef = signal(
|
|
21545
|
+
gridOverlayRef = signal.ref();
|
|
21448
21546
|
cellPopovers;
|
|
21449
21547
|
paintFormatStore;
|
|
21450
21548
|
hoveredIconStore;
|
|
@@ -21471,7 +21569,7 @@ var GridOverlay = class extends Component {
|
|
|
21471
21569
|
return el;
|
|
21472
21570
|
}
|
|
21473
21571
|
get style() {
|
|
21474
|
-
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "
|
|
21572
|
+
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
|
|
21475
21573
|
}
|
|
21476
21574
|
get isPaintingFormat() {
|
|
21477
21575
|
return this.paintFormatStore.isActive;
|
|
@@ -22111,8 +22209,9 @@ var FormulaFingerprintStore = class extends SpreadsheetStore {
|
|
|
22111
22209
|
const CELL_ANIMATION_DURATION = 200;
|
|
22112
22210
|
var GridRenderer = class extends DisposableStore {
|
|
22113
22211
|
renderer;
|
|
22212
|
+
storeGetters = ["drawLayer"];
|
|
22114
22213
|
fingerprints;
|
|
22115
|
-
|
|
22214
|
+
cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
22116
22215
|
hoveredIcon;
|
|
22117
22216
|
lastRenderSheetId = void 0;
|
|
22118
22217
|
lastRenderBoxes = /* @__PURE__ */ new Map();
|
|
@@ -22126,15 +22225,14 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22126
22225
|
const model = get(ModelStore);
|
|
22127
22226
|
this.getters = model.getters;
|
|
22128
22227
|
this.fingerprints = get(FormulaFingerprintStore);
|
|
22129
|
-
this.hoveredTables = get(HoveredTableStore);
|
|
22130
22228
|
this.hoveredIcon = get(HoveredIconStore);
|
|
22131
22229
|
model.on("command-dispatched", this, this.handle);
|
|
22132
22230
|
model.on("command-finalized", this, this.finalize);
|
|
22133
|
-
this.renderer.register(this);
|
|
22231
|
+
if (this.renderer) this.renderer.register(this);
|
|
22134
22232
|
this.onDispose(() => {
|
|
22135
22233
|
model.off("command-dispatched", this);
|
|
22136
22234
|
model.off("command-finalized", this);
|
|
22137
|
-
this.renderer.unRegister(this);
|
|
22235
|
+
if (this.renderer) this.renderer.unRegister(this);
|
|
22138
22236
|
});
|
|
22139
22237
|
}
|
|
22140
22238
|
handle(cmd) {
|
|
@@ -22678,7 +22776,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22678
22776
|
border: this.getters.getCellComputedBorder(position) || void 0,
|
|
22679
22777
|
style,
|
|
22680
22778
|
dataBarFill,
|
|
22681
|
-
overlayColor: this.
|
|
22779
|
+
overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
|
|
22682
22780
|
isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
|
|
22683
22781
|
icons: cellIcons,
|
|
22684
22782
|
disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
|
|
@@ -22854,6 +22952,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22854
22952
|
return boxes;
|
|
22855
22953
|
}
|
|
22856
22954
|
getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
|
|
22955
|
+
if (!this.renderer) return boxes;
|
|
22857
22956
|
this.updateAnimationsProgress(timeStamp);
|
|
22858
22957
|
this.addNewAnimations(boxes, oldBoxes, timeStamp);
|
|
22859
22958
|
if (this.animations.size > 0) {
|
|
@@ -22998,7 +23097,7 @@ var ScrollBar = class extends Component {
|
|
|
22998
23097
|
offset: types$1.Pixel(),
|
|
22999
23098
|
onScroll: types$1.function()
|
|
23000
23099
|
});
|
|
23001
|
-
scrollbarRef = signal(
|
|
23100
|
+
scrollbarRef = signal.ref();
|
|
23002
23101
|
scrollbar;
|
|
23003
23102
|
setup() {
|
|
23004
23103
|
this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
|
|
@@ -23100,6 +23199,9 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23100
23199
|
zoneToDisplay: this.range.zone,
|
|
23101
23200
|
getFooterSize: () => 0
|
|
23102
23201
|
});
|
|
23202
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
23203
|
+
cellHoverOverlayStore.register(this);
|
|
23204
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
23103
23205
|
}
|
|
23104
23206
|
handle(cmd) {
|
|
23105
23207
|
this.cachedColDimensions = void 0;
|
|
@@ -23248,6 +23350,16 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23248
23350
|
}
|
|
23249
23351
|
return normalizedWeights;
|
|
23250
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
|
+
}
|
|
23251
23363
|
};
|
|
23252
23364
|
|
|
23253
23365
|
//#endregion
|
|
@@ -23264,10 +23376,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23264
23376
|
range: types$1.Range(),
|
|
23265
23377
|
canResizeColumns: types$1.boolean().optional(true),
|
|
23266
23378
|
onResizeColumns: types$1.function().optional(),
|
|
23267
|
-
columnWeights: types$1.array().optional()
|
|
23379
|
+
columnWeights: types$1.array().optional(),
|
|
23380
|
+
size: types$1.object()
|
|
23268
23381
|
});
|
|
23269
|
-
canvasRef = signal(
|
|
23270
|
-
containerRef = signal(
|
|
23382
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
23383
|
+
containerRef = signal.ref();
|
|
23271
23384
|
store;
|
|
23272
23385
|
dndState = proxy({ col: void 0 });
|
|
23273
23386
|
onMouseWheel;
|
|
@@ -23279,8 +23392,8 @@ var StandaloneViewport = class extends Component {
|
|
|
23279
23392
|
useChildStoreProvider([
|
|
23280
23393
|
ViewportsStore,
|
|
23281
23394
|
HoveredIconStore,
|
|
23282
|
-
HoveredTableStore,
|
|
23283
23395
|
ClickableCellsStore,
|
|
23396
|
+
CellHoverOverlayStore,
|
|
23284
23397
|
DelayedHoveredCellStore,
|
|
23285
23398
|
CellPopoverStore
|
|
23286
23399
|
]);
|
|
@@ -23289,18 +23402,10 @@ var StandaloneViewport = class extends Component {
|
|
|
23289
23402
|
this.zoomStore = useStore(ZoomStore);
|
|
23290
23403
|
this.cellPopoverStore = useStore(CellPopoverStore);
|
|
23291
23404
|
this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
|
|
23292
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
23293
|
-
this.store.setContainerSize(this.containerWidth, this.containerHeight);
|
|
23294
|
-
});
|
|
23295
|
-
useEffect(() => {
|
|
23296
|
-
const el = this.containerRef();
|
|
23297
|
-
if (el) resizeObserver.observe(el);
|
|
23298
|
-
return () => resizeObserver.disconnect();
|
|
23299
|
-
});
|
|
23300
23405
|
useEffect(() => {
|
|
23301
23406
|
this.store.setRange(this.props.range);
|
|
23302
23407
|
if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
|
|
23303
|
-
this.store.setContainerSize(this.
|
|
23408
|
+
this.store.setContainerSize(this.contentWidth, this.props.size.height);
|
|
23304
23409
|
});
|
|
23305
23410
|
useGridDrawing({
|
|
23306
23411
|
canvasRef: this.canvasRef,
|
|
@@ -23319,14 +23424,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23319
23424
|
}
|
|
23320
23425
|
});
|
|
23321
23426
|
}
|
|
23322
|
-
get
|
|
23323
|
-
return
|
|
23324
|
-
}
|
|
23325
|
-
get containerHeight() {
|
|
23326
|
-
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;
|
|
23327
23429
|
}
|
|
23328
23430
|
get hasVerticalScrollBar() {
|
|
23329
|
-
return this.
|
|
23431
|
+
return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
|
|
23330
23432
|
}
|
|
23331
23433
|
get scrollBarContainerStyle() {
|
|
23332
23434
|
return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
|
|
@@ -23378,7 +23480,7 @@ var StandaloneViewport = class extends Component {
|
|
|
23378
23480
|
};
|
|
23379
23481
|
const onMouseMove = (ev) => {
|
|
23380
23482
|
deltaX = withZoom(this.env, ev).clientX - initialX;
|
|
23381
|
-
const weightDelta = deltaX / this.
|
|
23483
|
+
const weightDelta = deltaX / this.props.size.width * totalWeight;
|
|
23382
23484
|
this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
|
|
23383
23485
|
};
|
|
23384
23486
|
startDnd(onMouseMove, onMouseUp);
|
|
@@ -23408,8 +23510,8 @@ var Select = class extends Component {
|
|
|
23408
23510
|
name: types$1.string().optional(),
|
|
23409
23511
|
title: types$1.string().optional()
|
|
23410
23512
|
});
|
|
23411
|
-
selectRef = signal(
|
|
23412
|
-
dropdownRef = signal(
|
|
23513
|
+
selectRef = signal.ref();
|
|
23514
|
+
dropdownRef = signal.ref();
|
|
23413
23515
|
state = proxy({
|
|
23414
23516
|
isPopoverOpen: false,
|
|
23415
23517
|
hoveredValue: void 0
|
|
@@ -23519,7 +23621,7 @@ var InfoPopover = class extends Component {
|
|
|
23519
23621
|
setup() {
|
|
23520
23622
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
23521
23623
|
}
|
|
23522
|
-
infoRef = signal(
|
|
23624
|
+
infoRef = signal.ref();
|
|
23523
23625
|
get popoverProps() {
|
|
23524
23626
|
return {
|
|
23525
23627
|
anchorRect: this.props.anchorRect,
|
|
@@ -23664,8 +23766,8 @@ var CarouselFigure = class extends Component {
|
|
|
23664
23766
|
isFullScreen: types$1.boolean().optional(),
|
|
23665
23767
|
openContextMenu: types$1.function().optional()
|
|
23666
23768
|
});
|
|
23667
|
-
carouselTabsRef = signal(
|
|
23668
|
-
carouselTabsDropdownRef = signal(
|
|
23769
|
+
carouselTabsRef = signal.ref();
|
|
23770
|
+
carouselTabsDropdownRef = signal.ref();
|
|
23669
23771
|
menuState = proxy({
|
|
23670
23772
|
isOpen: false,
|
|
23671
23773
|
anchorRect: null,
|
|
@@ -23733,6 +23835,19 @@ var CarouselFigure = class extends Component {
|
|
|
23733
23835
|
} else cssProperties["background-color"] = backgroundColor;
|
|
23734
23836
|
return cssPropertiesToCss(cssProperties);
|
|
23735
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
|
+
}
|
|
23736
23851
|
get title() {
|
|
23737
23852
|
return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
|
|
23738
23853
|
}
|
|
@@ -23742,6 +23857,14 @@ var CarouselFigure = class extends Component {
|
|
|
23742
23857
|
...this.carousel.title
|
|
23743
23858
|
})));
|
|
23744
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
|
+
}
|
|
23745
23868
|
updateTabsVisibility() {
|
|
23746
23869
|
const tabsContainerEl = this.carouselTabsRef();
|
|
23747
23870
|
const dropDownEl = this.carouselTabsDropdownRef();
|
|
@@ -23833,6 +23956,13 @@ var CarouselFigure = class extends Component {
|
|
|
23833
23956
|
get canResizeDataViewColumns() {
|
|
23834
23957
|
return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
23835
23958
|
}
|
|
23959
|
+
get carouselLayout() {
|
|
23960
|
+
return getCarouselLayout({
|
|
23961
|
+
...this.props.figureUI,
|
|
23962
|
+
x: 0,
|
|
23963
|
+
y: 0
|
|
23964
|
+
}, this.carousel, this.selectedCarouselItem);
|
|
23965
|
+
}
|
|
23836
23966
|
};
|
|
23837
23967
|
|
|
23838
23968
|
//#endregion
|
|
@@ -23873,7 +24003,7 @@ var ImageFigure = class extends Component {
|
|
|
23873
24003
|
figureUI: types$1.FigureUI(),
|
|
23874
24004
|
openContextMenu: types$1.function()
|
|
23875
24005
|
});
|
|
23876
|
-
menuButtonRef = signal(
|
|
24006
|
+
menuButtonRef = signal.ref();
|
|
23877
24007
|
showMenu(ev) {
|
|
23878
24008
|
if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
|
|
23879
24009
|
figureId: this.props.figureUI.id,
|
|
@@ -23901,7 +24031,8 @@ figureRegistry.add("chart", {
|
|
|
23901
24031
|
menuBuilder: getChartMenuActions,
|
|
23902
24032
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23903
24033
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23904
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24034
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24035
|
+
isThemeDependant: true
|
|
23905
24036
|
});
|
|
23906
24037
|
figureRegistry.add("image", {
|
|
23907
24038
|
Component: ImageFigure,
|
|
@@ -23910,14 +24041,16 @@ figureRegistry.add("image", {
|
|
|
23910
24041
|
borderWidth: () => 0,
|
|
23911
24042
|
hasShadow: () => false,
|
|
23912
24043
|
isRounded: () => false,
|
|
23913
|
-
menuBuilder: getImageMenuActions
|
|
24044
|
+
menuBuilder: getImageMenuActions,
|
|
24045
|
+
isThemeDependant: false
|
|
23914
24046
|
});
|
|
23915
24047
|
figureRegistry.add("carousel", {
|
|
23916
24048
|
Component: CarouselFigure,
|
|
23917
24049
|
menuBuilder: getCarouselMenuActions,
|
|
23918
24050
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23919
24051
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23920
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24052
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24053
|
+
isThemeDependant: true
|
|
23921
24054
|
});
|
|
23922
24055
|
|
|
23923
24056
|
//#endregion
|
|
@@ -23939,10 +24072,13 @@ var FigureComponent = class extends Component {
|
|
|
23939
24072
|
anchorRect: null,
|
|
23940
24073
|
menuItems: []
|
|
23941
24074
|
});
|
|
23942
|
-
figureRef = signal(
|
|
24075
|
+
figureRef = signal.ref();
|
|
23943
24076
|
get isSelected() {
|
|
23944
24077
|
return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
|
|
23945
24078
|
}
|
|
24079
|
+
get isThemeDependant() {
|
|
24080
|
+
return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
|
|
24081
|
+
}
|
|
23946
24082
|
get figureRegistry() {
|
|
23947
24083
|
return figureRegistry;
|
|
23948
24084
|
}
|
|
@@ -29541,6 +29677,35 @@ async function interactivePasteFromOS(env, target, parsedClipboardContent, paste
|
|
|
29541
29677
|
}));
|
|
29542
29678
|
}
|
|
29543
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
|
+
|
|
29544
29709
|
//#endregion
|
|
29545
29710
|
//#region src/helpers/figures/charts/smart_chart_engine.ts
|
|
29546
29711
|
const DEFAULT_BAR_CHART_CONFIG = {
|
|
@@ -29571,9 +29736,6 @@ const DEFAULT_LINE_CHART_CONFIG = {
|
|
|
29571
29736
|
labelsAsText: false,
|
|
29572
29737
|
humanize: true
|
|
29573
29738
|
};
|
|
29574
|
-
function getUnboundRange(getters, zone) {
|
|
29575
|
-
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29576
|
-
}
|
|
29577
29739
|
function detectColumnType(cells) {
|
|
29578
29740
|
if (!cells.length) return "empty";
|
|
29579
29741
|
const counts = {
|
|
@@ -29620,14 +29782,6 @@ function getCellStats(getters, zone) {
|
|
|
29620
29782
|
totalCount: values.length
|
|
29621
29783
|
};
|
|
29622
29784
|
}
|
|
29623
|
-
function isDatasetTitled(getters, column) {
|
|
29624
|
-
const titleCell = getters.getEvaluatedCell({
|
|
29625
|
-
sheetId: getters.getActiveSheetId(),
|
|
29626
|
-
col: column.zone.left,
|
|
29627
|
-
row: column.zone.top
|
|
29628
|
-
});
|
|
29629
|
-
return !["number", "empty"].includes(titleCell.type);
|
|
29630
|
-
}
|
|
29631
29785
|
/**
|
|
29632
29786
|
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
29633
29787
|
* - If the column contains a single cell, create a scorecard.
|
|
@@ -29639,8 +29793,7 @@ function isDatasetTitled(getters, column) {
|
|
|
29639
29793
|
function buildSingleColumnChart(column, getters) {
|
|
29640
29794
|
const { type, zone } = column;
|
|
29641
29795
|
const sheetId = getters.getActiveSheetId();
|
|
29642
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
29643
|
-
const dataRange = getUnboundRange(getters, zone);
|
|
29796
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
|
|
29644
29797
|
const titleCell = getters.getEvaluatedCell({
|
|
29645
29798
|
sheetId,
|
|
29646
29799
|
col: zone.left,
|
|
@@ -29651,14 +29804,7 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29651
29804
|
case "percentage": return {
|
|
29652
29805
|
type: "pie",
|
|
29653
29806
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29654
|
-
dataSource:
|
|
29655
|
-
type: "range",
|
|
29656
|
-
dataSets: [{
|
|
29657
|
-
dataRange,
|
|
29658
|
-
dataSetId: "0"
|
|
29659
|
-
}],
|
|
29660
|
-
dataSetsHaveTitle
|
|
29661
|
-
},
|
|
29807
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29662
29808
|
dataSetStyles: {},
|
|
29663
29809
|
legendPosition: "none"
|
|
29664
29810
|
};
|
|
@@ -29669,45 +29815,28 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29669
29815
|
return {
|
|
29670
29816
|
type: "pie",
|
|
29671
29817
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
29672
|
-
dataSource:
|
|
29673
|
-
type: "range",
|
|
29674
|
-
dataSets: [{
|
|
29675
|
-
dataRange,
|
|
29676
|
-
dataSetId: "0"
|
|
29677
|
-
}],
|
|
29678
|
-
labelRange: dataRange,
|
|
29679
|
-
dataSetsHaveTitle: hasUniqueTitle
|
|
29680
|
-
},
|
|
29818
|
+
dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
|
|
29681
29819
|
dataSetStyles: {},
|
|
29682
29820
|
aggregated: true,
|
|
29683
29821
|
legendPosition: "top"
|
|
29684
29822
|
};
|
|
29685
|
-
case "date":
|
|
29686
|
-
|
|
29687
|
-
|
|
29688
|
-
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
29697
|
-
verticalGroupBy: "month_number"
|
|
29698
|
-
};
|
|
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
|
+
}
|
|
29699
29835
|
}
|
|
29700
29836
|
return {
|
|
29701
29837
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29702
29838
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29703
|
-
dataSource:
|
|
29704
|
-
type: "range",
|
|
29705
|
-
dataSets: [{
|
|
29706
|
-
dataRange,
|
|
29707
|
-
dataSetId: "0"
|
|
29708
|
-
}],
|
|
29709
|
-
dataSetsHaveTitle
|
|
29710
|
-
},
|
|
29839
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29711
29840
|
dataSetStyles: {}
|
|
29712
29841
|
};
|
|
29713
29842
|
}
|
|
@@ -29725,15 +29854,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29725
29854
|
if (columns[1].type === "percentage") return {
|
|
29726
29855
|
type: "pie",
|
|
29727
29856
|
title: {},
|
|
29728
|
-
dataSource:
|
|
29729
|
-
type: "range",
|
|
29730
|
-
dataSets: [{
|
|
29731
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29732
|
-
dataSetId: "0"
|
|
29733
|
-
}],
|
|
29734
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29735
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29736
|
-
},
|
|
29857
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29737
29858
|
dataSetStyles: {},
|
|
29738
29859
|
aggregated: true,
|
|
29739
29860
|
legendPosition: "none"
|
|
@@ -29741,15 +29862,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29741
29862
|
if (columns[0].type === "number" && columns[1].type === "number") return {
|
|
29742
29863
|
type: "scatter",
|
|
29743
29864
|
title: {},
|
|
29744
|
-
dataSource:
|
|
29745
|
-
type: "range",
|
|
29746
|
-
dataSets: [{
|
|
29747
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29748
|
-
dataSetId: "0"
|
|
29749
|
-
}],
|
|
29750
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29751
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29752
|
-
},
|
|
29865
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29753
29866
|
dataSetStyles: {},
|
|
29754
29867
|
labelsAsText: false,
|
|
29755
29868
|
legendPosition: "none"
|
|
@@ -29757,49 +29870,25 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29757
29870
|
if (columns[0].type === "date" && columns[1].type === "number") return {
|
|
29758
29871
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29759
29872
|
type: "line",
|
|
29760
|
-
dataSource:
|
|
29761
|
-
type: "range",
|
|
29762
|
-
dataSets: [{
|
|
29763
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29764
|
-
dataSetId: "0"
|
|
29765
|
-
}],
|
|
29766
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29767
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
|
|
29768
|
-
},
|
|
29873
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29769
29874
|
dataSetStyles: {}
|
|
29770
29875
|
};
|
|
29771
29876
|
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
29772
29877
|
const textColumn = columns[0];
|
|
29773
29878
|
const numberColumn = columns[1];
|
|
29774
29879
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
29775
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
29880
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
|
|
29776
29881
|
if (uniqueCount !== totalCount) return {
|
|
29777
29882
|
type: "treemap",
|
|
29778
29883
|
title: {},
|
|
29779
|
-
dataSource:
|
|
29780
|
-
type: "range",
|
|
29781
|
-
dataSets: [{
|
|
29782
|
-
dataRange: getUnboundRange(getters, textColumn.zone),
|
|
29783
|
-
dataSetId: "0"
|
|
29784
|
-
}],
|
|
29785
|
-
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
29786
|
-
dataSetsHaveTitle
|
|
29787
|
-
},
|
|
29884
|
+
dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
|
|
29788
29885
|
dataSetStyles: {},
|
|
29789
29886
|
legendPosition: "none"
|
|
29790
29887
|
};
|
|
29791
29888
|
}
|
|
29792
29889
|
return {
|
|
29793
29890
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29794
|
-
dataSource:
|
|
29795
|
-
type: "range",
|
|
29796
|
-
dataSets: [{
|
|
29797
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29798
|
-
dataSetId: "0"
|
|
29799
|
-
}],
|
|
29800
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29801
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29802
|
-
},
|
|
29891
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29803
29892
|
dataSetStyles: {}
|
|
29804
29893
|
};
|
|
29805
29894
|
}
|
|
@@ -29813,42 +29902,26 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29813
29902
|
*/
|
|
29814
29903
|
function buildMultiColumnChart(columns, getters) {
|
|
29815
29904
|
if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
|
|
29816
|
-
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
29905
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
|
|
29817
29906
|
const lastColumn = columns[columns.length - 1];
|
|
29818
29907
|
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
29819
29908
|
if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
|
|
29820
|
-
const dataSets = columnsExceptLast.map(({ zone }, i) => (
|
|
29821
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29822
|
-
dataSetId: i.toString()
|
|
29823
|
-
}));
|
|
29909
|
+
const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29824
29910
|
return {
|
|
29825
29911
|
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
29826
29912
|
title: {},
|
|
29827
|
-
dataSource:
|
|
29828
|
-
type: "range",
|
|
29829
|
-
dataSets,
|
|
29830
|
-
dataSetsHaveTitle,
|
|
29831
|
-
labelRange: getUnboundRange(getters, lastColumn.zone)
|
|
29832
|
-
},
|
|
29913
|
+
dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
|
|
29833
29914
|
dataSetStyles: {},
|
|
29834
29915
|
legendPosition: "none"
|
|
29835
29916
|
};
|
|
29836
29917
|
}
|
|
29837
29918
|
const firstColumn = columns[0];
|
|
29838
29919
|
const columnsExceptFirst = columns.slice(1);
|
|
29839
|
-
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => (
|
|
29840
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29841
|
-
dataSetId: i.toString()
|
|
29842
|
-
}));
|
|
29920
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29843
29921
|
if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
|
|
29844
29922
|
type: "pie",
|
|
29845
29923
|
title: {},
|
|
29846
|
-
dataSource:
|
|
29847
|
-
type: "range",
|
|
29848
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29849
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29850
|
-
dataSetsHaveTitle
|
|
29851
|
-
},
|
|
29924
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29852
29925
|
dataSetStyles: {},
|
|
29853
29926
|
aggregated: false,
|
|
29854
29927
|
legendPosition: "top"
|
|
@@ -29856,23 +29929,13 @@ function buildMultiColumnChart(columns, getters) {
|
|
|
29856
29929
|
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
|
|
29857
29930
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29858
29931
|
type: "line",
|
|
29859
|
-
dataSource:
|
|
29860
|
-
type: "range",
|
|
29861
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29862
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29863
|
-
dataSetsHaveTitle
|
|
29864
|
-
},
|
|
29932
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29865
29933
|
dataSetStyles: {},
|
|
29866
29934
|
legendPosition: "top"
|
|
29867
29935
|
};
|
|
29868
29936
|
return {
|
|
29869
29937
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29870
|
-
dataSource:
|
|
29871
|
-
type: "range",
|
|
29872
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29873
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29874
|
-
dataSetsHaveTitle
|
|
29875
|
-
},
|
|
29938
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29876
29939
|
dataSetStyles: {},
|
|
29877
29940
|
legendPosition: "top"
|
|
29878
29941
|
};
|
|
@@ -29899,17 +29962,10 @@ function buildScorecard(zone, getters) {
|
|
|
29899
29962
|
function getSmartChartDefinition(zones, getters) {
|
|
29900
29963
|
const columns = categorizeColumns(zones, getters);
|
|
29901
29964
|
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
29902
|
-
const dataSets = columns.map(({ zone }, i) => (
|
|
29903
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29904
|
-
dataSetId: i.toString()
|
|
29905
|
-
}));
|
|
29965
|
+
const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29906
29966
|
return {
|
|
29907
29967
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29908
|
-
dataSource:
|
|
29909
|
-
type: "range",
|
|
29910
|
-
dataSets,
|
|
29911
|
-
dataSetsHaveTitle: false
|
|
29912
|
-
}
|
|
29968
|
+
dataSource: rangeSource(dataSets, false)
|
|
29913
29969
|
};
|
|
29914
29970
|
}
|
|
29915
29971
|
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
@@ -31597,7 +31653,7 @@ var SelectionInput = class extends Component {
|
|
|
31597
31653
|
dragAndDrop = useDragAndDropListItems();
|
|
31598
31654
|
focusedInputRef = signal.ref(HTMLInputElement);
|
|
31599
31655
|
unfocusedInputRef = signal.ref(HTMLInputElement);
|
|
31600
|
-
selectionRef = signal(
|
|
31656
|
+
selectionRef = signal.ref();
|
|
31601
31657
|
DOMFocusableElementStore;
|
|
31602
31658
|
store;
|
|
31603
31659
|
get ranges() {
|
|
@@ -32104,7 +32160,7 @@ var ColorPickerWidget = class extends Component {
|
|
|
32104
32160
|
dropdownMaxHeight: types$1.Pixel().optional(),
|
|
32105
32161
|
class: types$1.string().optional()
|
|
32106
32162
|
});
|
|
32107
|
-
colorPickerButtonRef = signal(
|
|
32163
|
+
colorPickerButtonRef = signal.ref();
|
|
32108
32164
|
get iconStyle() {
|
|
32109
32165
|
return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
|
|
32110
32166
|
}
|
|
@@ -32132,8 +32188,8 @@ var NumberEditor = class extends Component {
|
|
|
32132
32188
|
});
|
|
32133
32189
|
dropdown = proxy({ isOpen: false });
|
|
32134
32190
|
inputRef = signal.ref(HTMLInputElement);
|
|
32135
|
-
rootEditorRef = signal(
|
|
32136
|
-
valueListRef = signal(
|
|
32191
|
+
rootEditorRef = signal.ref();
|
|
32192
|
+
valueListRef = signal.ref();
|
|
32137
32193
|
DOMFocusableElementStore;
|
|
32138
32194
|
setup() {
|
|
32139
32195
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -32439,7 +32495,7 @@ var ChartTypePickerPopover = class extends Component {
|
|
|
32439
32495
|
});
|
|
32440
32496
|
categories = chartCategories;
|
|
32441
32497
|
chartTypeByCategories = {};
|
|
32442
|
-
popoverRef = signal(
|
|
32498
|
+
popoverRef = signal.ref();
|
|
32443
32499
|
setup() {
|
|
32444
32500
|
useListener(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
|
|
32445
32501
|
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
@@ -32464,7 +32520,7 @@ var CogWheelMenu = class extends Component {
|
|
|
32464
32520
|
static template = "o-spreadsheet-CogWheelMenu";
|
|
32465
32521
|
static components = { MenuPopover };
|
|
32466
32522
|
props = useProps({ items: types$1.array(types$1.ActionSpec()) });
|
|
32467
|
-
buttonRef = signal(
|
|
32523
|
+
buttonRef = signal.ref();
|
|
32468
32524
|
menuState = proxy({
|
|
32469
32525
|
isOpen: false,
|
|
32470
32526
|
anchorRect: null,
|
|
@@ -32508,8 +32564,8 @@ var CarouselPanel = class extends Component {
|
|
|
32508
32564
|
});
|
|
32509
32565
|
DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
|
|
32510
32566
|
dragAndDrop = useDragAndDropListItems();
|
|
32511
|
-
previewListRef = signal(
|
|
32512
|
-
addChartButton = signal(
|
|
32567
|
+
previewListRef = signal.ref();
|
|
32568
|
+
addChartButton = signal.ref(HTMLButtonElement);
|
|
32513
32569
|
state = proxy({
|
|
32514
32570
|
popoverProps: void 0,
|
|
32515
32571
|
currentRange: void 0
|
|
@@ -32953,7 +33009,7 @@ var BarConfigPanel = class extends GenericChartConfigPanel {
|
|
|
32953
33009
|
var Collapse = class extends Component {
|
|
32954
33010
|
static template = "o-spreadsheet-Collapse";
|
|
32955
33011
|
props = useProps({ isCollapsed: types$1.boolean() });
|
|
32956
|
-
contentRef = signal(
|
|
33012
|
+
contentRef = signal.ref();
|
|
32957
33013
|
setup() {
|
|
32958
33014
|
onMounted(() => {
|
|
32959
33015
|
if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
|
|
@@ -33006,7 +33062,7 @@ var ChartAnnotation = class extends Component {
|
|
|
33006
33062
|
TextInput
|
|
33007
33063
|
};
|
|
33008
33064
|
props = useProps(chartSidePanelPropsDefinition);
|
|
33009
|
-
editorRef = signal(
|
|
33065
|
+
editorRef = signal.ref();
|
|
33010
33066
|
state = proxy({
|
|
33011
33067
|
annotationTextInput: this.props.definition.annotationText || "",
|
|
33012
33068
|
isOverflowing: false
|
|
@@ -33328,7 +33384,7 @@ var RoundColorPicker = class extends Component {
|
|
|
33328
33384
|
onColorPicked: types$1.function(),
|
|
33329
33385
|
disableNoColor: types$1.boolean().optional()
|
|
33330
33386
|
});
|
|
33331
|
-
colorPickerButtonRef = signal(
|
|
33387
|
+
colorPickerButtonRef = signal.ref();
|
|
33332
33388
|
state;
|
|
33333
33389
|
setup() {
|
|
33334
33390
|
this.state = proxy({ pickerOpened: false });
|
|
@@ -36170,6 +36226,7 @@ var helpers_index_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
36170
36226
|
getDefinedAxis: () => getDefinedAxis,
|
|
36171
36227
|
getPieColors: () => getPieColors,
|
|
36172
36228
|
isTrendLineAxis: () => isTrendLineAxis,
|
|
36229
|
+
limitChartConfigDataPoints: () => limitChartConfigDataPoints,
|
|
36173
36230
|
shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
|
|
36174
36231
|
toExcelDataset: () => toExcelDataset,
|
|
36175
36232
|
toExcelLabelRange: () => toExcelLabelRange,
|
|
@@ -36940,7 +36997,7 @@ var ColorScalePicker = class extends Component {
|
|
|
36940
36997
|
popoverProps: void 0,
|
|
36941
36998
|
popoverStyle: ""
|
|
36942
36999
|
});
|
|
36943
|
-
popoverRef = signal(
|
|
37000
|
+
popoverRef = signal.ref(HTMLTableElement);
|
|
36944
37001
|
setup() {
|
|
36945
37002
|
useExternalListener(window, "click", this.closePopover);
|
|
36946
37003
|
}
|
|
@@ -37189,7 +37246,7 @@ var TextValueProvider = class extends Component {
|
|
|
37189
37246
|
onValueSelected: types$1.function(),
|
|
37190
37247
|
onValueHovered: types$1.function()
|
|
37191
37248
|
});
|
|
37192
|
-
autoCompleteListRef = signal(
|
|
37249
|
+
autoCompleteListRef = signal.ref();
|
|
37193
37250
|
setup() {
|
|
37194
37251
|
useEffect(() => {
|
|
37195
37252
|
const selectedIndex = this.props.selectedIndex;
|
|
@@ -37480,7 +37537,7 @@ var SpeechBubble = class extends Component {
|
|
|
37480
37537
|
anchorRect: types$1.Rect()
|
|
37481
37538
|
});
|
|
37482
37539
|
spreadsheetRect = useSpreadsheetRect();
|
|
37483
|
-
bubbleRef = signal(
|
|
37540
|
+
bubbleRef = signal.ref();
|
|
37484
37541
|
setup() {
|
|
37485
37542
|
useEffect(() => {
|
|
37486
37543
|
const el = this.bubbleRef();
|
|
@@ -37525,8 +37582,8 @@ var Composer = class extends Component {
|
|
|
37525
37582
|
showAssistant: types$1.boolean().optional(true)
|
|
37526
37583
|
});
|
|
37527
37584
|
DOMFocusableElementStore;
|
|
37528
|
-
composerRef = signal(
|
|
37529
|
-
containerRef = signal(
|
|
37585
|
+
composerRef = signal.ref();
|
|
37586
|
+
containerRef = signal.ref();
|
|
37530
37587
|
contentHelper = new ContentEditableHelper(this.composerRef());
|
|
37531
37588
|
composerState = proxy({
|
|
37532
37589
|
positionStart: 0,
|
|
@@ -40028,7 +40085,7 @@ var ChartTypePicker = class extends Component {
|
|
|
40028
40085
|
chartId: types$1.UID(),
|
|
40029
40086
|
chartPanelStore: types$1.Store()
|
|
40030
40087
|
});
|
|
40031
|
-
selectRef = signal(
|
|
40088
|
+
selectRef = signal.ref();
|
|
40032
40089
|
state = proxy({ popoverProps: void 0 });
|
|
40033
40090
|
getSupportedChartTypes() {
|
|
40034
40091
|
let supportedTypes;
|
|
@@ -40596,7 +40653,7 @@ var ColumnStatsPanel = class extends Component {
|
|
|
40596
40653
|
highlightPositions: []
|
|
40597
40654
|
});
|
|
40598
40655
|
store;
|
|
40599
|
-
chartCanvasRef = signal(
|
|
40656
|
+
chartCanvasRef = signal.ref(HTMLCanvasElement);
|
|
40600
40657
|
chart;
|
|
40601
40658
|
setup() {
|
|
40602
40659
|
this.store = useStore(ColumnStatisticsStore);
|
|
@@ -42024,7 +42081,7 @@ var ConditionalFormatPreview = class extends Component {
|
|
|
42024
42081
|
class: types$1.string()
|
|
42025
42082
|
});
|
|
42026
42083
|
icons = ICONS;
|
|
42027
|
-
cfPreviewRef = signal(
|
|
42084
|
+
cfPreviewRef = signal.ref();
|
|
42028
42085
|
setup() {
|
|
42029
42086
|
useHighlightsOnHover(this.cfPreviewRef, this);
|
|
42030
42087
|
}
|
|
@@ -42081,7 +42138,7 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42081
42138
|
static components = { ConditionalFormatPreview };
|
|
42082
42139
|
props = useProps({ onCloseSidePanel: types$1.function() });
|
|
42083
42140
|
dragAndDrop = useDragAndDropListItems();
|
|
42084
|
-
cfListRef = signal(
|
|
42141
|
+
cfListRef = signal.ref();
|
|
42085
42142
|
get conditionalFormats() {
|
|
42086
42143
|
return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
|
|
42087
42144
|
}
|
|
@@ -42141,6 +42198,1477 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42141
42198
|
}
|
|
42142
42199
|
};
|
|
42143
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
|
+
|
|
42144
43672
|
//#endregion
|
|
42145
43673
|
//#region src/registries/data_source_registry.ts
|
|
42146
43674
|
const unusedDataSourceRegistry = new Registry();
|
|
@@ -42217,7 +43745,7 @@ var DataSourcesCleanup = class extends Component {
|
|
|
42217
43745
|
var DataValidationPreview = class extends Component {
|
|
42218
43746
|
static template = "o-spreadsheet-DataValidationPreview";
|
|
42219
43747
|
props = useProps({ rule: types$1.DataValidationRule() });
|
|
42220
|
-
dvPreviewRef = signal(
|
|
43748
|
+
dvPreviewRef = signal.ref();
|
|
42221
43749
|
setup() {
|
|
42222
43750
|
useHighlightsOnHover(this.dvPreviewRef, this);
|
|
42223
43751
|
}
|
|
@@ -43716,7 +45244,7 @@ var NamedRangePreview = class extends Component {
|
|
|
43716
45244
|
};
|
|
43717
45245
|
props = useProps({ namedRange: types$1.NamedRange() });
|
|
43718
45246
|
state = proxy({});
|
|
43719
|
-
namedRangePreviewRef = signal(
|
|
45247
|
+
namedRangePreviewRef = signal.ref();
|
|
43720
45248
|
setup() {
|
|
43721
45249
|
useHighlightsOnHover(this.namedRangePreviewRef, this);
|
|
43722
45250
|
}
|
|
@@ -44207,7 +45735,7 @@ var FilterMenuValueItem = class extends Component {
|
|
|
44207
45735
|
onClick: types$1.function(),
|
|
44208
45736
|
scrolledTo: types$1.or([types$1.literal("top"), types$1.literal("bottom")]).optional()
|
|
44209
45737
|
});
|
|
44210
|
-
itemRef = signal(
|
|
45738
|
+
itemRef = signal.ref();
|
|
44211
45739
|
setup() {
|
|
44212
45740
|
onWillPatch(() => {
|
|
44213
45741
|
if (this.props.scrolledTo) this.scrollListToSelectedValue();
|
|
@@ -44447,7 +45975,7 @@ var PivotFilterEditor = class extends Component {
|
|
|
44447
45975
|
onFiltersUpdated: types$1.function()
|
|
44448
45976
|
});
|
|
44449
45977
|
state;
|
|
44450
|
-
buttonFilter = signal(
|
|
45978
|
+
buttonFilter = signal.ref();
|
|
44451
45979
|
popover;
|
|
44452
45980
|
setup() {
|
|
44453
45981
|
useExternalListener(window, "click", (ev) => {
|
|
@@ -44576,11 +46104,11 @@ var AddDimensionButton = class extends Component {
|
|
|
44576
46104
|
onFieldPicked: types$1.function(),
|
|
44577
46105
|
fields: types$1.array()
|
|
44578
46106
|
});
|
|
44579
|
-
buttonRef = signal(
|
|
46107
|
+
buttonRef = signal.ref(HTMLButtonElement);
|
|
44580
46108
|
popover = proxy({ isOpen: false });
|
|
44581
46109
|
search = proxy({ input: "" });
|
|
44582
46110
|
autoComplete;
|
|
44583
|
-
autofocusRef = signal(
|
|
46111
|
+
autofocusRef = signal.ref(HTMLInputElement);
|
|
44584
46112
|
setup() {
|
|
44585
46113
|
this.autoComplete = useLocalStore(AutoCompleteStore);
|
|
44586
46114
|
this.autoComplete.useProvider(this.getProvider());
|
|
@@ -44989,7 +46517,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
44989
46517
|
getScrollableContainerEl: types$1.function().optional(),
|
|
44990
46518
|
pivotId: types$1.UID()
|
|
44991
46519
|
});
|
|
44992
|
-
dimensionsRef = signal(
|
|
46520
|
+
dimensionsRef = signal.ref();
|
|
44993
46521
|
dragAndDrop = useDragAndDropListItems();
|
|
44994
46522
|
AGGREGATORS = AGGREGATORS;
|
|
44995
46523
|
isDateOrDatetimeField = isDateOrDatetimeField;
|
|
@@ -45060,16 +46588,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
45060
46588
|
});
|
|
45061
46589
|
}
|
|
45062
46590
|
getDimensionElementsRects() {
|
|
45063
|
-
return Array.from(this.dimensionsRef().children).map((el) =>
|
|
45064
|
-
const style = getComputedStyle(el);
|
|
45065
|
-
const rect = el.getBoundingClientRect();
|
|
45066
|
-
return {
|
|
45067
|
-
x: rect.x,
|
|
45068
|
-
y: rect.y,
|
|
45069
|
-
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
45070
|
-
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
45071
|
-
};
|
|
45072
|
-
});
|
|
46591
|
+
return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
|
|
45073
46592
|
}
|
|
45074
46593
|
removeDimension(dimension) {
|
|
45075
46594
|
const { columns, rows } = this.props.definition;
|
|
@@ -45634,7 +47153,7 @@ var PivotSpreadsheetSidePanel = class extends Component {
|
|
|
45634
47153
|
});
|
|
45635
47154
|
store;
|
|
45636
47155
|
state;
|
|
45637
|
-
pivotSidePanelRef = signal(
|
|
47156
|
+
pivotSidePanelRef = signal.ref();
|
|
45638
47157
|
setup() {
|
|
45639
47158
|
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
|
|
45640
47159
|
this.state = proxy({
|
|
@@ -46874,7 +48393,7 @@ var TableStylePreview = class extends Component {
|
|
|
46874
48393
|
selected: types$1.boolean().optional(),
|
|
46875
48394
|
onClick: types$1.function().optional()
|
|
46876
48395
|
});
|
|
46877
|
-
canvasRef = signal(
|
|
48396
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
46878
48397
|
menu = proxy({
|
|
46879
48398
|
isOpen: false,
|
|
46880
48399
|
anchorRect: null,
|
|
@@ -46968,7 +48487,7 @@ var TableStylesPopover = class extends Component {
|
|
|
46968
48487
|
tableStyles: types$1.RecordOf(),
|
|
46969
48488
|
type: types$1.or([types$1.literal("table"), types$1.literal("pivot")])
|
|
46970
48489
|
});
|
|
46971
|
-
tableStyleListRef = signal(
|
|
48490
|
+
tableStyleListRef = signal.ref();
|
|
46972
48491
|
state = proxy({ selectedCategory: this.initialSelectedCategory });
|
|
46973
48492
|
setup() {
|
|
46974
48493
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
@@ -48339,6 +49858,10 @@ sidePanelRegistry.add("ColumnStats", {
|
|
|
48339
49858
|
title: _t("Column statistics"),
|
|
48340
49859
|
Body: ColumnStatsPanel
|
|
48341
49860
|
});
|
|
49861
|
+
sidePanelRegistry.add("DataAnalysisPanel", {
|
|
49862
|
+
title: _t("Data analysis"),
|
|
49863
|
+
Body: DataAnalysisPanel
|
|
49864
|
+
});
|
|
48342
49865
|
sidePanelRegistry.add("TableSidePanel", {
|
|
48343
49866
|
title: _t("Edit table"),
|
|
48344
49867
|
Body: TablePanel,
|
|
@@ -48446,6 +49969,7 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48446
49969
|
"changePanelSize",
|
|
48447
49970
|
"resetPanelSize",
|
|
48448
49971
|
"togglePinPanel",
|
|
49972
|
+
"togglePinnedSidePanel",
|
|
48449
49973
|
"closeMainPanel",
|
|
48450
49974
|
"changeSpreadsheetWidth",
|
|
48451
49975
|
"toggleCollapsePanel"
|
|
@@ -48597,6 +50121,19 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48597
50121
|
this.secondaryPanel = void 0;
|
|
48598
50122
|
}
|
|
48599
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
|
+
}
|
|
48600
50137
|
toggleCollapsePanel(panel) {
|
|
48601
50138
|
const panelInfo = this[panel];
|
|
48602
50139
|
if (!panelInfo) return;
|
|
@@ -48975,6 +50512,219 @@ var MenuItemRegistry = class extends Registry {
|
|
|
48975
50512
|
}
|
|
48976
50513
|
};
|
|
48977
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
|
+
|
|
48978
50728
|
//#endregion
|
|
48979
50729
|
//#region src/helpers/ui/merge_interactive.ts
|
|
48980
50730
|
const AddMergeInteractiveContent = {
|
|
@@ -49189,6 +50939,10 @@ cellMenuRegistry.add("cut", {
|
|
|
49189
50939
|
}).addChild("paste_format_only", ["paste_special"], {
|
|
49190
50940
|
...pasteSpecialFormat,
|
|
49191
50941
|
sequence: 20
|
|
50942
|
+
}).add("data_analysis", {
|
|
50943
|
+
...dataAnalysis,
|
|
50944
|
+
sequence: 50,
|
|
50945
|
+
separator: true
|
|
49192
50946
|
}).add("add_row_before", {
|
|
49193
50947
|
...cellInsertRowsBefore,
|
|
49194
50948
|
sequence: 70
|
|
@@ -49296,211 +51050,6 @@ cellMenuRegistry.add("cut", {
|
|
|
49296
51050
|
sequence: 30
|
|
49297
51051
|
});
|
|
49298
51052
|
|
|
49299
|
-
//#endregion
|
|
49300
|
-
//#region src/helpers/sort_interactive.ts
|
|
49301
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
49302
|
-
let multiColumns = zone.right > zone.left;
|
|
49303
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
49304
|
-
multiColumns = false;
|
|
49305
|
-
let table;
|
|
49306
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
49307
|
-
table = [];
|
|
49308
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
49309
|
-
const merge = env.model.getters.getMerge({
|
|
49310
|
-
sheetId,
|
|
49311
|
-
col,
|
|
49312
|
-
row
|
|
49313
|
-
});
|
|
49314
|
-
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
49315
|
-
}
|
|
49316
|
-
if (table.length >= 2) {
|
|
49317
|
-
multiColumns = true;
|
|
49318
|
-
break;
|
|
49319
|
-
}
|
|
49320
|
-
}
|
|
49321
|
-
}
|
|
49322
|
-
if (multiColumns) {
|
|
49323
|
-
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49324
|
-
return;
|
|
49325
|
-
}
|
|
49326
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
49327
|
-
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49328
|
-
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));
|
|
49329
|
-
}
|
|
49330
|
-
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
49331
|
-
const result = env.model.dispatch("SORT_CELLS", {
|
|
49332
|
-
sheetId,
|
|
49333
|
-
col: anchor.col,
|
|
49334
|
-
row: anchor.row,
|
|
49335
|
-
zone,
|
|
49336
|
-
sortDirection,
|
|
49337
|
-
sortOptions
|
|
49338
|
-
});
|
|
49339
|
-
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
49340
|
-
const { col, row } = anchor;
|
|
49341
|
-
env.model.selection.selectZone({
|
|
49342
|
-
cell: {
|
|
49343
|
-
col,
|
|
49344
|
-
row
|
|
49345
|
-
},
|
|
49346
|
-
zone
|
|
49347
|
-
});
|
|
49348
|
-
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
49349
|
-
}
|
|
49350
|
-
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
49351
|
-
const { col, row } = anchor;
|
|
49352
|
-
env.model.selection.selectZone({
|
|
49353
|
-
cell: {
|
|
49354
|
-
col,
|
|
49355
|
-
row
|
|
49356
|
-
},
|
|
49357
|
-
zone
|
|
49358
|
-
});
|
|
49359
|
-
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
49360
|
-
}
|
|
49361
|
-
}
|
|
49362
|
-
|
|
49363
|
-
//#endregion
|
|
49364
|
-
//#region src/stores/trim_whitespace_store.ts
|
|
49365
|
-
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
49366
|
-
mutators = ["trimWhitespace"];
|
|
49367
|
-
notificationStore = this.get(NotificationStore);
|
|
49368
|
-
trimWhitespace() {
|
|
49369
|
-
this.model.dispatch("TRIM_WHITESPACE");
|
|
49370
|
-
}
|
|
49371
|
-
handle(cmd) {
|
|
49372
|
-
switch (cmd.type) {
|
|
49373
|
-
case "TRIM_WHITESPACE":
|
|
49374
|
-
this._trimWhitespace();
|
|
49375
|
-
break;
|
|
49376
|
-
}
|
|
49377
|
-
}
|
|
49378
|
-
_trimWhitespace() {
|
|
49379
|
-
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
49380
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
49381
|
-
let count = 0;
|
|
49382
|
-
for (const { col, row } of zones.map(positions).flat()) {
|
|
49383
|
-
const cell = this.getters.getCell({
|
|
49384
|
-
col,
|
|
49385
|
-
row,
|
|
49386
|
-
sheetId
|
|
49387
|
-
});
|
|
49388
|
-
if (!cell) continue;
|
|
49389
|
-
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
49390
|
-
const trimmedContent = trimContent(currentContent);
|
|
49391
|
-
if (trimmedContent !== currentContent) {
|
|
49392
|
-
count += 1;
|
|
49393
|
-
this.model.dispatch("UPDATE_CELL", {
|
|
49394
|
-
sheetId,
|
|
49395
|
-
col,
|
|
49396
|
-
row,
|
|
49397
|
-
content: trimmedContent
|
|
49398
|
-
});
|
|
49399
|
-
}
|
|
49400
|
-
}
|
|
49401
|
-
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
49402
|
-
this.notificationStore.notifyUser({
|
|
49403
|
-
type: "info",
|
|
49404
|
-
text,
|
|
49405
|
-
sticky: false
|
|
49406
|
-
});
|
|
49407
|
-
}
|
|
49408
|
-
};
|
|
49409
|
-
|
|
49410
|
-
//#endregion
|
|
49411
|
-
//#region src/actions/data_actions.ts
|
|
49412
|
-
const sortRange = {
|
|
49413
|
-
name: _t("Sort range"),
|
|
49414
|
-
isVisible: IS_ONLY_ONE_RANGE,
|
|
49415
|
-
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
49416
|
-
};
|
|
49417
|
-
const sortAscending = {
|
|
49418
|
-
name: _t("Ascending (A ⟶ Z)"),
|
|
49419
|
-
execute: (env) => {
|
|
49420
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49421
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
49422
|
-
},
|
|
49423
|
-
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
49424
|
-
};
|
|
49425
|
-
const dataCleanup = {
|
|
49426
|
-
name: _t("Data cleanup"),
|
|
49427
|
-
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
49428
|
-
};
|
|
49429
|
-
const removeDuplicates = {
|
|
49430
|
-
name: _t("Remove duplicates"),
|
|
49431
|
-
execute: (env) => {
|
|
49432
|
-
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
49433
|
-
env.openSidePanel("RemoveDuplicates", {});
|
|
49434
|
-
},
|
|
49435
|
-
isEnabled: (env) => !env.isSmall
|
|
49436
|
-
};
|
|
49437
|
-
const trimWhitespace = {
|
|
49438
|
-
name: _t("Trim whitespace"),
|
|
49439
|
-
execute: (env) => {
|
|
49440
|
-
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
49441
|
-
}
|
|
49442
|
-
};
|
|
49443
|
-
const cleanupDataSources = {
|
|
49444
|
-
name: _t("Remove unused data sources"),
|
|
49445
|
-
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
49446
|
-
isEnabled: (env) => !env.isSmall
|
|
49447
|
-
};
|
|
49448
|
-
const sortDescending = {
|
|
49449
|
-
name: _t("Descending (Z ⟶ A)"),
|
|
49450
|
-
execute: (env) => {
|
|
49451
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49452
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
49453
|
-
},
|
|
49454
|
-
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
49455
|
-
};
|
|
49456
|
-
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
49457
|
-
const createRemoveFilterTool = {
|
|
49458
|
-
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
49459
|
-
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
49460
|
-
};
|
|
49461
|
-
const splitToColumns = {
|
|
49462
|
-
name: _t("Split text to columns"),
|
|
49463
|
-
sequence: 1,
|
|
49464
|
-
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
49465
|
-
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
49466
|
-
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
49467
|
-
};
|
|
49468
|
-
const columnStatistics = {
|
|
49469
|
-
name: _t("Column statistics"),
|
|
49470
|
-
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
49471
|
-
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
49472
|
-
};
|
|
49473
|
-
const reinsertDynamicPivotMenu = {
|
|
49474
|
-
id: "reinsert_dynamic_pivot",
|
|
49475
|
-
name: _t("Re-insert dynamic pivot"),
|
|
49476
|
-
sequence: 60,
|
|
49477
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49478
|
-
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
49479
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49480
|
-
};
|
|
49481
|
-
const reinsertStaticPivotMenu = {
|
|
49482
|
-
id: "reinsert_static_pivot",
|
|
49483
|
-
name: _t("Re-insert static pivot"),
|
|
49484
|
-
sequence: 70,
|
|
49485
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49486
|
-
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
49487
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49488
|
-
};
|
|
49489
|
-
const calculationMode = {
|
|
49490
|
-
name: _t("Calculation"),
|
|
49491
|
-
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
49492
|
-
};
|
|
49493
|
-
const automaticCalculation = {
|
|
49494
|
-
name: _t("Automatic"),
|
|
49495
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
49496
|
-
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
49497
|
-
};
|
|
49498
|
-
const manualCalculation = {
|
|
49499
|
-
name: _t("Manual"),
|
|
49500
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
49501
|
-
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
49502
|
-
};
|
|
49503
|
-
|
|
49504
51053
|
//#endregion
|
|
49505
51054
|
//#region src/registries/menus/col_menu_registry.ts
|
|
49506
51055
|
const colMenuRegistry = new MenuItemRegistry();
|
|
@@ -49534,6 +51083,9 @@ colMenuRegistry.add("cut", {
|
|
|
49534
51083
|
}).addChild("sort_descending", ["sort_columns"], {
|
|
49535
51084
|
...sortDescending,
|
|
49536
51085
|
sequence: 20
|
|
51086
|
+
}).add("data_analysis", {
|
|
51087
|
+
...dataAnalysis,
|
|
51088
|
+
sequence: 55
|
|
49537
51089
|
}).add("column_statistics", {
|
|
49538
51090
|
...columnStatistics,
|
|
49539
51091
|
sequence: 60,
|
|
@@ -52349,14 +53901,15 @@ var FiguresContainer = class extends Component {
|
|
|
52349
53901
|
selectedRect: void 0,
|
|
52350
53902
|
horizontalSnap: void 0,
|
|
52351
53903
|
verticalSnap: void 0,
|
|
52352
|
-
cancelDnd: void 0
|
|
52353
|
-
overlappingChartOrCarousel: void 0
|
|
53904
|
+
cancelDnd: void 0
|
|
52354
53905
|
});
|
|
52355
53906
|
viewStore;
|
|
52356
53907
|
zoomStore;
|
|
53908
|
+
chartDragStore;
|
|
52357
53909
|
setup() {
|
|
52358
53910
|
this.viewStore = useStore(ViewportsStore);
|
|
52359
53911
|
this.zoomStore = useStore(ZoomStore);
|
|
53912
|
+
this.chartDragStore = useStore(ChartDragStore);
|
|
52360
53913
|
onMounted(() => {
|
|
52361
53914
|
this.render();
|
|
52362
53915
|
});
|
|
@@ -52370,7 +53923,7 @@ var FiguresContainer = class extends Component {
|
|
|
52370
53923
|
this.dnd.selectedRect = void 0;
|
|
52371
53924
|
this.dnd.horizontalSnap = void 0;
|
|
52372
53925
|
this.dnd.verticalSnap = void 0;
|
|
52373
|
-
this.
|
|
53926
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52374
53927
|
this.dnd.cancelDnd = void 0;
|
|
52375
53928
|
}
|
|
52376
53929
|
});
|
|
@@ -52496,6 +54049,7 @@ var FiguresContainer = class extends Component {
|
|
|
52496
54049
|
maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
|
|
52497
54050
|
};
|
|
52498
54051
|
let hasStartedDnd = false;
|
|
54052
|
+
let overlappingChartOrCarousel = void 0;
|
|
52499
54053
|
const onMouseMove = (ev) => {
|
|
52500
54054
|
const currentMousePosition = {
|
|
52501
54055
|
x: ev.clientX / zoom,
|
|
@@ -52507,10 +54061,10 @@ var FiguresContainer = class extends Component {
|
|
|
52507
54061
|
hasStartedDnd = true;
|
|
52508
54062
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
|
|
52509
54063
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
52510
|
-
|
|
54064
|
+
overlappingChartOrCarousel = void 0;
|
|
52511
54065
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
52512
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel =
|
|
52513
|
-
this.
|
|
54066
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
|
|
54067
|
+
this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
|
|
52514
54068
|
if (!overlappingChartOrCarousel) {
|
|
52515
54069
|
const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
|
|
52516
54070
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
@@ -52532,7 +54086,7 @@ var FiguresContainer = class extends Component {
|
|
|
52532
54086
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
52533
54087
|
return;
|
|
52534
54088
|
}
|
|
52535
|
-
if (!
|
|
54089
|
+
if (!overlappingChartOrCarousel) {
|
|
52536
54090
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
52537
54091
|
return {
|
|
52538
54092
|
sheetId,
|
|
@@ -52542,14 +54096,14 @@ var FiguresContainer = class extends Component {
|
|
|
52542
54096
|
}) || [];
|
|
52543
54097
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
52544
54098
|
} else {
|
|
52545
|
-
const overlappingFigureId =
|
|
54099
|
+
const overlappingFigureId = overlappingChartOrCarousel.id;
|
|
52546
54100
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
52547
|
-
if (
|
|
54101
|
+
if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
52548
54102
|
sheetId,
|
|
52549
54103
|
carouselFigureId: overlappingFigureId,
|
|
52550
54104
|
chartFigureIds
|
|
52551
54105
|
});
|
|
52552
|
-
else if (
|
|
54106
|
+
else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
52553
54107
|
sheetId,
|
|
52554
54108
|
baseFigureId: overlappingFigureId,
|
|
52555
54109
|
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
@@ -52560,7 +54114,7 @@ var FiguresContainer = class extends Component {
|
|
|
52560
54114
|
this.dnd.selectedRect = void 0;
|
|
52561
54115
|
this.dnd.horizontalSnap = void 0;
|
|
52562
54116
|
this.dnd.verticalSnap = void 0;
|
|
52563
|
-
this.
|
|
54117
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52564
54118
|
};
|
|
52565
54119
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52566
54120
|
}
|
|
@@ -52616,7 +54170,6 @@ var FiguresContainer = class extends Component {
|
|
|
52616
54170
|
this.dnd.selectedRect = void 0;
|
|
52617
54171
|
this.dnd.horizontalSnap = void 0;
|
|
52618
54172
|
this.dnd.verticalSnap = void 0;
|
|
52619
|
-
this.dnd.overlappingChartOrCarousel = void 0;
|
|
52620
54173
|
};
|
|
52621
54174
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52622
54175
|
}
|
|
@@ -52626,12 +54179,12 @@ var FiguresContainer = class extends Component {
|
|
|
52626
54179
|
getFigureStyle(figureUI) {
|
|
52627
54180
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
52628
54181
|
return cssPropertiesToCss({
|
|
52629
|
-
opacity: this.
|
|
54182
|
+
opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
|
|
52630
54183
|
cursor: "grabbing"
|
|
52631
54184
|
});
|
|
52632
54185
|
}
|
|
52633
54186
|
getFigureClass(figureUI) {
|
|
52634
|
-
if (figureUI.id !== this.
|
|
54187
|
+
if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
|
|
52635
54188
|
return "o-add-to-carousel";
|
|
52636
54189
|
}
|
|
52637
54190
|
getSnap(snapLine) {
|
|
@@ -52674,26 +54227,6 @@ var FiguresContainer = class extends Component {
|
|
|
52674
54227
|
height: `100%`
|
|
52675
54228
|
});
|
|
52676
54229
|
}
|
|
52677
|
-
getOverlappingFigure(figureUI, otherFigures) {
|
|
52678
|
-
if (figureUI.tag !== "chart") return;
|
|
52679
|
-
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
52680
|
-
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
52681
|
-
let bestMatch;
|
|
52682
|
-
let smallestDistance = Infinity;
|
|
52683
|
-
for (const figure of otherFigures) {
|
|
52684
|
-
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
52685
|
-
const targetCenterX = figure.x + figure.width / 2;
|
|
52686
|
-
const targetCenterY = figure.y + figure.height / 2;
|
|
52687
|
-
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
52688
|
-
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
52689
|
-
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
52690
|
-
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
52691
|
-
smallestDistance = squaredDistance;
|
|
52692
|
-
bestMatch = figure;
|
|
52693
|
-
}
|
|
52694
|
-
}
|
|
52695
|
-
return bestMatch;
|
|
52696
|
-
}
|
|
52697
54230
|
};
|
|
52698
54231
|
|
|
52699
54232
|
//#endregion
|
|
@@ -52993,7 +54526,7 @@ var AbstractResizer = class extends Component {
|
|
|
52993
54526
|
var ColResizer = class extends AbstractResizer {
|
|
52994
54527
|
static template = "o-spreadsheet-ColResizer";
|
|
52995
54528
|
static components = { UnhideColumnHeaders };
|
|
52996
|
-
colResizerRef = signal(
|
|
54529
|
+
colResizerRef = signal.ref();
|
|
52997
54530
|
setup() {
|
|
52998
54531
|
super.setup();
|
|
52999
54532
|
this.PADDING = 15;
|
|
@@ -53127,7 +54660,7 @@ var ColResizer = class extends AbstractResizer {
|
|
|
53127
54660
|
var RowResizer = class extends AbstractResizer {
|
|
53128
54661
|
static template = "o-spreadsheet-RowResizer";
|
|
53129
54662
|
static components = { UnhideRowHeaders };
|
|
53130
|
-
rowResizerRef = signal(
|
|
54663
|
+
rowResizerRef = signal.ref();
|
|
53131
54664
|
setup() {
|
|
53132
54665
|
super.setup();
|
|
53133
54666
|
this.PADDING = 5;
|
|
@@ -53736,9 +55269,8 @@ var TableResizer = class extends Component {
|
|
|
53736
55269
|
useStore(TableResizeStore);
|
|
53737
55270
|
}
|
|
53738
55271
|
get containerStyle() {
|
|
53739
|
-
const tableZone = this.props.table.range.zone;
|
|
53740
55272
|
const sheetId = this.props.table.range.sheetId;
|
|
53741
|
-
|
|
55273
|
+
const tableZone = this.props.table.range.zone;
|
|
53742
55274
|
const bottomRight = {
|
|
53743
55275
|
...tableZone,
|
|
53744
55276
|
left: tableZone.right,
|
|
@@ -53825,8 +55357,8 @@ var Grid = class extends Component {
|
|
|
53825
55357
|
HEADER_HEIGHT = 26;
|
|
53826
55358
|
HEADER_WIDTH = 48;
|
|
53827
55359
|
menuState;
|
|
53828
|
-
gridRef = signal(
|
|
53829
|
-
canvasRef = signal(
|
|
55360
|
+
gridRef = signal.ref();
|
|
55361
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
53830
55362
|
highlightStore;
|
|
53831
55363
|
viewStore;
|
|
53832
55364
|
zoomStore;
|
|
@@ -54457,6 +55989,9 @@ var Grid = class extends Component {
|
|
|
54457
55989
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
54458
55990
|
return this.env.model.getters.getClientsToDisplay(sheetId);
|
|
54459
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
|
+
}
|
|
54460
55995
|
};
|
|
54461
55996
|
|
|
54462
55997
|
//#endregion
|
|
@@ -54475,7 +56010,7 @@ var FullScreenFigure = class extends Component {
|
|
|
54475
56010
|
static template = "o-spreadsheet-FullScreenFigure";
|
|
54476
56011
|
static components = { ChartFigure };
|
|
54477
56012
|
fullScreenFigureStore;
|
|
54478
|
-
fullScreenFigureRef = signal(
|
|
56013
|
+
fullScreenFigureRef = signal.ref();
|
|
54479
56014
|
spreadsheetRect = useSpreadsheetRect();
|
|
54480
56015
|
figureRegistry = figureRegistry;
|
|
54481
56016
|
setup() {
|
|
@@ -54723,6 +56258,41 @@ var PivotHTMLRenderer = class extends Component {
|
|
|
54723
56258
|
}
|
|
54724
56259
|
};
|
|
54725
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
|
+
|
|
54726
56296
|
//#endregion
|
|
54727
56297
|
//#region src/helpers/figures/charts/runtime/helpers_index.ts
|
|
54728
56298
|
var helpers_index_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -55513,7 +57083,8 @@ var Squisher = class {
|
|
|
55513
57083
|
* The result of this method is:
|
|
55514
57084
|
* - if the cell is not a formula, returns the content as is and resets the base formula
|
|
55515
57085
|
* - if the cell is a formula:
|
|
55516
|
-
* - if there is no previous formula, or the normalized formula is different from the previous one
|
|
57086
|
+
* - if there is no previous formula, or the normalized formula is different from the previous one or it contains blacklisted functions (see NonSquishableFunctionRegistry),
|
|
57087
|
+
* resets the base formula to this one and returns the full formula string
|
|
55517
57088
|
* - else, compares the literal values and range dependencies to the previous formula, and for each parameter:
|
|
55518
57089
|
* - for numbers: returns a relative change (+N or -N) if possible, else the full number or "=" if unchanged
|
|
55519
57090
|
* - for strings: returns the full string if changed, else "="
|
|
@@ -55521,6 +57092,10 @@ var Squisher = class {
|
|
|
55521
57092
|
* */
|
|
55522
57093
|
squish(cell, forSheetId) {
|
|
55523
57094
|
if (cell.isFormula) {
|
|
57095
|
+
if (NonSquishableFunctionRegistry.getAll().some((functionName) => cell.compiledFormula.usesSymbol(functionName))) {
|
|
57096
|
+
this.resetBaseFormula();
|
|
57097
|
+
return cell.compiledFormula.toFormulaString(this.getters, this.rangeToStringOptions);
|
|
57098
|
+
}
|
|
55524
57099
|
let numbers = [];
|
|
55525
57100
|
let strings = [];
|
|
55526
57101
|
let references = [];
|
|
@@ -65323,7 +66898,7 @@ var Session = class extends EventBus {
|
|
|
65323
66898
|
}
|
|
65324
66899
|
message = {
|
|
65325
66900
|
...message,
|
|
65326
|
-
commands:
|
|
66901
|
+
commands: revision.commands
|
|
65327
66902
|
};
|
|
65328
66903
|
}
|
|
65329
66904
|
if (this.isReplayingInitialRevisions) throw new Error(`Trying to send a new revision while replaying initial revision. This can lead to endless dispatches every time the spreadsheet is open.
|
|
@@ -67820,6 +69395,12 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
67820
69395
|
const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
|
|
67821
69396
|
const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
67822
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;
|
|
67823
69404
|
}
|
|
67824
69405
|
return "Success";
|
|
67825
69406
|
}
|
|
@@ -67855,6 +69436,29 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
67855
69436
|
carouselFigureId,
|
|
67856
69437
|
chartFigureIds: cmd.chartFigureIds
|
|
67857
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;
|
|
67858
69462
|
}
|
|
67859
69463
|
}
|
|
67860
69464
|
getFigureUI(sheetId, figure) {
|
|
@@ -69544,6 +71148,9 @@ topbarMenuRegistry.add("file", {
|
|
|
69544
71148
|
}).addChild("split_to_columns", ["data"], {
|
|
69545
71149
|
...splitToColumns,
|
|
69546
71150
|
sequence: 20
|
|
71151
|
+
}).addChild("data_analysis", ["data"], {
|
|
71152
|
+
...dataAnalysis,
|
|
71153
|
+
sequence: 24
|
|
69547
71154
|
}).addChild("column_statistics", ["data"], {
|
|
69548
71155
|
...columnStatistics,
|
|
69549
71156
|
sequence: 25
|
|
@@ -69945,7 +71552,7 @@ var RippleEffect = class extends Component {
|
|
|
69945
71552
|
onAnimationEnd: types$1.function(),
|
|
69946
71553
|
style: types$1.string()
|
|
69947
71554
|
});
|
|
69948
|
-
rippleRef = signal(
|
|
71555
|
+
rippleRef = signal.ref();
|
|
69949
71556
|
setup() {
|
|
69950
71557
|
let animation = void 0;
|
|
69951
71558
|
onMounted(() => {
|
|
@@ -69999,7 +71606,7 @@ var Ripple = class extends Component {
|
|
|
69999
71606
|
onAnimationEnd: types$1.function().optional(() => () => {}),
|
|
70000
71607
|
class: types$1.string().optional("")
|
|
70001
71608
|
});
|
|
70002
|
-
childContainerRef = signal(
|
|
71609
|
+
childContainerRef = signal.ref();
|
|
70003
71610
|
state = proxy({ ripples: [] });
|
|
70004
71611
|
currentId = 1;
|
|
70005
71612
|
onClick(ev) {
|
|
@@ -70270,9 +71877,9 @@ var BottomBarSheet = class extends Component {
|
|
|
70270
71877
|
isEditing: false,
|
|
70271
71878
|
openedPicker: void 0
|
|
70272
71879
|
});
|
|
70273
|
-
sheetDivRef = signal(
|
|
70274
|
-
iconRef = signal(
|
|
70275
|
-
sheetNameRef = signal(
|
|
71880
|
+
sheetDivRef = signal.ref();
|
|
71881
|
+
iconRef = signal.ref();
|
|
71882
|
+
sheetNameRef = signal.ref();
|
|
70276
71883
|
editionState = "initializing";
|
|
70277
71884
|
DOMFocusableElementStore;
|
|
70278
71885
|
setup() {
|
|
@@ -70556,8 +72163,10 @@ var BottomBarStatistic = class extends Component {
|
|
|
70556
72163
|
});
|
|
70557
72164
|
state = proxy({ selectedStatisticFn: "" });
|
|
70558
72165
|
store;
|
|
72166
|
+
sidePanelStore;
|
|
70559
72167
|
setup() {
|
|
70560
72168
|
this.store = useStore(AggregateStatisticsStore);
|
|
72169
|
+
this.sidePanelStore = useStore(SidePanelStore);
|
|
70561
72170
|
onWillUpdateProps(() => {
|
|
70562
72171
|
if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
|
|
70563
72172
|
});
|
|
@@ -70597,6 +72206,12 @@ var BottomBarStatistic = class extends Component {
|
|
|
70597
72206
|
format: fnValue.format()
|
|
70598
72207
|
});
|
|
70599
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
|
+
}
|
|
70600
72215
|
};
|
|
70601
72216
|
|
|
70602
72217
|
//#endregion
|
|
@@ -70611,8 +72226,8 @@ var BottomBar = class extends Component {
|
|
|
70611
72226
|
BottomBarSheet,
|
|
70612
72227
|
BottomBarStatistic
|
|
70613
72228
|
};
|
|
70614
|
-
bottomBarRef = signal(
|
|
70615
|
-
sheetListRef = signal(
|
|
72229
|
+
bottomBarRef = signal.ref();
|
|
72230
|
+
sheetListRef = signal.ref();
|
|
70616
72231
|
dragAndDrop = useDragAndDropListItems();
|
|
70617
72232
|
targetScroll = void 0;
|
|
70618
72233
|
state = proxy({
|
|
@@ -70825,12 +72440,13 @@ var SpreadsheetDashboard = class extends Component {
|
|
|
70825
72440
|
hoveredCell;
|
|
70826
72441
|
viewStore;
|
|
70827
72442
|
zoomStore;
|
|
70828
|
-
gridRef = signal(
|
|
70829
|
-
canvasRef = signal(
|
|
72443
|
+
gridRef = signal.ref();
|
|
72444
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
70830
72445
|
setup() {
|
|
70831
72446
|
this.hoveredCell = useStore(DelayedHoveredCellStore);
|
|
70832
72447
|
this.viewStore = useStore(ViewportsStore);
|
|
70833
72448
|
this.zoomStore = useStore(ZoomStore);
|
|
72449
|
+
useLocalStore(HoveredTableStore);
|
|
70834
72450
|
const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
|
|
70835
72451
|
useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
|
|
70836
72452
|
useGridDrawing({
|
|
@@ -71248,8 +72864,8 @@ var RibbonMenu = class extends Component {
|
|
|
71248
72864
|
static components = { Menu };
|
|
71249
72865
|
props = useProps({ onClose: types$1.function() });
|
|
71250
72866
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
71251
|
-
menuRef = signal(
|
|
71252
|
-
containerRef = signal(
|
|
72867
|
+
menuRef = signal.ref();
|
|
72868
|
+
containerRef = signal.ref();
|
|
71253
72869
|
state = proxy({
|
|
71254
72870
|
menuItems: this.rootItems,
|
|
71255
72871
|
title: _t("Menu Bar"),
|
|
@@ -71322,7 +72938,7 @@ var SmallBottomBar = class extends Component {
|
|
|
71322
72938
|
composerStore;
|
|
71323
72939
|
viewStore;
|
|
71324
72940
|
composerInterface;
|
|
71325
|
-
composerRef = signal(
|
|
72941
|
+
composerRef = signal.ref();
|
|
71326
72942
|
menuState = proxy({ isOpen: false });
|
|
71327
72943
|
setup() {
|
|
71328
72944
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -71401,6 +73017,49 @@ var SmallBottomBar = class extends Component {
|
|
|
71401
73017
|
}
|
|
71402
73018
|
};
|
|
71403
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
|
+
|
|
71404
73063
|
//#endregion
|
|
71405
73064
|
//#region src/components/standalone_grid_canvas/figure_renderer_store.ts
|
|
71406
73065
|
/** Store that draws the figures directly onto the canvas */
|
|
@@ -71426,29 +73085,25 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71426
73085
|
drawLayer(renderingCtx) {
|
|
71427
73086
|
const { viewports, ctx } = renderingCtx;
|
|
71428
73087
|
const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
|
|
71429
|
-
const
|
|
73088
|
+
const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
|
|
71430
73089
|
for (const figure of visibleFigures) {
|
|
71431
|
-
const
|
|
71432
|
-
|
|
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;
|
|
71433
73096
|
if (figure.tag === "chart") {
|
|
71434
73097
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71435
|
-
if (chartId)
|
|
71436
|
-
const chartRect = {
|
|
71437
|
-
x,
|
|
71438
|
-
y,
|
|
71439
|
-
width: figure.width,
|
|
71440
|
-
height: figure.height
|
|
71441
|
-
};
|
|
71442
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71443
|
-
}
|
|
73098
|
+
if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
|
|
71444
73099
|
} else if (figure.tag === "image") {
|
|
71445
73100
|
const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
|
|
71446
|
-
if (loadedImage) ctx.drawImage(loadedImage, x, y,
|
|
71447
|
-
} 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);
|
|
71448
73103
|
if (!this.getters.isDashboard()) {
|
|
71449
73104
|
ctx.strokeStyle = GRAY_400;
|
|
71450
73105
|
ctx.lineWidth = 1;
|
|
71451
|
-
ctx.strokeRect(x, y,
|
|
73106
|
+
ctx.strokeRect(x, y, width, height);
|
|
71452
73107
|
}
|
|
71453
73108
|
}
|
|
71454
73109
|
}
|
|
@@ -71466,53 +73121,37 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71466
73121
|
renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
|
|
71467
73122
|
cleanUp();
|
|
71468
73123
|
}
|
|
71469
|
-
drawCarousel(renderingCtx, figure) {
|
|
71470
|
-
const {
|
|
71471
|
-
const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
|
|
71472
|
-
const x = figure.x - offsetX;
|
|
71473
|
-
const y = figure.y - offsetY;
|
|
73124
|
+
drawCarousel(renderingCtx, figure, figureRect) {
|
|
73125
|
+
const { ctx } = renderingCtx;
|
|
71474
73126
|
const carousel = this.getters.getCarousel(figure.id);
|
|
71475
73127
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71476
73128
|
if (!carousel) return;
|
|
73129
|
+
const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
|
|
71477
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);
|
|
71478
73137
|
const title = {
|
|
71479
73138
|
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
71480
73139
|
...carousel.title
|
|
71481
73140
|
};
|
|
71482
|
-
|
|
71483
|
-
|
|
71484
|
-
if (!title.text && chartId) {
|
|
71485
|
-
const chartRect = {
|
|
71486
|
-
x,
|
|
71487
|
-
y,
|
|
71488
|
-
width: figure.width,
|
|
71489
|
-
height: figure.height
|
|
71490
|
-
};
|
|
71491
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71492
|
-
} else if (title.text) {
|
|
71493
|
-
ctx.save();
|
|
71494
|
-
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
71495
|
-
ctx.fillRect(x, y, figure.width, headerSize);
|
|
71496
|
-
ctx.font = computeTextFont(chartStyleToCellStyle(title));
|
|
73141
|
+
if (title.text) {
|
|
73142
|
+
ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
|
|
71497
73143
|
ctx.fillStyle = title.color;
|
|
71498
|
-
ctx.
|
|
71499
|
-
|
|
71500
|
-
|
|
71501
|
-
|
|
71502
|
-
|
|
71503
|
-
|
|
71504
|
-
|
|
71505
|
-
|
|
71506
|
-
if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
|
|
71507
|
-
else if (!this.getters.isDashboard()) {
|
|
71508
|
-
ctx.strokeStyle = GRAY_400;
|
|
71509
|
-
ctx.lineWidth = 1;
|
|
71510
|
-
ctx.beginPath();
|
|
71511
|
-
ctx.moveTo(x, y + headerSize);
|
|
71512
|
-
ctx.lineTo(x + figure.width, y + headerSize);
|
|
71513
|
-
ctx.stroke();
|
|
71514
|
-
}
|
|
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);
|
|
71515
73152
|
}
|
|
73153
|
+
ctx.restore();
|
|
73154
|
+
if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
|
|
71516
73155
|
}
|
|
71517
73156
|
};
|
|
71518
73157
|
|
|
@@ -71520,12 +73159,13 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71520
73159
|
//#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
|
|
71521
73160
|
var StandaloneGridCanvas = class extends Component {
|
|
71522
73161
|
static template = "o-spreadsheet-StandaloneGridCanvas";
|
|
73162
|
+
static components = { CarouselDataViewPrint };
|
|
71523
73163
|
props = useProps({
|
|
71524
73164
|
sheetId: types$1.UID(),
|
|
71525
73165
|
zone: types$1.Zone(),
|
|
71526
73166
|
renderingCtx: types$1.object()
|
|
71527
73167
|
});
|
|
71528
|
-
canvasRef = signal(
|
|
73168
|
+
canvasRef = signal.ref(HTMLCanvasElement);
|
|
71529
73169
|
rendererStore;
|
|
71530
73170
|
figureRendererStore;
|
|
71531
73171
|
setup() {
|
|
@@ -71547,6 +73187,28 @@ var StandaloneGridCanvas = class extends Component {
|
|
|
71547
73187
|
this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
|
|
71548
73188
|
}));
|
|
71549
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
|
+
}
|
|
71550
73212
|
};
|
|
71551
73213
|
|
|
71552
73214
|
//#endregion
|
|
@@ -72008,7 +73670,7 @@ var NamedRangeSelector = class extends Component {
|
|
|
72008
73670
|
anchorRect: null,
|
|
72009
73671
|
menuItems: []
|
|
72010
73672
|
});
|
|
72011
|
-
namedRangeSelectorRef = signal(
|
|
73673
|
+
namedRangeSelectorRef = signal.ref();
|
|
72012
73674
|
setup() {
|
|
72013
73675
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72014
73676
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -72206,7 +73868,7 @@ var BorderEditor = class extends Component {
|
|
|
72206
73868
|
anchorRect: types$1.Rect()
|
|
72207
73869
|
});
|
|
72208
73870
|
BORDER_POSITIONS = BORDER_POSITIONS;
|
|
72209
|
-
lineStyleButtonRef = signal(
|
|
73871
|
+
lineStyleButtonRef = signal.ref();
|
|
72210
73872
|
borderStyles = borderStyles;
|
|
72211
73873
|
state = proxy({ activeTool: void 0 });
|
|
72212
73874
|
toggleDropdownTool(tool) {
|
|
@@ -72273,7 +73935,7 @@ var BorderEditorWidget = class extends Component {
|
|
|
72273
73935
|
});
|
|
72274
73936
|
topBarToolStore;
|
|
72275
73937
|
viewStore;
|
|
72276
|
-
borderEditorButtonRef = signal(
|
|
73938
|
+
borderEditorButtonRef = signal.ref();
|
|
72277
73939
|
state = proxy({
|
|
72278
73940
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
72279
73941
|
currentStyle: DEFAULT_BORDER_DESC.style,
|
|
@@ -72503,7 +74165,7 @@ var DropdownAction = class extends Component {
|
|
|
72503
74165
|
childClass: types$1.string()
|
|
72504
74166
|
});
|
|
72505
74167
|
topBarToolStore;
|
|
72506
|
-
actionRef = signal(
|
|
74168
|
+
actionRef = signal.ref();
|
|
72507
74169
|
setup() {
|
|
72508
74170
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72509
74171
|
}
|
|
@@ -72566,7 +74228,7 @@ var NumberFormatsTool = class extends Component {
|
|
|
72566
74228
|
props = useProps({ class: types$1.string() });
|
|
72567
74229
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
72568
74230
|
topBarToolStore;
|
|
72569
|
-
buttonRef = signal(
|
|
74231
|
+
buttonRef = signal.ref();
|
|
72570
74232
|
state = proxy({
|
|
72571
74233
|
anchorRect: {
|
|
72572
74234
|
x: 0,
|
|
@@ -72855,12 +74517,12 @@ var TopBar = class extends Component {
|
|
|
72855
74517
|
composerFocusStore;
|
|
72856
74518
|
fingerprints;
|
|
72857
74519
|
topBarToolStore;
|
|
72858
|
-
toolBarContainerRef = signal(
|
|
72859
|
-
toolbarRef = signal(
|
|
72860
|
-
namedRangesRef = signal(
|
|
72861
|
-
topBarTopRef = signal(
|
|
72862
|
-
moreToolsContainerRef = signal(
|
|
72863
|
-
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();
|
|
72864
74526
|
spreadsheetRect = useSpreadsheetRect();
|
|
72865
74527
|
setup() {
|
|
72866
74528
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -73122,7 +74784,7 @@ var Spreadsheet = class extends Component {
|
|
|
73122
74784
|
SpreadsheetPrint
|
|
73123
74785
|
};
|
|
73124
74786
|
sidePanel;
|
|
73125
|
-
spreadsheetRef = signal(
|
|
74787
|
+
spreadsheetRef = signal.ref();
|
|
73126
74788
|
spreadsheetRect = useSpreadsheetRect();
|
|
73127
74789
|
state = proxy({ printModeEnabled: false });
|
|
73128
74790
|
_focusGrid;
|
|
@@ -79130,12 +80792,13 @@ var FilterMenu = class extends Component {
|
|
|
79130
80792
|
}
|
|
79131
80793
|
sortFilterZone(sortDirection) {
|
|
79132
80794
|
const filterPosition = this.props.filterPosition;
|
|
79133
|
-
const
|
|
80795
|
+
const table = this.table;
|
|
80796
|
+
const tableZone = table?.range.zone;
|
|
79134
80797
|
if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
|
|
79135
80798
|
const sheetId = this.viewStore.displayedSheetId;
|
|
79136
80799
|
const contentZone = {
|
|
79137
80800
|
...tableZone,
|
|
79138
|
-
top: tableZone.top +
|
|
80801
|
+
top: tableZone.top + table.config.numberOfHeaders
|
|
79139
80802
|
};
|
|
79140
80803
|
const sortAnchor = {
|
|
79141
80804
|
col: filterPosition.col,
|
|
@@ -79293,9 +80956,9 @@ var LinkEditor = class extends Component {
|
|
|
79293
80956
|
onClosed: types$1.function().optional()
|
|
79294
80957
|
});
|
|
79295
80958
|
static size = { maxHeight: 500 };
|
|
79296
|
-
urlInput = signal(
|
|
79297
|
-
suggestionListRef = signal(
|
|
79298
|
-
urlInputContainer = signal(
|
|
80959
|
+
urlInput = signal.ref(HTMLInputElement);
|
|
80960
|
+
suggestionListRef = signal.ref();
|
|
80961
|
+
urlInputContainer = signal.ref();
|
|
79299
80962
|
state;
|
|
79300
80963
|
viewStore;
|
|
79301
80964
|
setup() {
|
|
@@ -81413,7 +83076,7 @@ const ARRAYTOTEXT = {
|
|
|
81413
83076
|
else if (_format === 0) {
|
|
81414
83077
|
const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
|
|
81415
83078
|
return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
|
|
81416
|
-
return isEvaluationError(value.value) ? value.value :
|
|
83079
|
+
return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
|
|
81417
83080
|
})).join(rowSeparator) };
|
|
81418
83081
|
} else return new EvaluationError(_t("Format must be 0 or 1"));
|
|
81419
83082
|
},
|
|
@@ -87461,7 +89124,7 @@ const CONCAT = {
|
|
|
87461
89124
|
description: _t("Concatenation of two values."),
|
|
87462
89125
|
args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
|
|
87463
89126
|
compute: function(value1, value2) {
|
|
87464
|
-
return { value:
|
|
89127
|
+
return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
|
|
87465
89128
|
},
|
|
87466
89129
|
isExported: true
|
|
87467
89130
|
};
|
|
@@ -88344,7 +90007,7 @@ const CLEAN = {
|
|
|
88344
90007
|
description: _t("Remove non-printable characters from a piece of text."),
|
|
88345
90008
|
args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
|
|
88346
90009
|
compute: function(text) {
|
|
88347
|
-
const _text =
|
|
90010
|
+
const _text = toLocaleString(text, this.locale);
|
|
88348
90011
|
let cleanedStr = "";
|
|
88349
90012
|
for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
|
|
88350
90013
|
return { value: cleanedStr };
|
|
@@ -88355,7 +90018,7 @@ const CONCATENATE = {
|
|
|
88355
90018
|
description: _t("Appends strings to one another."),
|
|
88356
90019
|
args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
|
|
88357
90020
|
compute: function(...datas) {
|
|
88358
|
-
return { value: reduceAny(datas, (acc, a) => acc +
|
|
90021
|
+
return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
|
|
88359
90022
|
},
|
|
88360
90023
|
isExported: true
|
|
88361
90024
|
};
|
|
@@ -88363,7 +90026,7 @@ const EXACT = {
|
|
|
88363
90026
|
description: _t("Tests whether two strings are identical."),
|
|
88364
90027
|
args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
|
|
88365
90028
|
compute: function(string1, string2) {
|
|
88366
|
-
return { value:
|
|
90029
|
+
return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
|
|
88367
90030
|
},
|
|
88368
90031
|
isExported: true
|
|
88369
90032
|
};
|
|
@@ -88375,8 +90038,8 @@ const FIND = {
|
|
|
88375
90038
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88376
90039
|
],
|
|
88377
90040
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88378
|
-
const _searchFor =
|
|
88379
|
-
const _textToSearch =
|
|
90041
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
90042
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
88380
90043
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88381
90044
|
if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
88382
90045
|
if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
@@ -88413,8 +90076,8 @@ const JOIN = {
|
|
|
88413
90076
|
description: _t("Concatenates elements of arrays with delimiter."),
|
|
88414
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."))],
|
|
88415
90078
|
compute: function(delimiter, ...valuesOrArrays) {
|
|
88416
|
-
const _delimiter =
|
|
88417
|
-
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), "") };
|
|
88418
90081
|
}
|
|
88419
90082
|
};
|
|
88420
90083
|
const LEFT = {
|
|
@@ -88423,7 +90086,7 @@ const LEFT = {
|
|
|
88423
90086
|
compute: function(text, ...args) {
|
|
88424
90087
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88425
90088
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
88426
|
-
return { value:
|
|
90089
|
+
return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
|
|
88427
90090
|
},
|
|
88428
90091
|
isExported: true
|
|
88429
90092
|
};
|
|
@@ -88431,7 +90094,7 @@ const LEN = {
|
|
|
88431
90094
|
description: _t("Length of a string."),
|
|
88432
90095
|
args: [arg("text (string)", _t("The string whose length will be returned."))],
|
|
88433
90096
|
compute: function(text) {
|
|
88434
|
-
return { value:
|
|
90097
|
+
return { value: toLocaleString(text, this.locale).length };
|
|
88435
90098
|
},
|
|
88436
90099
|
isExported: true
|
|
88437
90100
|
};
|
|
@@ -88439,7 +90102,7 @@ const LOWER = {
|
|
|
88439
90102
|
description: _t("Converts a specified string to lowercase."),
|
|
88440
90103
|
args: [arg("text (string)", _t("The string to convert to lowercase."))],
|
|
88441
90104
|
compute: function(text) {
|
|
88442
|
-
return { value:
|
|
90105
|
+
return { value: toLocaleString(text, this.locale).toLowerCase() };
|
|
88443
90106
|
},
|
|
88444
90107
|
isExported: true
|
|
88445
90108
|
};
|
|
@@ -88451,7 +90114,7 @@ const MID = {
|
|
|
88451
90114
|
arg("extract_length (number)", _t("The length of the segment to extract."))
|
|
88452
90115
|
],
|
|
88453
90116
|
compute: function(text, starting_at, extract_length) {
|
|
88454
|
-
const _text =
|
|
90117
|
+
const _text = toLocaleString(text, this.locale);
|
|
88455
90118
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
88456
90119
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
88457
90120
|
if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
@@ -88464,7 +90127,7 @@ const PROPER = {
|
|
|
88464
90127
|
description: _t("Capitalizes each word in a specified string."),
|
|
88465
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."))],
|
|
88466
90129
|
compute: function(text) {
|
|
88467
|
-
return { value:
|
|
90130
|
+
return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
|
|
88468
90131
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
88469
90132
|
}) };
|
|
88470
90133
|
},
|
|
@@ -88614,8 +90277,8 @@ const REPLACE = {
|
|
|
88614
90277
|
if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
88615
90278
|
const _length = toNumber(length, this.locale);
|
|
88616
90279
|
if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
|
|
88617
|
-
const _text =
|
|
88618
|
-
const _newText =
|
|
90280
|
+
const _text = toLocaleString(text, this.locale);
|
|
90281
|
+
const _newText = toLocaleString(newText, this.locale);
|
|
88619
90282
|
return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
|
|
88620
90283
|
},
|
|
88621
90284
|
isExported: true
|
|
@@ -88626,7 +90289,7 @@ const RIGHT = {
|
|
|
88626
90289
|
compute: function(text, ...args) {
|
|
88627
90290
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88628
90291
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
|
|
88629
|
-
const _text =
|
|
90292
|
+
const _text = toLocaleString(text, this.locale);
|
|
88630
90293
|
const stringLength = _text.length;
|
|
88631
90294
|
return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
|
|
88632
90295
|
},
|
|
@@ -88640,8 +90303,8 @@ const SEARCH = {
|
|
|
88640
90303
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88641
90304
|
],
|
|
88642
90305
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88643
|
-
const _searchFor =
|
|
88644
|
-
const _textToSearch =
|
|
90306
|
+
const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
|
|
90307
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
|
|
88645
90308
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88646
90309
|
if (_textToSearch === "") return {
|
|
88647
90310
|
value: CellErrorType.GenericError,
|
|
@@ -88671,8 +90334,8 @@ const SPLIT = {
|
|
|
88671
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."))
|
|
88672
90335
|
],
|
|
88673
90336
|
computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
|
|
88674
|
-
const _text =
|
|
88675
|
-
const _delimiter = escapeRegExp(
|
|
90337
|
+
const _text = toLocaleString(text, this.locale);
|
|
90338
|
+
const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
|
|
88676
90339
|
const _splitByEach = toBoolean(splitByEach);
|
|
88677
90340
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
88678
90341
|
if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
@@ -88694,10 +90357,10 @@ const SUBSTITUTE = {
|
|
|
88694
90357
|
compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
88695
90358
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
88696
90359
|
if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
88697
|
-
const _textToSearch =
|
|
88698
|
-
const _searchFor =
|
|
90360
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
90361
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
88699
90362
|
if (_searchFor === "") return { value: _textToSearch };
|
|
88700
|
-
const _replaceWith =
|
|
90363
|
+
const _replaceWith = toLocaleString(replaceWith, this.locale);
|
|
88701
90364
|
const reg = new RegExp(escapeRegExp(_searchFor), "g");
|
|
88702
90365
|
if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
|
|
88703
90366
|
let n = 0;
|
|
@@ -88720,10 +90383,10 @@ const TEXTJOIN = {
|
|
|
88720
90383
|
arg("texts (string, range<string>, repeating)", _t("Text item to join."))
|
|
88721
90384
|
],
|
|
88722
90385
|
compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
|
|
88723
|
-
const _delimiter =
|
|
90386
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
88724
90387
|
const _ignoreEmpty = toBoolean(ignoreEmpty);
|
|
88725
90388
|
let n = 0;
|
|
88726
|
-
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, "") };
|
|
88727
90390
|
},
|
|
88728
90391
|
isExported: true
|
|
88729
90392
|
};
|
|
@@ -88776,7 +90439,7 @@ const TRIM = {
|
|
|
88776
90439
|
description: _t("Removes space characters."),
|
|
88777
90440
|
args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
|
|
88778
90441
|
compute: function(text) {
|
|
88779
|
-
return { value: trimContent(
|
|
90442
|
+
return { value: trimContent(toLocaleString(text, this.locale)) };
|
|
88780
90443
|
},
|
|
88781
90444
|
isExported: true
|
|
88782
90445
|
};
|
|
@@ -88784,7 +90447,7 @@ const UPPER = {
|
|
|
88784
90447
|
description: _t("Converts a specified string to uppercase."),
|
|
88785
90448
|
args: [arg("text (string)", _t("The string to convert to uppercase."))],
|
|
88786
90449
|
compute: function(text) {
|
|
88787
|
-
return { value:
|
|
90450
|
+
return { value: toLocaleString(text, this.locale).toUpperCase() };
|
|
88788
90451
|
},
|
|
88789
90452
|
isExported: true
|
|
88790
90453
|
};
|
|
@@ -88881,7 +90544,7 @@ const HYPERLINK = {
|
|
|
88881
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."))],
|
|
88882
90545
|
compute: function(url, linkLabel) {
|
|
88883
90546
|
const processedUrl = toString(url).trim();
|
|
88884
|
-
const processedLabel =
|
|
90547
|
+
const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
|
|
88885
90548
|
if (processedUrl === "") return { value: processedLabel };
|
|
88886
90549
|
return { value: markdownLink(processedLabel, processedUrl) };
|
|
88887
90550
|
},
|
|
@@ -89028,7 +90691,8 @@ const registries = {
|
|
|
89028
90691
|
chartJsExtensionRegistry,
|
|
89029
90692
|
onIterationEndEvaluationRegistry,
|
|
89030
90693
|
specificRangeTransformRegistry,
|
|
89031
|
-
unusedDataSourceRegistry
|
|
90694
|
+
unusedDataSourceRegistry,
|
|
90695
|
+
NonSquishableFunctionRegistry
|
|
89032
90696
|
};
|
|
89033
90697
|
const helpers = {
|
|
89034
90698
|
arg,
|
|
@@ -89110,7 +90774,9 @@ const helpers = {
|
|
|
89110
90774
|
computeCachedTextDimension,
|
|
89111
90775
|
createComputeFunction,
|
|
89112
90776
|
isMobileOS,
|
|
89113
|
-
drawHighlight
|
|
90777
|
+
drawHighlight,
|
|
90778
|
+
getBoundingRectWithMargins,
|
|
90779
|
+
hasInteractiveElementInEventTree
|
|
89114
90780
|
};
|
|
89115
90781
|
const links = {
|
|
89116
90782
|
isMarkdownLink,
|
|
@@ -89248,6 +90914,6 @@ const chartHelpers = {
|
|
|
89248
90914
|
//#endregion
|
|
89249
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 };
|
|
89250
90916
|
|
|
89251
|
-
__info__.version = "19.5.0-alpha.
|
|
89252
|
-
__info__.date = "2026-08-
|
|
89253
|
-
__info__.hash = "
|
|
90917
|
+
__info__.version = "19.5.0-alpha.12";
|
|
90918
|
+
__info__.date = "2026-08-19T09:47:03.774Z";
|
|
90919
|
+
__info__.hash = "68ca68e";
|