@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
|
(function(exports, _odoo_owl) {
|
|
@@ -208,6 +208,16 @@ _odoo_owl = __toESM(_odoo_owl, 1);
|
|
|
208
208
|
height: rect.height
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
|
+
function getBoundingRectWithMargins(el) {
|
|
212
|
+
const style = getComputedStyle(el);
|
|
213
|
+
const rect = el.getBoundingClientRect();
|
|
214
|
+
return {
|
|
215
|
+
x: rect.x,
|
|
216
|
+
y: rect.y,
|
|
217
|
+
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
218
|
+
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
219
|
+
};
|
|
220
|
+
}
|
|
211
221
|
/**
|
|
212
222
|
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
213
223
|
*/
|
|
@@ -651,6 +661,14 @@ _odoo_owl = __toESM(_odoo_owl, 1);
|
|
|
651
661
|
fontSize: 18,
|
|
652
662
|
color: TEXT_BODY
|
|
653
663
|
};
|
|
664
|
+
const CAROUSEL_LAYOUT = {
|
|
665
|
+
paddingX: 24,
|
|
666
|
+
paddingY: 8,
|
|
667
|
+
headerPaddingTop: 4,
|
|
668
|
+
separatorWidth: 1,
|
|
669
|
+
headerLineHeight: 1.5,
|
|
670
|
+
minHeaderHeight: 25
|
|
671
|
+
};
|
|
654
672
|
const DEFAULT_TOKEN_COLOR = "light-dark(#000000, #ffffff)";
|
|
655
673
|
const functionColor = DEFAULT_TOKEN_COLOR;
|
|
656
674
|
const operatorColor = "#3da4ab";
|
|
@@ -2635,7 +2653,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2635
2653
|
*
|
|
2636
2654
|
* The end goal is to eliminate all compatibility shims.
|
|
2637
2655
|
*/
|
|
2638
|
-
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$
|
|
2656
|
+
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$26, onPatched: onPatched$5, onWillUnmount: onWillUnmount$20, App: OwlApp, Component: OwlComponent, EnvPlugin: OwlEnvPlugin, useChildEnv: OwlUseChildEnv, useChildSubEnv: OwlUseChildSubEnv, useComponent: OwlUseComponent, useEnv: OwlUseEnv, useExternalListener: OwlUseExternalListener, useLayoutEffect: OwlUseLayoutEffect, useSubEnv: OwlUseSubEnv, Plugin, plugin, props, providePlugins, useScope, xml: xml$5 } = _odoo_owl;
|
|
2639
2657
|
const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
|
|
2640
2658
|
var _Component = class extends OwlComponent {
|
|
2641
2659
|
static template = "";
|
|
@@ -2656,8 +2674,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2656
2674
|
function _useExternalListener(target, eventName, handler, eventParams) {
|
|
2657
2675
|
const node = useScope();
|
|
2658
2676
|
const boundHandler = handler.bind(node.component);
|
|
2659
|
-
onMounted$
|
|
2660
|
-
onWillUnmount$
|
|
2677
|
+
onMounted$26(() => target.addEventListener(eventName, boundHandler, eventParams));
|
|
2678
|
+
onWillUnmount$20(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
|
2661
2679
|
}
|
|
2662
2680
|
function onWillRender(cb) {
|
|
2663
2681
|
const node = useScope();
|
|
@@ -2672,7 +2690,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2672
2690
|
let cleanup;
|
|
2673
2691
|
/** @type {any[]} */
|
|
2674
2692
|
let dependencies;
|
|
2675
|
-
onMounted$
|
|
2693
|
+
onMounted$26(() => {
|
|
2676
2694
|
dependencies = computeDependencies();
|
|
2677
2695
|
cleanup = effect(...dependencies);
|
|
2678
2696
|
});
|
|
@@ -2689,7 +2707,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2689
2707
|
cleanup = effect(...dependencies);
|
|
2690
2708
|
}
|
|
2691
2709
|
});
|
|
2692
|
-
onWillUnmount$
|
|
2710
|
+
onWillUnmount$20(() => cleanup && cleanup());
|
|
2693
2711
|
}
|
|
2694
2712
|
var _EnvPlugin = class extends Plugin {
|
|
2695
2713
|
static id = "__ENV__";
|
|
@@ -2762,7 +2780,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2762
2780
|
}
|
|
2763
2781
|
};
|
|
2764
2782
|
var Portal = class extends OwlComponent {
|
|
2765
|
-
static template = xml$
|
|
2783
|
+
static template = xml$5`<t t-call-slot="default"/>`;
|
|
2766
2784
|
constructor(node) {
|
|
2767
2785
|
super(node);
|
|
2768
2786
|
this.props = props();
|
|
@@ -2772,7 +2790,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2772
2790
|
const node = this.__owl__;
|
|
2773
2791
|
const renderContent = node.renderFn;
|
|
2774
2792
|
node.renderFn = (...args) => new VPortal(node.props.selector, renderContent(...args));
|
|
2775
|
-
onMounted$
|
|
2793
|
+
onMounted$26(() => {
|
|
2776
2794
|
const portal = node.bdom;
|
|
2777
2795
|
if (!portal.target) {
|
|
2778
2796
|
const target = document.querySelector(node.props.selector);
|
|
@@ -2780,7 +2798,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2780
2798
|
else throw new Error("invalid portal target");
|
|
2781
2799
|
}
|
|
2782
2800
|
});
|
|
2783
|
-
onWillUnmount$
|
|
2801
|
+
onWillUnmount$20(() => {
|
|
2784
2802
|
node.bdom.remove();
|
|
2785
2803
|
});
|
|
2786
2804
|
}
|
|
@@ -4073,6 +4091,18 @@ set(value) {
|
|
|
4073
4091
|
default: return "";
|
|
4074
4092
|
}
|
|
4075
4093
|
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Only converts the decimal separator, ignore number format such as % or dates
|
|
4096
|
+
*/
|
|
4097
|
+
function toLocaleString(data, locale) {
|
|
4098
|
+
const value = toValue(data);
|
|
4099
|
+
switch (typeof value) {
|
|
4100
|
+
case "string": return value;
|
|
4101
|
+
case "number": return value.toString().replace(".", locale.decimalSeparator);
|
|
4102
|
+
case "boolean": return value ? "TRUE" : "FALSE";
|
|
4103
|
+
default: return "";
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4076
4106
|
/** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
|
|
4077
4107
|
const normalizeString = memoize(function normalizeString(str) {
|
|
4078
4108
|
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -5987,6 +6017,9 @@ set(value) {
|
|
|
5987
6017
|
}
|
|
5988
6018
|
};
|
|
5989
6019
|
const functionRegistry = new FunctionRegistry();
|
|
6020
|
+
/** Formulas using one of these functions are never squished: they are always exported
|
|
6021
|
+
* as a full formula string, and they don't become the base formula of the next cells. */
|
|
6022
|
+
const NonSquishableFunctionRegistry = new Registry();
|
|
5990
6023
|
|
|
5991
6024
|
//#endregion
|
|
5992
6025
|
//#region src/helpers/references.ts
|
|
@@ -9246,8 +9279,8 @@ set(value) {
|
|
|
9246
9279
|
function fontSizeInPixels(fontSize) {
|
|
9247
9280
|
return Math.round(fontSize * 96 / 72);
|
|
9248
9281
|
}
|
|
9249
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
9250
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" :
|
|
9282
|
+
function computeTextFont(style, fontUnit = "pt", fontWeight = 400) {
|
|
9283
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : fontWeight} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9251
9284
|
}
|
|
9252
9285
|
function computeTextFontSizeInPixels(style) {
|
|
9253
9286
|
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
@@ -11881,7 +11914,7 @@ set(value) {
|
|
|
11881
11914
|
chartId: types.string(),
|
|
11882
11915
|
isFullScreen: types.boolean().optional()
|
|
11883
11916
|
});
|
|
11884
|
-
canvas =
|
|
11917
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
11885
11918
|
chart;
|
|
11886
11919
|
currentRuntime;
|
|
11887
11920
|
animationStore;
|
|
@@ -12604,7 +12637,7 @@ set(value) {
|
|
|
12604
12637
|
chartId: types.string(),
|
|
12605
12638
|
isFullScreen: types.boolean().optional()
|
|
12606
12639
|
});
|
|
12607
|
-
canvas =
|
|
12640
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
12608
12641
|
zoomStore;
|
|
12609
12642
|
get runtime() {
|
|
12610
12643
|
return this.env.model.getters.getChartRuntime(this.props.chartId);
|
|
@@ -13989,6 +14022,44 @@ set(value) {
|
|
|
13989
14022
|
const definition = getters.getChartDefinition(item.chartId);
|
|
13990
14023
|
return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
|
|
13991
14024
|
}
|
|
14025
|
+
function getCarouselLayout(figureRect, carousel, selectedItem) {
|
|
14026
|
+
const layout = CAROUSEL_LAYOUT;
|
|
14027
|
+
const title = {
|
|
14028
|
+
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
14029
|
+
...carousel.title
|
|
14030
|
+
};
|
|
14031
|
+
const x = figureRect.x + layout.paddingX;
|
|
14032
|
+
const width = Math.max(0, figureRect.width - 2 * layout.paddingX);
|
|
14033
|
+
const titleHeight = title.text ? title.fontSize * layout.headerLineHeight : 0;
|
|
14034
|
+
const headerRect = {
|
|
14035
|
+
x,
|
|
14036
|
+
y: figureRect.y + layout.paddingY + layout.headerPaddingTop,
|
|
14037
|
+
width,
|
|
14038
|
+
height: Math.max(layout.minHeaderHeight, titleHeight)
|
|
14039
|
+
};
|
|
14040
|
+
const headerBottom = headerRect.y + headerRect.height;
|
|
14041
|
+
const separatorRect = title.text ? {
|
|
14042
|
+
x,
|
|
14043
|
+
y: headerBottom + layout.paddingY,
|
|
14044
|
+
width,
|
|
14045
|
+
height: layout.separatorWidth
|
|
14046
|
+
} : void 0;
|
|
14047
|
+
const contentPaddingTop = selectedItem?.type === "carouselDataView" ? layout.paddingY : 0;
|
|
14048
|
+
const rectAboveContent = separatorRect || headerRect;
|
|
14049
|
+
const contentY = rectAboveContent.y + rectAboveContent.height + contentPaddingTop;
|
|
14050
|
+
const contentBottom = figureRect.y + figureRect.height - layout.paddingY;
|
|
14051
|
+
return {
|
|
14052
|
+
headerRect,
|
|
14053
|
+
separatorRect,
|
|
14054
|
+
contentRect: {
|
|
14055
|
+
x,
|
|
14056
|
+
y: contentY,
|
|
14057
|
+
width,
|
|
14058
|
+
height: Math.max(0, contentBottom - contentY)
|
|
14059
|
+
},
|
|
14060
|
+
headerLineHeight: layout.headerLineHeight
|
|
14061
|
+
};
|
|
14062
|
+
}
|
|
13992
14063
|
|
|
13993
14064
|
//#endregion
|
|
13994
14065
|
//#region src/helpers/figures/charts/gauge_chart_rendering.ts
|
|
@@ -14000,7 +14071,7 @@ set(value) {
|
|
|
14000
14071
|
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
14001
14072
|
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
14002
14073
|
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
14003
|
-
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
14074
|
+
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
|
|
14004
14075
|
const size = dimensions ?? getCanvasSize(canvas);
|
|
14005
14076
|
const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
14006
14077
|
canvas.width = size.width * dpr;
|
|
@@ -14013,7 +14084,7 @@ set(value) {
|
|
|
14013
14084
|
height: size.height / zoom,
|
|
14014
14085
|
x: 0,
|
|
14015
14086
|
y: 0
|
|
14016
|
-
}, runtime, ctx);
|
|
14087
|
+
}, runtime, ctx, options);
|
|
14017
14088
|
drawBackground(ctx, config);
|
|
14018
14089
|
drawGauge(ctx, config);
|
|
14019
14090
|
drawInflectionValues(ctx, config);
|
|
@@ -14101,7 +14172,8 @@ set(value) {
|
|
|
14101
14172
|
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
14102
14173
|
ctx.restore();
|
|
14103
14174
|
}
|
|
14104
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
14175
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
|
|
14176
|
+
const labelFontSize = options?.labelFontSize ?? 12;
|
|
14105
14177
|
const maxValue = runtime.maxValue;
|
|
14106
14178
|
const minValue = runtime.minValue;
|
|
14107
14179
|
const gaugeValue = getGaugeValue(runtime, "animated");
|
|
@@ -14119,14 +14191,14 @@ set(value) {
|
|
|
14119
14191
|
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
14120
14192
|
const minLabelPosition = {
|
|
14121
14193
|
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
14122
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14194
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14123
14195
|
};
|
|
14124
14196
|
const maxLabelPosition = {
|
|
14125
14197
|
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
14126
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14198
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14127
14199
|
};
|
|
14128
14200
|
const textColor = chartMutedFontColor(runtime.background);
|
|
14129
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
14201
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
|
|
14130
14202
|
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
14131
14203
|
if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
|
|
14132
14204
|
fontSize: 16,
|
|
@@ -14174,13 +14246,13 @@ set(value) {
|
|
|
14174
14246
|
minLabel: {
|
|
14175
14247
|
label: runtime.minValue.label,
|
|
14176
14248
|
textPosition: minLabelPosition,
|
|
14177
|
-
fontSize:
|
|
14249
|
+
fontSize: labelFontSize,
|
|
14178
14250
|
color: textColor
|
|
14179
14251
|
},
|
|
14180
14252
|
maxLabel: {
|
|
14181
14253
|
label: runtime.maxValue.label,
|
|
14182
14254
|
textPosition: maxLabelPosition,
|
|
14183
|
-
fontSize:
|
|
14255
|
+
fontSize: labelFontSize,
|
|
14184
14256
|
color: textColor
|
|
14185
14257
|
}
|
|
14186
14258
|
};
|
|
@@ -14214,27 +14286,27 @@ set(value) {
|
|
|
14214
14286
|
*
|
|
14215
14287
|
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
14216
14288
|
*/
|
|
14217
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
14289
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
|
|
14218
14290
|
const maxValue = runtime.maxValue;
|
|
14219
14291
|
const minValue = runtime.minValue;
|
|
14220
14292
|
const gaugeCircleCenter = {
|
|
14221
14293
|
x: gaugeRect.x + gaugeRect.width / 2,
|
|
14222
14294
|
y: gaugeRect.y + gaugeRect.height
|
|
14223
14295
|
};
|
|
14224
|
-
const textStyle = { fontSize:
|
|
14296
|
+
const textStyle = { fontSize: labelFontSize };
|
|
14225
14297
|
const inflectionValues = [];
|
|
14226
14298
|
const inflectionValuesTextRects = [];
|
|
14227
14299
|
for (const inflectionValue of runtime.inflectionValues) {
|
|
14228
14300
|
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
14229
14301
|
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
14230
14302
|
const angle = Math.PI - Math.PI * percentage;
|
|
14231
|
-
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2,
|
|
14232
|
-
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ?
|
|
14303
|
+
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2, labelFontSize);
|
|
14304
|
+
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ? labelFontSize : 0;
|
|
14233
14305
|
inflectionValuesTextRects.push(textRect);
|
|
14234
14306
|
inflectionValues.push({
|
|
14235
14307
|
rotation: angle,
|
|
14236
14308
|
label: inflectionValue.label,
|
|
14237
|
-
fontSize:
|
|
14309
|
+
fontSize: labelFontSize,
|
|
14238
14310
|
color: textColor,
|
|
14239
14311
|
offset
|
|
14240
14312
|
});
|
|
@@ -14354,6 +14426,44 @@ set(value) {
|
|
|
14354
14426
|
"mouseup"
|
|
14355
14427
|
]
|
|
14356
14428
|
};
|
|
14429
|
+
/**
|
|
14430
|
+
* Sample down a Chart.js configuration's data points to at most `maxPoints`, evenly spaced.
|
|
14431
|
+
* Used for lightweight previews (drag preview, chart suggestions) where rendering every point
|
|
14432
|
+
* of a large dataset would make Chart.js slow without any visible benefit on a small canvas.
|
|
14433
|
+
*/
|
|
14434
|
+
function limitChartConfigDataPoints(config, maxPoints = 150) {
|
|
14435
|
+
const data = config.data;
|
|
14436
|
+
if (!data) return config;
|
|
14437
|
+
return {
|
|
14438
|
+
...config,
|
|
14439
|
+
data: {
|
|
14440
|
+
...data,
|
|
14441
|
+
labels: limitArrayDataPoints(data.labels, maxPoints),
|
|
14442
|
+
datasets: data.datasets?.map((dataset) => limitDatasetDataPoints(dataset, maxPoints))
|
|
14443
|
+
}
|
|
14444
|
+
};
|
|
14445
|
+
}
|
|
14446
|
+
function limitDatasetDataPoints(dataset, maxPoints) {
|
|
14447
|
+
const values = dataset.data;
|
|
14448
|
+
if (!Array.isArray(values) || values.length <= maxPoints) return dataset;
|
|
14449
|
+
const length = values.length;
|
|
14450
|
+
const indices = sampleIndices(length, maxPoints);
|
|
14451
|
+
const limited = { ...dataset };
|
|
14452
|
+
for (const key of Object.keys(limited)) {
|
|
14453
|
+
const value = limited[key];
|
|
14454
|
+
if (Array.isArray(value) && value.length === length) limited[key] = indices.map((i) => value[i]);
|
|
14455
|
+
}
|
|
14456
|
+
return limited;
|
|
14457
|
+
}
|
|
14458
|
+
function limitArrayDataPoints(arr, maxPoints) {
|
|
14459
|
+
if (!Array.isArray(arr) || arr.length <= maxPoints) return arr;
|
|
14460
|
+
return sampleIndices(arr.length, maxPoints).map((i) => arr[i]);
|
|
14461
|
+
}
|
|
14462
|
+
/** Evenly spaced indices sampled from [0, length). */
|
|
14463
|
+
function sampleIndices(length, maxPoints) {
|
|
14464
|
+
const step = length / maxPoints;
|
|
14465
|
+
return Array.from({ length: maxPoints }, (_, i) => Math.min(length - 1, Math.floor(i * step)));
|
|
14466
|
+
}
|
|
14357
14467
|
async function chartToImageUrl(runtime, figure, type) {
|
|
14358
14468
|
try {
|
|
14359
14469
|
const canvas = createRenderingSurface(figure.width, figure.height);
|
|
@@ -14401,7 +14511,7 @@ set(value) {
|
|
|
14401
14511
|
*
|
|
14402
14512
|
* @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
|
|
14403
14513
|
*/
|
|
14404
|
-
function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
14514
|
+
function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
|
|
14405
14515
|
if ("chartJsConfig" in runtime) {
|
|
14406
14516
|
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
14407
14517
|
const extensionsLoaded = areChartJSExtensionsLoaded();
|
|
@@ -14417,8 +14527,8 @@ set(value) {
|
|
|
14417
14527
|
chart.destroy();
|
|
14418
14528
|
if (!extensionsLoaded) unregisterChartJsExtensions();
|
|
14419
14529
|
};
|
|
14420
|
-
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
|
|
14421
|
-
else if (type === "gauge") drawGaugeChart(canvas, runtime,
|
|
14530
|
+
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas, zoom);
|
|
14531
|
+
else if (type === "gauge") drawGaugeChart(canvas, runtime, zoom, size);
|
|
14422
14532
|
return () => {};
|
|
14423
14533
|
}
|
|
14424
14534
|
|
|
@@ -14956,7 +15066,7 @@ set(value) {
|
|
|
14956
15066
|
var ZoomableChartJsComponent = class extends ChartJsComponent {
|
|
14957
15067
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
14958
15068
|
store;
|
|
14959
|
-
masterChartCanvas =
|
|
15069
|
+
masterChartCanvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
14960
15070
|
masterChart;
|
|
14961
15071
|
mode;
|
|
14962
15072
|
hasLinearScale;
|
|
@@ -15710,7 +15820,7 @@ set(value) {
|
|
|
15710
15820
|
chartId: types.string(),
|
|
15711
15821
|
isFullScreen: types.boolean().optional()
|
|
15712
15822
|
});
|
|
15713
|
-
canvas =
|
|
15823
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
15714
15824
|
animationStore;
|
|
15715
15825
|
zoomStore;
|
|
15716
15826
|
get runtime() {
|
|
@@ -15947,7 +16057,7 @@ set(value) {
|
|
|
15947
16057
|
onKeyDown: types.function().optional(),
|
|
15948
16058
|
disableKeyboardNavigation: types.boolean().optional()
|
|
15949
16059
|
});
|
|
15950
|
-
menuRef =
|
|
16060
|
+
menuRef = _odoo_owl.signal.ref();
|
|
15951
16061
|
setup() {
|
|
15952
16062
|
useLayoutEffect(() => {
|
|
15953
16063
|
const menuEl = this.menuRef();
|
|
@@ -16067,8 +16177,8 @@ set(value) {
|
|
|
16067
16177
|
zIndex: types.number().optional(),
|
|
16068
16178
|
class: types.string().optional()
|
|
16069
16179
|
});
|
|
16070
|
-
popoverRef =
|
|
16071
|
-
popoverContentRef =
|
|
16180
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
16181
|
+
popoverContentRef = _odoo_owl.signal.ref();
|
|
16072
16182
|
currentPosition = void 0;
|
|
16073
16183
|
currentDisplayValue = void 0;
|
|
16074
16184
|
spreadsheetRect = useSpreadsheetRect();
|
|
@@ -16256,7 +16366,7 @@ set(value) {
|
|
|
16256
16366
|
isHoveringChild: false
|
|
16257
16367
|
});
|
|
16258
16368
|
state = (0, _odoo_owl.proxy)({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
|
|
16259
|
-
menuRef =
|
|
16369
|
+
menuRef = _odoo_owl.signal.ref();
|
|
16260
16370
|
openingTimeOut = useTimeOut();
|
|
16261
16371
|
setup() {
|
|
16262
16372
|
const domFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -16545,43 +16655,29 @@ set(value) {
|
|
|
16545
16655
|
};
|
|
16546
16656
|
|
|
16547
16657
|
//#endregion
|
|
16548
|
-
//#region src/
|
|
16549
|
-
var
|
|
16550
|
-
mutators = [
|
|
16551
|
-
|
|
16658
|
+
//#region src/stores/cell_hover_overlay_store.ts
|
|
16659
|
+
var CellHoverOverlayStore = class extends SpreadsheetStore {
|
|
16660
|
+
mutators = [
|
|
16661
|
+
"hover",
|
|
16662
|
+
"register",
|
|
16663
|
+
"unRegister"
|
|
16664
|
+
];
|
|
16665
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
16666
|
+
providers = [];
|
|
16552
16667
|
overlayColors = new PositionMap();
|
|
16553
|
-
hover(
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
this.
|
|
16668
|
+
hover(hoveredPosition) {
|
|
16669
|
+
this.overlayColors = new PositionMap();
|
|
16670
|
+
if (!hoveredPosition) return;
|
|
16671
|
+
for (const provider of this.providers) {
|
|
16672
|
+
const positions = provider.getHighlightedPositions(hoveredPosition);
|
|
16673
|
+
for (const position of positions) this.overlayColors.set(position, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16674
|
+
}
|
|
16557
16675
|
}
|
|
16558
|
-
|
|
16559
|
-
this.
|
|
16676
|
+
register(highlightProvider) {
|
|
16677
|
+
this.providers.push(highlightProvider);
|
|
16560
16678
|
}
|
|
16561
|
-
|
|
16562
|
-
this.
|
|
16563
|
-
if (!this.position) return;
|
|
16564
|
-
const { sheetId, col, row } = this.position;
|
|
16565
|
-
const table = this.getters.getTable({
|
|
16566
|
-
sheetId,
|
|
16567
|
-
col,
|
|
16568
|
-
row
|
|
16569
|
-
});
|
|
16570
|
-
if (!table) return;
|
|
16571
|
-
const { left, right, top } = table.range.zone;
|
|
16572
|
-
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
16573
|
-
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
16574
|
-
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
16575
|
-
sheetId,
|
|
16576
|
-
col,
|
|
16577
|
-
row
|
|
16578
|
-
}).formattedValue;
|
|
16579
|
-
});
|
|
16580
|
-
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
|
|
16581
|
-
sheetId,
|
|
16582
|
-
col,
|
|
16583
|
-
row
|
|
16584
|
-
}, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16679
|
+
unRegister(highlightProvider) {
|
|
16680
|
+
this.providers = this.providers.filter((h) => h !== highlightProvider);
|
|
16585
16681
|
}
|
|
16586
16682
|
};
|
|
16587
16683
|
|
|
@@ -16593,9 +16689,9 @@ set(value) {
|
|
|
16593
16689
|
position: types.CellPosition(),
|
|
16594
16690
|
sortDirection: types.or([types.SortDirection, types.literal("none")])
|
|
16595
16691
|
});
|
|
16596
|
-
|
|
16692
|
+
hoveredCellOverlayStore;
|
|
16597
16693
|
setup() {
|
|
16598
|
-
this.
|
|
16694
|
+
this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
|
|
16599
16695
|
}
|
|
16600
16696
|
get style() {
|
|
16601
16697
|
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
|
|
@@ -16615,7 +16711,7 @@ set(value) {
|
|
|
16615
16711
|
}
|
|
16616
16712
|
}
|
|
16617
16713
|
getBackgroundColor(cellStyle) {
|
|
16618
|
-
const overlayColor = this.
|
|
16714
|
+
const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
|
|
16619
16715
|
if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
|
|
16620
16716
|
return cellStyle.fillColor || "#FFFFFF";
|
|
16621
16717
|
}
|
|
@@ -21354,7 +21450,7 @@ set(value) {
|
|
|
21354
21450
|
//#region src/components/grid_overlay/grid_overlay.ts
|
|
21355
21451
|
function useCellHovered(env, gridRef) {
|
|
21356
21452
|
const delayedHoveredCell = useStore(DelayedHoveredCellStore);
|
|
21357
|
-
const
|
|
21453
|
+
const cellHoverOverlay = useStore(CellHoverOverlayStore);
|
|
21358
21454
|
const viewStore = useStore(ViewportsStore);
|
|
21359
21455
|
const hoveredPosition = {
|
|
21360
21456
|
col: void 0,
|
|
@@ -21398,7 +21494,9 @@ set(value) {
|
|
|
21398
21494
|
if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
|
|
21399
21495
|
({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
|
|
21400
21496
|
lastMoved = Date.now();
|
|
21401
|
-
|
|
21497
|
+
const position = getPosition();
|
|
21498
|
+
if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
|
|
21499
|
+
else cellHoverOverlay.hover(getPosition());
|
|
21402
21500
|
}
|
|
21403
21501
|
}
|
|
21404
21502
|
function recompute() {
|
|
@@ -21409,7 +21507,7 @@ set(value) {
|
|
|
21409
21507
|
const zoomedMouseEvent = withZoom(env, e);
|
|
21410
21508
|
const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
|
|
21411
21509
|
const gridRect = getElBoundingRect(gridRef());
|
|
21412
|
-
if (y
|
|
21510
|
+
if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
|
|
21413
21511
|
else return pause();
|
|
21414
21512
|
}
|
|
21415
21513
|
(0, _odoo_owl.useListener)(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
|
|
@@ -21446,7 +21544,7 @@ set(value) {
|
|
|
21446
21544
|
gridOverlayDimensions: types.string(),
|
|
21447
21545
|
hasFooter: types.boolean().optional(() => true)
|
|
21448
21546
|
});
|
|
21449
|
-
gridOverlayRef =
|
|
21547
|
+
gridOverlayRef = _odoo_owl.signal.ref();
|
|
21450
21548
|
cellPopovers;
|
|
21451
21549
|
paintFormatStore;
|
|
21452
21550
|
hoveredIconStore;
|
|
@@ -21473,7 +21571,7 @@ set(value) {
|
|
|
21473
21571
|
return el;
|
|
21474
21572
|
}
|
|
21475
21573
|
get style() {
|
|
21476
|
-
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "
|
|
21574
|
+
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
|
|
21477
21575
|
}
|
|
21478
21576
|
get isPaintingFormat() {
|
|
21479
21577
|
return this.paintFormatStore.isActive;
|
|
@@ -22113,8 +22211,9 @@ set(value) {
|
|
|
22113
22211
|
const CELL_ANIMATION_DURATION = 200;
|
|
22114
22212
|
var GridRenderer = class extends DisposableStore {
|
|
22115
22213
|
renderer;
|
|
22214
|
+
storeGetters = ["drawLayer"];
|
|
22116
22215
|
fingerprints;
|
|
22117
|
-
|
|
22216
|
+
cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
22118
22217
|
hoveredIcon;
|
|
22119
22218
|
lastRenderSheetId = void 0;
|
|
22120
22219
|
lastRenderBoxes = /* @__PURE__ */ new Map();
|
|
@@ -22128,15 +22227,14 @@ set(value) {
|
|
|
22128
22227
|
const model = get(ModelStore);
|
|
22129
22228
|
this.getters = model.getters;
|
|
22130
22229
|
this.fingerprints = get(FormulaFingerprintStore);
|
|
22131
|
-
this.hoveredTables = get(HoveredTableStore);
|
|
22132
22230
|
this.hoveredIcon = get(HoveredIconStore);
|
|
22133
22231
|
model.on("command-dispatched", this, this.handle);
|
|
22134
22232
|
model.on("command-finalized", this, this.finalize);
|
|
22135
|
-
this.renderer.register(this);
|
|
22233
|
+
if (this.renderer) this.renderer.register(this);
|
|
22136
22234
|
this.onDispose(() => {
|
|
22137
22235
|
model.off("command-dispatched", this);
|
|
22138
22236
|
model.off("command-finalized", this);
|
|
22139
|
-
this.renderer.unRegister(this);
|
|
22237
|
+
if (this.renderer) this.renderer.unRegister(this);
|
|
22140
22238
|
});
|
|
22141
22239
|
}
|
|
22142
22240
|
handle(cmd) {
|
|
@@ -22680,7 +22778,7 @@ set(value) {
|
|
|
22680
22778
|
border: this.getters.getCellComputedBorder(position) || void 0,
|
|
22681
22779
|
style,
|
|
22682
22780
|
dataBarFill,
|
|
22683
|
-
overlayColor: this.
|
|
22781
|
+
overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
|
|
22684
22782
|
isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
|
|
22685
22783
|
icons: cellIcons,
|
|
22686
22784
|
disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
|
|
@@ -22856,6 +22954,7 @@ set(value) {
|
|
|
22856
22954
|
return boxes;
|
|
22857
22955
|
}
|
|
22858
22956
|
getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
|
|
22957
|
+
if (!this.renderer) return boxes;
|
|
22859
22958
|
this.updateAnimationsProgress(timeStamp);
|
|
22860
22959
|
this.addNewAnimations(boxes, oldBoxes, timeStamp);
|
|
22861
22960
|
if (this.animations.size > 0) {
|
|
@@ -23000,7 +23099,7 @@ set(value) {
|
|
|
23000
23099
|
offset: types.Pixel(),
|
|
23001
23100
|
onScroll: types.function()
|
|
23002
23101
|
});
|
|
23003
|
-
scrollbarRef =
|
|
23102
|
+
scrollbarRef = _odoo_owl.signal.ref();
|
|
23004
23103
|
scrollbar;
|
|
23005
23104
|
setup() {
|
|
23006
23105
|
this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
|
|
@@ -23102,6 +23201,9 @@ set(value) {
|
|
|
23102
23201
|
zoneToDisplay: this.range.zone,
|
|
23103
23202
|
getFooterSize: () => 0
|
|
23104
23203
|
});
|
|
23204
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
23205
|
+
cellHoverOverlayStore.register(this);
|
|
23206
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
23105
23207
|
}
|
|
23106
23208
|
handle(cmd) {
|
|
23107
23209
|
this.cachedColDimensions = void 0;
|
|
@@ -23250,6 +23352,16 @@ set(value) {
|
|
|
23250
23352
|
}
|
|
23251
23353
|
return normalizedWeights;
|
|
23252
23354
|
}
|
|
23355
|
+
getHighlightedPositions(position) {
|
|
23356
|
+
const highlightedPositions = [];
|
|
23357
|
+
const { col, row, sheetId } = position;
|
|
23358
|
+
if (sheetId !== this.range.sheetId || !isInside(col, row, this.range.zone)) return highlightedPositions;
|
|
23359
|
+
for (let col = this.range.zone.left; col <= this.range.zone.right; col++) highlightedPositions.push({
|
|
23360
|
+
...position,
|
|
23361
|
+
col
|
|
23362
|
+
});
|
|
23363
|
+
return highlightedPositions;
|
|
23364
|
+
}
|
|
23253
23365
|
};
|
|
23254
23366
|
|
|
23255
23367
|
//#endregion
|
|
@@ -23266,10 +23378,11 @@ set(value) {
|
|
|
23266
23378
|
range: types.Range(),
|
|
23267
23379
|
canResizeColumns: types.boolean().optional(true),
|
|
23268
23380
|
onResizeColumns: types.function().optional(),
|
|
23269
|
-
columnWeights: types.array().optional()
|
|
23381
|
+
columnWeights: types.array().optional(),
|
|
23382
|
+
size: types.object()
|
|
23270
23383
|
});
|
|
23271
|
-
canvasRef =
|
|
23272
|
-
containerRef =
|
|
23384
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
23385
|
+
containerRef = _odoo_owl.signal.ref();
|
|
23273
23386
|
store;
|
|
23274
23387
|
dndState = (0, _odoo_owl.proxy)({ col: void 0 });
|
|
23275
23388
|
onMouseWheel;
|
|
@@ -23281,8 +23394,8 @@ set(value) {
|
|
|
23281
23394
|
useChildStoreProvider([
|
|
23282
23395
|
ViewportsStore,
|
|
23283
23396
|
HoveredIconStore,
|
|
23284
|
-
HoveredTableStore,
|
|
23285
23397
|
ClickableCellsStore,
|
|
23398
|
+
CellHoverOverlayStore,
|
|
23286
23399
|
DelayedHoveredCellStore,
|
|
23287
23400
|
CellPopoverStore
|
|
23288
23401
|
]);
|
|
@@ -23291,18 +23404,10 @@ set(value) {
|
|
|
23291
23404
|
this.zoomStore = useStore(ZoomStore);
|
|
23292
23405
|
this.cellPopoverStore = useStore(CellPopoverStore);
|
|
23293
23406
|
this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
|
|
23294
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
23295
|
-
this.store.setContainerSize(this.containerWidth, this.containerHeight);
|
|
23296
|
-
});
|
|
23297
|
-
(0, _odoo_owl.useEffect)(() => {
|
|
23298
|
-
const el = this.containerRef();
|
|
23299
|
-
if (el) resizeObserver.observe(el);
|
|
23300
|
-
return () => resizeObserver.disconnect();
|
|
23301
|
-
});
|
|
23302
23407
|
(0, _odoo_owl.useEffect)(() => {
|
|
23303
23408
|
this.store.setRange(this.props.range);
|
|
23304
23409
|
if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
|
|
23305
|
-
this.store.setContainerSize(this.
|
|
23410
|
+
this.store.setContainerSize(this.contentWidth, this.props.size.height);
|
|
23306
23411
|
});
|
|
23307
23412
|
useGridDrawing({
|
|
23308
23413
|
canvasRef: this.canvasRef,
|
|
@@ -23321,14 +23426,11 @@ set(value) {
|
|
|
23321
23426
|
}
|
|
23322
23427
|
});
|
|
23323
23428
|
}
|
|
23324
|
-
get
|
|
23325
|
-
return
|
|
23326
|
-
}
|
|
23327
|
-
get containerHeight() {
|
|
23328
|
-
return Math.floor(getElBoundingRect(this.containerRef()).height / this.zoomStore.zoomLevel);
|
|
23429
|
+
get contentWidth() {
|
|
23430
|
+
return this.hasVerticalScrollBar ? this.props.size.width - this.zoomStore.scrollBarWidth : this.props.size.width;
|
|
23329
23431
|
}
|
|
23330
23432
|
get hasVerticalScrollBar() {
|
|
23331
|
-
return this.
|
|
23433
|
+
return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
|
|
23332
23434
|
}
|
|
23333
23435
|
get scrollBarContainerStyle() {
|
|
23334
23436
|
return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
|
|
@@ -23380,7 +23482,7 @@ set(value) {
|
|
|
23380
23482
|
};
|
|
23381
23483
|
const onMouseMove = (ev) => {
|
|
23382
23484
|
deltaX = withZoom(this.env, ev).clientX - initialX;
|
|
23383
|
-
const weightDelta = deltaX / this.
|
|
23485
|
+
const weightDelta = deltaX / this.props.size.width * totalWeight;
|
|
23384
23486
|
this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
|
|
23385
23487
|
};
|
|
23386
23488
|
startDnd(onMouseMove, onMouseUp);
|
|
@@ -23410,8 +23512,8 @@ set(value) {
|
|
|
23410
23512
|
name: types.string().optional(),
|
|
23411
23513
|
title: types.string().optional()
|
|
23412
23514
|
});
|
|
23413
|
-
selectRef =
|
|
23414
|
-
dropdownRef =
|
|
23515
|
+
selectRef = _odoo_owl.signal.ref();
|
|
23516
|
+
dropdownRef = _odoo_owl.signal.ref();
|
|
23415
23517
|
state = (0, _odoo_owl.proxy)({
|
|
23416
23518
|
isPopoverOpen: false,
|
|
23417
23519
|
hoveredValue: void 0
|
|
@@ -23521,7 +23623,7 @@ set(value) {
|
|
|
23521
23623
|
setup() {
|
|
23522
23624
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
23523
23625
|
}
|
|
23524
|
-
infoRef =
|
|
23626
|
+
infoRef = _odoo_owl.signal.ref();
|
|
23525
23627
|
get popoverProps() {
|
|
23526
23628
|
return {
|
|
23527
23629
|
anchorRect: this.props.anchorRect,
|
|
@@ -23666,8 +23768,8 @@ set(value) {
|
|
|
23666
23768
|
isFullScreen: types.boolean().optional(),
|
|
23667
23769
|
openContextMenu: types.function().optional()
|
|
23668
23770
|
});
|
|
23669
|
-
carouselTabsRef =
|
|
23670
|
-
carouselTabsDropdownRef =
|
|
23771
|
+
carouselTabsRef = _odoo_owl.signal.ref();
|
|
23772
|
+
carouselTabsDropdownRef = _odoo_owl.signal.ref();
|
|
23671
23773
|
menuState = (0, _odoo_owl.proxy)({
|
|
23672
23774
|
isOpen: false,
|
|
23673
23775
|
anchorRect: null,
|
|
@@ -23735,6 +23837,19 @@ set(value) {
|
|
|
23735
23837
|
} else cssProperties["background-color"] = backgroundColor;
|
|
23736
23838
|
return cssPropertiesToCss(cssProperties);
|
|
23737
23839
|
}
|
|
23840
|
+
get contentStyle() {
|
|
23841
|
+
return cssPropertiesToCss(this.rectToCss(this.carouselLayout.contentRect));
|
|
23842
|
+
}
|
|
23843
|
+
get headerStyle() {
|
|
23844
|
+
return cssPropertiesToCss({
|
|
23845
|
+
...this.rectToCss(this.carouselLayout.headerRect),
|
|
23846
|
+
"line-height": String(this.carouselLayout.headerLineHeight)
|
|
23847
|
+
});
|
|
23848
|
+
}
|
|
23849
|
+
get separatorStyle() {
|
|
23850
|
+
const separatorRect = this.carouselLayout.separatorRect;
|
|
23851
|
+
return separatorRect ? cssPropertiesToCss(this.rectToCss(separatorRect)) : "";
|
|
23852
|
+
}
|
|
23738
23853
|
get title() {
|
|
23739
23854
|
return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
|
|
23740
23855
|
}
|
|
@@ -23744,6 +23859,14 @@ set(value) {
|
|
|
23744
23859
|
...this.carousel.title
|
|
23745
23860
|
})));
|
|
23746
23861
|
}
|
|
23862
|
+
rectToCss(rect) {
|
|
23863
|
+
return {
|
|
23864
|
+
top: `${rect.y}px`,
|
|
23865
|
+
left: `${rect.x}px`,
|
|
23866
|
+
width: `${rect.width}px`,
|
|
23867
|
+
height: `${rect.height}px`
|
|
23868
|
+
};
|
|
23869
|
+
}
|
|
23747
23870
|
updateTabsVisibility() {
|
|
23748
23871
|
const tabsContainerEl = this.carouselTabsRef();
|
|
23749
23872
|
const dropDownEl = this.carouselTabsDropdownRef();
|
|
@@ -23835,6 +23958,13 @@ set(value) {
|
|
|
23835
23958
|
get canResizeDataViewColumns() {
|
|
23836
23959
|
return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
23837
23960
|
}
|
|
23961
|
+
get carouselLayout() {
|
|
23962
|
+
return getCarouselLayout({
|
|
23963
|
+
...this.props.figureUI,
|
|
23964
|
+
x: 0,
|
|
23965
|
+
y: 0
|
|
23966
|
+
}, this.carousel, this.selectedCarouselItem);
|
|
23967
|
+
}
|
|
23838
23968
|
};
|
|
23839
23969
|
|
|
23840
23970
|
//#endregion
|
|
@@ -23875,7 +24005,7 @@ set(value) {
|
|
|
23875
24005
|
figureUI: types.FigureUI(),
|
|
23876
24006
|
openContextMenu: types.function()
|
|
23877
24007
|
});
|
|
23878
|
-
menuButtonRef =
|
|
24008
|
+
menuButtonRef = _odoo_owl.signal.ref();
|
|
23879
24009
|
showMenu(ev) {
|
|
23880
24010
|
if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
|
|
23881
24011
|
figureId: this.props.figureUI.id,
|
|
@@ -23903,7 +24033,8 @@ set(value) {
|
|
|
23903
24033
|
menuBuilder: getChartMenuActions,
|
|
23904
24034
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23905
24035
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23906
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24036
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24037
|
+
isThemeDependant: true
|
|
23907
24038
|
});
|
|
23908
24039
|
figureRegistry.add("image", {
|
|
23909
24040
|
Component: ImageFigure,
|
|
@@ -23912,14 +24043,16 @@ set(value) {
|
|
|
23912
24043
|
borderWidth: () => 0,
|
|
23913
24044
|
hasShadow: () => false,
|
|
23914
24045
|
isRounded: () => false,
|
|
23915
|
-
menuBuilder: getImageMenuActions
|
|
24046
|
+
menuBuilder: getImageMenuActions,
|
|
24047
|
+
isThemeDependant: false
|
|
23916
24048
|
});
|
|
23917
24049
|
figureRegistry.add("carousel", {
|
|
23918
24050
|
Component: CarouselFigure,
|
|
23919
24051
|
menuBuilder: getCarouselMenuActions,
|
|
23920
24052
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23921
24053
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23922
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24054
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24055
|
+
isThemeDependant: true
|
|
23923
24056
|
});
|
|
23924
24057
|
|
|
23925
24058
|
//#endregion
|
|
@@ -23941,10 +24074,13 @@ set(value) {
|
|
|
23941
24074
|
anchorRect: null,
|
|
23942
24075
|
menuItems: []
|
|
23943
24076
|
});
|
|
23944
|
-
figureRef =
|
|
24077
|
+
figureRef = _odoo_owl.signal.ref();
|
|
23945
24078
|
get isSelected() {
|
|
23946
24079
|
return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
|
|
23947
24080
|
}
|
|
24081
|
+
get isThemeDependant() {
|
|
24082
|
+
return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
|
|
24083
|
+
}
|
|
23948
24084
|
get figureRegistry() {
|
|
23949
24085
|
return figureRegistry;
|
|
23950
24086
|
}
|
|
@@ -29543,6 +29679,35 @@ set(value) {
|
|
|
29543
29679
|
}));
|
|
29544
29680
|
}
|
|
29545
29681
|
|
|
29682
|
+
//#endregion
|
|
29683
|
+
//#region src/helpers/figures/charts/chart_data_source_helpers.ts
|
|
29684
|
+
function getUnboundRange(getters, zone) {
|
|
29685
|
+
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29686
|
+
}
|
|
29687
|
+
function isDatasetTitled(getters, zone) {
|
|
29688
|
+
const sheetId = getters.getActiveSheetId();
|
|
29689
|
+
const cell = getters.getEvaluatedCell({
|
|
29690
|
+
sheetId,
|
|
29691
|
+
col: zone.left,
|
|
29692
|
+
row: zone.top
|
|
29693
|
+
});
|
|
29694
|
+
return !["number", "empty"].includes(cell.type);
|
|
29695
|
+
}
|
|
29696
|
+
function dataset(zone, getters, id = "0") {
|
|
29697
|
+
return {
|
|
29698
|
+
dataRange: getUnboundRange(getters, zone),
|
|
29699
|
+
dataSetId: id
|
|
29700
|
+
};
|
|
29701
|
+
}
|
|
29702
|
+
function rangeSource(dataSets, dataSetsHaveTitle, labelRange) {
|
|
29703
|
+
return {
|
|
29704
|
+
type: "range",
|
|
29705
|
+
dataSets,
|
|
29706
|
+
dataSetsHaveTitle,
|
|
29707
|
+
labelRange
|
|
29708
|
+
};
|
|
29709
|
+
}
|
|
29710
|
+
|
|
29546
29711
|
//#endregion
|
|
29547
29712
|
//#region src/helpers/figures/charts/smart_chart_engine.ts
|
|
29548
29713
|
const DEFAULT_BAR_CHART_CONFIG = {
|
|
@@ -29573,9 +29738,6 @@ set(value) {
|
|
|
29573
29738
|
labelsAsText: false,
|
|
29574
29739
|
humanize: true
|
|
29575
29740
|
};
|
|
29576
|
-
function getUnboundRange(getters, zone) {
|
|
29577
|
-
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29578
|
-
}
|
|
29579
29741
|
function detectColumnType(cells) {
|
|
29580
29742
|
if (!cells.length) return "empty";
|
|
29581
29743
|
const counts = {
|
|
@@ -29622,14 +29784,6 @@ set(value) {
|
|
|
29622
29784
|
totalCount: values.length
|
|
29623
29785
|
};
|
|
29624
29786
|
}
|
|
29625
|
-
function isDatasetTitled(getters, column) {
|
|
29626
|
-
const titleCell = getters.getEvaluatedCell({
|
|
29627
|
-
sheetId: getters.getActiveSheetId(),
|
|
29628
|
-
col: column.zone.left,
|
|
29629
|
-
row: column.zone.top
|
|
29630
|
-
});
|
|
29631
|
-
return !["number", "empty"].includes(titleCell.type);
|
|
29632
|
-
}
|
|
29633
29787
|
/**
|
|
29634
29788
|
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
29635
29789
|
* - If the column contains a single cell, create a scorecard.
|
|
@@ -29641,8 +29795,7 @@ set(value) {
|
|
|
29641
29795
|
function buildSingleColumnChart(column, getters) {
|
|
29642
29796
|
const { type, zone } = column;
|
|
29643
29797
|
const sheetId = getters.getActiveSheetId();
|
|
29644
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
29645
|
-
const dataRange = getUnboundRange(getters, zone);
|
|
29798
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
|
|
29646
29799
|
const titleCell = getters.getEvaluatedCell({
|
|
29647
29800
|
sheetId,
|
|
29648
29801
|
col: zone.left,
|
|
@@ -29653,14 +29806,7 @@ set(value) {
|
|
|
29653
29806
|
case "percentage": return {
|
|
29654
29807
|
type: "pie",
|
|
29655
29808
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29656
|
-
dataSource:
|
|
29657
|
-
type: "range",
|
|
29658
|
-
dataSets: [{
|
|
29659
|
-
dataRange,
|
|
29660
|
-
dataSetId: "0"
|
|
29661
|
-
}],
|
|
29662
|
-
dataSetsHaveTitle
|
|
29663
|
-
},
|
|
29809
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29664
29810
|
dataSetStyles: {},
|
|
29665
29811
|
legendPosition: "none"
|
|
29666
29812
|
};
|
|
@@ -29671,45 +29817,28 @@ set(value) {
|
|
|
29671
29817
|
return {
|
|
29672
29818
|
type: "pie",
|
|
29673
29819
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
29674
|
-
dataSource:
|
|
29675
|
-
type: "range",
|
|
29676
|
-
dataSets: [{
|
|
29677
|
-
dataRange,
|
|
29678
|
-
dataSetId: "0"
|
|
29679
|
-
}],
|
|
29680
|
-
labelRange: dataRange,
|
|
29681
|
-
dataSetsHaveTitle: hasUniqueTitle
|
|
29682
|
-
},
|
|
29820
|
+
dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
|
|
29683
29821
|
dataSetStyles: {},
|
|
29684
29822
|
aggregated: true,
|
|
29685
29823
|
legendPosition: "top"
|
|
29686
29824
|
};
|
|
29687
|
-
case "date":
|
|
29688
|
-
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
29697
|
-
|
|
29698
|
-
|
|
29699
|
-
verticalGroupBy: "month_number"
|
|
29700
|
-
};
|
|
29825
|
+
case "date": {
|
|
29826
|
+
const dataRange = getUnboundRange(getters, zone);
|
|
29827
|
+
return {
|
|
29828
|
+
type: "calendar",
|
|
29829
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29830
|
+
dataSource: rangeSource([], dataSetsHaveTitle, dataRange),
|
|
29831
|
+
dataSetStyles: {},
|
|
29832
|
+
legendPosition: "left",
|
|
29833
|
+
horizontalGroupBy: "day_of_week",
|
|
29834
|
+
verticalGroupBy: "month_number"
|
|
29835
|
+
};
|
|
29836
|
+
}
|
|
29701
29837
|
}
|
|
29702
29838
|
return {
|
|
29703
29839
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29704
29840
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29705
|
-
dataSource:
|
|
29706
|
-
type: "range",
|
|
29707
|
-
dataSets: [{
|
|
29708
|
-
dataRange,
|
|
29709
|
-
dataSetId: "0"
|
|
29710
|
-
}],
|
|
29711
|
-
dataSetsHaveTitle
|
|
29712
|
-
},
|
|
29841
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29713
29842
|
dataSetStyles: {}
|
|
29714
29843
|
};
|
|
29715
29844
|
}
|
|
@@ -29727,15 +29856,7 @@ set(value) {
|
|
|
29727
29856
|
if (columns[1].type === "percentage") return {
|
|
29728
29857
|
type: "pie",
|
|
29729
29858
|
title: {},
|
|
29730
|
-
dataSource:
|
|
29731
|
-
type: "range",
|
|
29732
|
-
dataSets: [{
|
|
29733
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29734
|
-
dataSetId: "0"
|
|
29735
|
-
}],
|
|
29736
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29737
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29738
|
-
},
|
|
29859
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29739
29860
|
dataSetStyles: {},
|
|
29740
29861
|
aggregated: true,
|
|
29741
29862
|
legendPosition: "none"
|
|
@@ -29743,15 +29864,7 @@ set(value) {
|
|
|
29743
29864
|
if (columns[0].type === "number" && columns[1].type === "number") return {
|
|
29744
29865
|
type: "scatter",
|
|
29745
29866
|
title: {},
|
|
29746
|
-
dataSource:
|
|
29747
|
-
type: "range",
|
|
29748
|
-
dataSets: [{
|
|
29749
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29750
|
-
dataSetId: "0"
|
|
29751
|
-
}],
|
|
29752
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29753
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29754
|
-
},
|
|
29867
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29755
29868
|
dataSetStyles: {},
|
|
29756
29869
|
labelsAsText: false,
|
|
29757
29870
|
legendPosition: "none"
|
|
@@ -29759,49 +29872,25 @@ set(value) {
|
|
|
29759
29872
|
if (columns[0].type === "date" && columns[1].type === "number") return {
|
|
29760
29873
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29761
29874
|
type: "line",
|
|
29762
|
-
dataSource:
|
|
29763
|
-
type: "range",
|
|
29764
|
-
dataSets: [{
|
|
29765
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29766
|
-
dataSetId: "0"
|
|
29767
|
-
}],
|
|
29768
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29769
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
|
|
29770
|
-
},
|
|
29875
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29771
29876
|
dataSetStyles: {}
|
|
29772
29877
|
};
|
|
29773
29878
|
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
29774
29879
|
const textColumn = columns[0];
|
|
29775
29880
|
const numberColumn = columns[1];
|
|
29776
29881
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
29777
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
29882
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
|
|
29778
29883
|
if (uniqueCount !== totalCount) return {
|
|
29779
29884
|
type: "treemap",
|
|
29780
29885
|
title: {},
|
|
29781
|
-
dataSource:
|
|
29782
|
-
type: "range",
|
|
29783
|
-
dataSets: [{
|
|
29784
|
-
dataRange: getUnboundRange(getters, textColumn.zone),
|
|
29785
|
-
dataSetId: "0"
|
|
29786
|
-
}],
|
|
29787
|
-
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
29788
|
-
dataSetsHaveTitle
|
|
29789
|
-
},
|
|
29886
|
+
dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
|
|
29790
29887
|
dataSetStyles: {},
|
|
29791
29888
|
legendPosition: "none"
|
|
29792
29889
|
};
|
|
29793
29890
|
}
|
|
29794
29891
|
return {
|
|
29795
29892
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29796
|
-
dataSource:
|
|
29797
|
-
type: "range",
|
|
29798
|
-
dataSets: [{
|
|
29799
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29800
|
-
dataSetId: "0"
|
|
29801
|
-
}],
|
|
29802
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29803
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29804
|
-
},
|
|
29893
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29805
29894
|
dataSetStyles: {}
|
|
29806
29895
|
};
|
|
29807
29896
|
}
|
|
@@ -29815,42 +29904,26 @@ set(value) {
|
|
|
29815
29904
|
*/
|
|
29816
29905
|
function buildMultiColumnChart(columns, getters) {
|
|
29817
29906
|
if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
|
|
29818
|
-
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
29907
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
|
|
29819
29908
|
const lastColumn = columns[columns.length - 1];
|
|
29820
29909
|
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
29821
29910
|
if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
|
|
29822
|
-
const dataSets = columnsExceptLast.map(({ zone }, i) => (
|
|
29823
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29824
|
-
dataSetId: i.toString()
|
|
29825
|
-
}));
|
|
29911
|
+
const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29826
29912
|
return {
|
|
29827
29913
|
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
29828
29914
|
title: {},
|
|
29829
|
-
dataSource:
|
|
29830
|
-
type: "range",
|
|
29831
|
-
dataSets,
|
|
29832
|
-
dataSetsHaveTitle,
|
|
29833
|
-
labelRange: getUnboundRange(getters, lastColumn.zone)
|
|
29834
|
-
},
|
|
29915
|
+
dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
|
|
29835
29916
|
dataSetStyles: {},
|
|
29836
29917
|
legendPosition: "none"
|
|
29837
29918
|
};
|
|
29838
29919
|
}
|
|
29839
29920
|
const firstColumn = columns[0];
|
|
29840
29921
|
const columnsExceptFirst = columns.slice(1);
|
|
29841
|
-
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => (
|
|
29842
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29843
|
-
dataSetId: i.toString()
|
|
29844
|
-
}));
|
|
29922
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29845
29923
|
if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
|
|
29846
29924
|
type: "pie",
|
|
29847
29925
|
title: {},
|
|
29848
|
-
dataSource:
|
|
29849
|
-
type: "range",
|
|
29850
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29851
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29852
|
-
dataSetsHaveTitle
|
|
29853
|
-
},
|
|
29926
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29854
29927
|
dataSetStyles: {},
|
|
29855
29928
|
aggregated: false,
|
|
29856
29929
|
legendPosition: "top"
|
|
@@ -29858,23 +29931,13 @@ set(value) {
|
|
|
29858
29931
|
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
|
|
29859
29932
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29860
29933
|
type: "line",
|
|
29861
|
-
dataSource:
|
|
29862
|
-
type: "range",
|
|
29863
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29864
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29865
|
-
dataSetsHaveTitle
|
|
29866
|
-
},
|
|
29934
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29867
29935
|
dataSetStyles: {},
|
|
29868
29936
|
legendPosition: "top"
|
|
29869
29937
|
};
|
|
29870
29938
|
return {
|
|
29871
29939
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29872
|
-
dataSource:
|
|
29873
|
-
type: "range",
|
|
29874
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29875
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29876
|
-
dataSetsHaveTitle
|
|
29877
|
-
},
|
|
29940
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29878
29941
|
dataSetStyles: {},
|
|
29879
29942
|
legendPosition: "top"
|
|
29880
29943
|
};
|
|
@@ -29901,17 +29964,10 @@ set(value) {
|
|
|
29901
29964
|
function getSmartChartDefinition(zones, getters) {
|
|
29902
29965
|
const columns = categorizeColumns(zones, getters);
|
|
29903
29966
|
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
29904
|
-
const dataSets = columns.map(({ zone }, i) => (
|
|
29905
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29906
|
-
dataSetId: i.toString()
|
|
29907
|
-
}));
|
|
29967
|
+
const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29908
29968
|
return {
|
|
29909
29969
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29910
|
-
dataSource:
|
|
29911
|
-
type: "range",
|
|
29912
|
-
dataSets,
|
|
29913
|
-
dataSetsHaveTitle: false
|
|
29914
|
-
}
|
|
29970
|
+
dataSource: rangeSource(dataSets, false)
|
|
29915
29971
|
};
|
|
29916
29972
|
}
|
|
29917
29973
|
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
@@ -31599,7 +31655,7 @@ set(value) {
|
|
|
31599
31655
|
dragAndDrop = useDragAndDropListItems();
|
|
31600
31656
|
focusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31601
31657
|
unfocusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31602
|
-
selectionRef =
|
|
31658
|
+
selectionRef = _odoo_owl.signal.ref();
|
|
31603
31659
|
DOMFocusableElementStore;
|
|
31604
31660
|
store;
|
|
31605
31661
|
get ranges() {
|
|
@@ -32106,7 +32162,7 @@ set(value) {
|
|
|
32106
32162
|
dropdownMaxHeight: types.Pixel().optional(),
|
|
32107
32163
|
class: types.string().optional()
|
|
32108
32164
|
});
|
|
32109
|
-
colorPickerButtonRef =
|
|
32165
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
32110
32166
|
get iconStyle() {
|
|
32111
32167
|
return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
|
|
32112
32168
|
}
|
|
@@ -32134,8 +32190,8 @@ set(value) {
|
|
|
32134
32190
|
});
|
|
32135
32191
|
dropdown = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
32136
32192
|
inputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
32137
|
-
rootEditorRef =
|
|
32138
|
-
valueListRef =
|
|
32193
|
+
rootEditorRef = _odoo_owl.signal.ref();
|
|
32194
|
+
valueListRef = _odoo_owl.signal.ref();
|
|
32139
32195
|
DOMFocusableElementStore;
|
|
32140
32196
|
setup() {
|
|
32141
32197
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -32441,7 +32497,7 @@ set(value) {
|
|
|
32441
32497
|
});
|
|
32442
32498
|
categories = chartCategories;
|
|
32443
32499
|
chartTypeByCategories = {};
|
|
32444
|
-
popoverRef =
|
|
32500
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
32445
32501
|
setup() {
|
|
32446
32502
|
(0, _odoo_owl.useListener)(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
|
|
32447
32503
|
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
@@ -32466,7 +32522,7 @@ set(value) {
|
|
|
32466
32522
|
static template = "o-spreadsheet-CogWheelMenu";
|
|
32467
32523
|
static components = { MenuPopover };
|
|
32468
32524
|
props = (0, _odoo_owl.useProps)({ items: types.array(types.ActionSpec()) });
|
|
32469
|
-
buttonRef =
|
|
32525
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
32470
32526
|
menuState = (0, _odoo_owl.proxy)({
|
|
32471
32527
|
isOpen: false,
|
|
32472
32528
|
anchorRect: null,
|
|
@@ -32510,8 +32566,8 @@ set(value) {
|
|
|
32510
32566
|
});
|
|
32511
32567
|
DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
|
|
32512
32568
|
dragAndDrop = useDragAndDropListItems();
|
|
32513
|
-
previewListRef =
|
|
32514
|
-
addChartButton =
|
|
32569
|
+
previewListRef = _odoo_owl.signal.ref();
|
|
32570
|
+
addChartButton = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
32515
32571
|
state = (0, _odoo_owl.proxy)({
|
|
32516
32572
|
popoverProps: void 0,
|
|
32517
32573
|
currentRange: void 0
|
|
@@ -32955,7 +33011,7 @@ set(value) {
|
|
|
32955
33011
|
var Collapse = class extends Component {
|
|
32956
33012
|
static template = "o-spreadsheet-Collapse";
|
|
32957
33013
|
props = (0, _odoo_owl.useProps)({ isCollapsed: types.boolean() });
|
|
32958
|
-
contentRef =
|
|
33014
|
+
contentRef = _odoo_owl.signal.ref();
|
|
32959
33015
|
setup() {
|
|
32960
33016
|
(0, _odoo_owl.onMounted)(() => {
|
|
32961
33017
|
if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
|
|
@@ -33008,7 +33064,7 @@ set(value) {
|
|
|
33008
33064
|
TextInput
|
|
33009
33065
|
};
|
|
33010
33066
|
props = (0, _odoo_owl.useProps)(chartSidePanelPropsDefinition);
|
|
33011
|
-
editorRef =
|
|
33067
|
+
editorRef = _odoo_owl.signal.ref();
|
|
33012
33068
|
state = (0, _odoo_owl.proxy)({
|
|
33013
33069
|
annotationTextInput: this.props.definition.annotationText || "",
|
|
33014
33070
|
isOverflowing: false
|
|
@@ -33330,7 +33386,7 @@ set(value) {
|
|
|
33330
33386
|
onColorPicked: types.function(),
|
|
33331
33387
|
disableNoColor: types.boolean().optional()
|
|
33332
33388
|
});
|
|
33333
|
-
colorPickerButtonRef =
|
|
33389
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
33334
33390
|
state;
|
|
33335
33391
|
setup() {
|
|
33336
33392
|
this.state = (0, _odoo_owl.proxy)({ pickerOpened: false });
|
|
@@ -36172,6 +36228,7 @@ set(value) {
|
|
|
36172
36228
|
getDefinedAxis: () => getDefinedAxis,
|
|
36173
36229
|
getPieColors: () => getPieColors,
|
|
36174
36230
|
isTrendLineAxis: () => isTrendLineAxis,
|
|
36231
|
+
limitChartConfigDataPoints: () => limitChartConfigDataPoints,
|
|
36175
36232
|
shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
|
|
36176
36233
|
toExcelDataset: () => toExcelDataset,
|
|
36177
36234
|
toExcelLabelRange: () => toExcelLabelRange,
|
|
@@ -36942,7 +36999,7 @@ set(value) {
|
|
|
36942
36999
|
popoverProps: void 0,
|
|
36943
37000
|
popoverStyle: ""
|
|
36944
37001
|
});
|
|
36945
|
-
popoverRef =
|
|
37002
|
+
popoverRef = _odoo_owl.signal.ref(HTMLTableElement);
|
|
36946
37003
|
setup() {
|
|
36947
37004
|
useExternalListener(window, "click", this.closePopover);
|
|
36948
37005
|
}
|
|
@@ -37191,7 +37248,7 @@ set(value) {
|
|
|
37191
37248
|
onValueSelected: types.function(),
|
|
37192
37249
|
onValueHovered: types.function()
|
|
37193
37250
|
});
|
|
37194
|
-
autoCompleteListRef =
|
|
37251
|
+
autoCompleteListRef = _odoo_owl.signal.ref();
|
|
37195
37252
|
setup() {
|
|
37196
37253
|
(0, _odoo_owl.useEffect)(() => {
|
|
37197
37254
|
const selectedIndex = this.props.selectedIndex;
|
|
@@ -37482,7 +37539,7 @@ set(value) {
|
|
|
37482
37539
|
anchorRect: types.Rect()
|
|
37483
37540
|
});
|
|
37484
37541
|
spreadsheetRect = useSpreadsheetRect();
|
|
37485
|
-
bubbleRef =
|
|
37542
|
+
bubbleRef = _odoo_owl.signal.ref();
|
|
37486
37543
|
setup() {
|
|
37487
37544
|
(0, _odoo_owl.useEffect)(() => {
|
|
37488
37545
|
const el = this.bubbleRef();
|
|
@@ -37527,8 +37584,8 @@ set(value) {
|
|
|
37527
37584
|
showAssistant: types.boolean().optional(true)
|
|
37528
37585
|
});
|
|
37529
37586
|
DOMFocusableElementStore;
|
|
37530
|
-
composerRef =
|
|
37531
|
-
containerRef =
|
|
37587
|
+
composerRef = _odoo_owl.signal.ref();
|
|
37588
|
+
containerRef = _odoo_owl.signal.ref();
|
|
37532
37589
|
contentHelper = new ContentEditableHelper(this.composerRef());
|
|
37533
37590
|
composerState = (0, _odoo_owl.proxy)({
|
|
37534
37591
|
positionStart: 0,
|
|
@@ -40030,7 +40087,7 @@ set(value) {
|
|
|
40030
40087
|
chartId: types.UID(),
|
|
40031
40088
|
chartPanelStore: types.Store()
|
|
40032
40089
|
});
|
|
40033
|
-
selectRef =
|
|
40090
|
+
selectRef = _odoo_owl.signal.ref();
|
|
40034
40091
|
state = (0, _odoo_owl.proxy)({ popoverProps: void 0 });
|
|
40035
40092
|
getSupportedChartTypes() {
|
|
40036
40093
|
let supportedTypes;
|
|
@@ -40598,7 +40655,7 @@ set(value) {
|
|
|
40598
40655
|
highlightPositions: []
|
|
40599
40656
|
});
|
|
40600
40657
|
store;
|
|
40601
|
-
chartCanvasRef =
|
|
40658
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
40602
40659
|
chart;
|
|
40603
40660
|
setup() {
|
|
40604
40661
|
this.store = useStore(ColumnStatisticsStore);
|
|
@@ -42026,7 +42083,7 @@ set(value) {
|
|
|
42026
42083
|
class: types.string()
|
|
42027
42084
|
});
|
|
42028
42085
|
icons = ICONS;
|
|
42029
|
-
cfPreviewRef =
|
|
42086
|
+
cfPreviewRef = _odoo_owl.signal.ref();
|
|
42030
42087
|
setup() {
|
|
42031
42088
|
useHighlightsOnHover(this.cfPreviewRef, this);
|
|
42032
42089
|
}
|
|
@@ -42083,7 +42140,7 @@ set(value) {
|
|
|
42083
42140
|
static components = { ConditionalFormatPreview };
|
|
42084
42141
|
props = (0, _odoo_owl.useProps)({ onCloseSidePanel: types.function() });
|
|
42085
42142
|
dragAndDrop = useDragAndDropListItems();
|
|
42086
|
-
cfListRef =
|
|
42143
|
+
cfListRef = _odoo_owl.signal.ref();
|
|
42087
42144
|
get conditionalFormats() {
|
|
42088
42145
|
return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
|
|
42089
42146
|
}
|
|
@@ -42143,6 +42200,1477 @@ set(value) {
|
|
|
42143
42200
|
}
|
|
42144
42201
|
};
|
|
42145
42202
|
|
|
42203
|
+
//#endregion
|
|
42204
|
+
//#region src/stores/chart_drag_store.ts
|
|
42205
|
+
/**
|
|
42206
|
+
* Holds the id of the figure that a dragged chart suggestion is currently
|
|
42207
|
+
* hovering over, so it can be highlighted reactively instead of through direct DOM manipulation.
|
|
42208
|
+
*/
|
|
42209
|
+
var ChartDragStore = class {
|
|
42210
|
+
mutators = ["setHighlightedFigure"];
|
|
42211
|
+
highlightedFigureId = void 0;
|
|
42212
|
+
setHighlightedFigure(figureId) {
|
|
42213
|
+
this.highlightedFigureId = figureId;
|
|
42214
|
+
}
|
|
42215
|
+
};
|
|
42216
|
+
|
|
42217
|
+
//#endregion
|
|
42218
|
+
//#region src/components/helpers/chart_drag_and_drop.ts
|
|
42219
|
+
function getDefaultChartFigureSize(type) {
|
|
42220
|
+
if (type === "scorecard") return {
|
|
42221
|
+
width: 213,
|
|
42222
|
+
height: 101
|
|
42223
|
+
};
|
|
42224
|
+
return {
|
|
42225
|
+
width: 536,
|
|
42226
|
+
height: 335
|
|
42227
|
+
};
|
|
42228
|
+
}
|
|
42229
|
+
function getCarouselOverlappingChart(figureUI, otherFigures, matchTags) {
|
|
42230
|
+
if (figureUI.tag !== "chart") return;
|
|
42231
|
+
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
42232
|
+
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
42233
|
+
let bestMatch;
|
|
42234
|
+
let smallestDistance = Infinity;
|
|
42235
|
+
for (const figure of otherFigures) {
|
|
42236
|
+
if (!matchTags.includes(figure.tag)) continue;
|
|
42237
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
42238
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
42239
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
42240
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
42241
|
+
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
42242
|
+
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
42243
|
+
smallestDistance = squaredDistance;
|
|
42244
|
+
bestMatch = figure;
|
|
42245
|
+
}
|
|
42246
|
+
}
|
|
42247
|
+
return bestMatch;
|
|
42248
|
+
}
|
|
42249
|
+
/**
|
|
42250
|
+
* Start dragging a floating preview of the given chart definition, following the mouse.
|
|
42251
|
+
* On drop, creates the chart on the grid (or inside a carousel if dropped on one).
|
|
42252
|
+
*/
|
|
42253
|
+
function startChartDragAndDrop(env, definition, ev) {
|
|
42254
|
+
const zoom = env.getStore(ZoomStore).zoomLevel;
|
|
42255
|
+
const gridPosition = gridOverlayPosition(zoom);
|
|
42256
|
+
const spreadsheet = document.querySelector(".o-spreadsheet");
|
|
42257
|
+
if (!spreadsheet) return;
|
|
42258
|
+
const startX = ev.clientX / zoom;
|
|
42259
|
+
const startY = ev.clientY / zoom;
|
|
42260
|
+
const { width, height } = getDefaultChartFigureSize(definition.type);
|
|
42261
|
+
const figureWidth = width * zoom;
|
|
42262
|
+
const figureHeight = height * zoom;
|
|
42263
|
+
const getters = env.model.getters;
|
|
42264
|
+
const sheetId = getters.getActiveSheetId();
|
|
42265
|
+
let container = null;
|
|
42266
|
+
let destroyChart = void 0;
|
|
42267
|
+
const chartDragStore = env.getStore(ChartDragStore);
|
|
42268
|
+
const previousCursor = document.body.style.cursor;
|
|
42269
|
+
document.body.style.cursor = "grabbing";
|
|
42270
|
+
/** Grid coordinates (in sheet pixels) of a mouse position, or undefined if outside the grid. */
|
|
42271
|
+
const getGridPosition = (clientX, clientY) => {
|
|
42272
|
+
if (clientX > gridPosition.x + gridPosition.width || clientY > gridPosition.y + gridPosition.height) return;
|
|
42273
|
+
const { scrollX, scrollY } = env.getStore(ViewportsStore).activeSheetScrollInfo;
|
|
42274
|
+
return {
|
|
42275
|
+
x: Math.max(0, (clientX - gridPosition.x) / zoom + scrollX),
|
|
42276
|
+
y: Math.max(0, (clientY - gridPosition.y) / zoom + scrollY)
|
|
42277
|
+
};
|
|
42278
|
+
};
|
|
42279
|
+
/** Carousel or standalone chart the dragged chart is dropped onto, if any. */
|
|
42280
|
+
const getOverlappingFigure = (clientX, clientY) => {
|
|
42281
|
+
const position = getGridPosition(clientX, clientY);
|
|
42282
|
+
if (!position) return;
|
|
42283
|
+
const figureUI = {
|
|
42284
|
+
tag: "chart",
|
|
42285
|
+
...position,
|
|
42286
|
+
width,
|
|
42287
|
+
height
|
|
42288
|
+
};
|
|
42289
|
+
const otherFigures = env.getStore(ViewportsStore).visibleFigures;
|
|
42290
|
+
return getCarouselOverlappingChart(figureUI, otherFigures, ["carousel", "chart"]);
|
|
42291
|
+
};
|
|
42292
|
+
const halfWidth = figureWidth / 2;
|
|
42293
|
+
const halfHeight = figureHeight / 2;
|
|
42294
|
+
const onMouseMove = (e) => {
|
|
42295
|
+
if (Math.abs(e.clientX - startX) <= 5 && Math.abs(e.clientY - startY) <= 5) return;
|
|
42296
|
+
if (container === null) {
|
|
42297
|
+
container = document.createElement("div");
|
|
42298
|
+
container.className = "o-chart-drag-preview os-theme-dependant position-fixed border pe-none";
|
|
42299
|
+
container.style.width = `${width}px`;
|
|
42300
|
+
container.style.height = `${height}px`;
|
|
42301
|
+
container.style.zoom = `${zoom}`;
|
|
42302
|
+
const canvas = document.createElement("canvas");
|
|
42303
|
+
canvas.className = "w-100 h-100";
|
|
42304
|
+
container.appendChild(canvas);
|
|
42305
|
+
spreadsheet.appendChild(container);
|
|
42306
|
+
destroyChart = drawChartOnCanvas(canvas, SpreadsheetChart.fromStrDefinition(getters, sheetId, definition).getRuntime(getters, "newChart"), {
|
|
42307
|
+
width,
|
|
42308
|
+
height
|
|
42309
|
+
}, definition.type, zoom);
|
|
42310
|
+
}
|
|
42311
|
+
container.style.left = `${Math.max(gridPosition.x, (e.clientX - halfWidth) / zoom)}px`;
|
|
42312
|
+
container.style.top = `${Math.max(gridPosition.y, (e.clientY - halfHeight) / zoom)}px`;
|
|
42313
|
+
const overlappingFigure = getOverlappingFigure(e.clientX - halfWidth, e.clientY - halfHeight);
|
|
42314
|
+
container.style.opacity = overlappingFigure?.id ? "0.6" : "0.9";
|
|
42315
|
+
chartDragStore.setHighlightedFigure(overlappingFigure?.id);
|
|
42316
|
+
};
|
|
42317
|
+
const onMouseUp = (mouseEvent) => {
|
|
42318
|
+
chartDragStore.setHighlightedFigure(void 0);
|
|
42319
|
+
if (container !== null) {
|
|
42320
|
+
spreadsheet.removeChild(container);
|
|
42321
|
+
container = null;
|
|
42322
|
+
}
|
|
42323
|
+
destroyChart?.();
|
|
42324
|
+
document.body.style.cursor = previousCursor;
|
|
42325
|
+
let position = getGridPosition(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42326
|
+
if (Math.abs(mouseEvent.clientX / zoom - startX) <= 5 && Math.abs(mouseEvent.clientY / zoom - startY) <= 5) position = {
|
|
42327
|
+
x: 0,
|
|
42328
|
+
y: 0
|
|
42329
|
+
};
|
|
42330
|
+
else if (!position || position.x + halfWidth > gridPosition.width) return;
|
|
42331
|
+
const { col, row, offset } = env.getStore(ViewportsStore).viewports.getPositionAnchorOffset(sheetId, position);
|
|
42332
|
+
const payload = {
|
|
42333
|
+
chartId: UuidGenerator.smallUuid(),
|
|
42334
|
+
figureId: UuidGenerator.smallUuid(),
|
|
42335
|
+
sheetId,
|
|
42336
|
+
size: {
|
|
42337
|
+
width,
|
|
42338
|
+
height
|
|
42339
|
+
},
|
|
42340
|
+
definition,
|
|
42341
|
+
col,
|
|
42342
|
+
row,
|
|
42343
|
+
offset
|
|
42344
|
+
};
|
|
42345
|
+
const overlappingFigure = getOverlappingFigure(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42346
|
+
if (overlappingFigure?.tag === "carousel") env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
|
|
42347
|
+
sheetId,
|
|
42348
|
+
figureId: overlappingFigure.id,
|
|
42349
|
+
newChartId: UuidGenerator.smallUuid(),
|
|
42350
|
+
chartDefinition: definition
|
|
42351
|
+
});
|
|
42352
|
+
else if (overlappingFigure?.tag === "chart") env.model.dispatch("CREATE_CHART_AND_MERGE_INTO_CAROUSEL", {
|
|
42353
|
+
chartId: payload.chartId,
|
|
42354
|
+
figureId: payload.figureId,
|
|
42355
|
+
sheetId: payload.sheetId,
|
|
42356
|
+
definition: payload.definition,
|
|
42357
|
+
baseFigureId: overlappingFigure.id
|
|
42358
|
+
});
|
|
42359
|
+
else env.model.dispatch("CREATE_CHART", payload);
|
|
42360
|
+
};
|
|
42361
|
+
startDnd(onMouseMove, onMouseUp);
|
|
42362
|
+
}
|
|
42363
|
+
|
|
42364
|
+
//#endregion
|
|
42365
|
+
//#region src/components/side_panel/data_analysis/chart_suggestion_preview.ts
|
|
42366
|
+
const PREVIEW_BUBBLE_RADIUS_RATIO = .3;
|
|
42367
|
+
const MIN_PREVIEW_BUBBLE_RADIUS = 1;
|
|
42368
|
+
var ChartSuggestionPreview = class extends Component {
|
|
42369
|
+
static template = "o-spreadsheet-ChartSuggestionPreview";
|
|
42370
|
+
props = (0, _odoo_owl.useProps)({
|
|
42371
|
+
definition: types.ChartDefinition(),
|
|
42372
|
+
description: types.string(),
|
|
42373
|
+
onPointerDown: types.function()
|
|
42374
|
+
});
|
|
42375
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
42376
|
+
chart;
|
|
42377
|
+
setup() {
|
|
42378
|
+
(0, _odoo_owl.onMounted)(() => registerChartJSExtensions());
|
|
42379
|
+
(0, _odoo_owl.onWillUnmount)(() => this.destroyChart());
|
|
42380
|
+
useLayoutEffect(() => {
|
|
42381
|
+
this.updateChart();
|
|
42382
|
+
}, () => [this.props.definition]);
|
|
42383
|
+
}
|
|
42384
|
+
getChartConfiguration() {
|
|
42385
|
+
const getters = this.env.model.getters;
|
|
42386
|
+
const activeSheetId = getters.getActiveSheetId();
|
|
42387
|
+
const runtime = SpreadsheetChart.fromStrDefinition(getters, activeSheetId, this.props.definition).getRuntime(getters, "myChart");
|
|
42388
|
+
if (!("chartJsConfig" in runtime)) return null;
|
|
42389
|
+
let config = runtime.chartJsConfig;
|
|
42390
|
+
const existingScales = config.options?.scales ?? {};
|
|
42391
|
+
config = {
|
|
42392
|
+
...config,
|
|
42393
|
+
data: this.props.definition.type === "bubble" ? {
|
|
42394
|
+
...config.data,
|
|
42395
|
+
datasets: (config.data?.datasets || []).map((dataset) => ({
|
|
42396
|
+
...dataset,
|
|
42397
|
+
pointRadius: this.scaleBubblePointRadius(dataset.pointRadius),
|
|
42398
|
+
pointHoverRadius: this.scaleBubblePointRadius(dataset.pointHoverRadius)
|
|
42399
|
+
}))
|
|
42400
|
+
} : config.data,
|
|
42401
|
+
options: {
|
|
42402
|
+
...config.options,
|
|
42403
|
+
plugins: {
|
|
42404
|
+
...config.options?.plugins,
|
|
42405
|
+
legend: { display: false },
|
|
42406
|
+
title: { display: false },
|
|
42407
|
+
tooltip: { enabled: false }
|
|
42408
|
+
},
|
|
42409
|
+
events: [],
|
|
42410
|
+
animation: false,
|
|
42411
|
+
scales: this.getScalesCongif(existingScales)
|
|
42412
|
+
}
|
|
42413
|
+
};
|
|
42414
|
+
return limitChartConfigDataPoints(config);
|
|
42415
|
+
}
|
|
42416
|
+
getScalesCongif(scales) {
|
|
42417
|
+
if (scales.x) scales.x = {
|
|
42418
|
+
...scales.x,
|
|
42419
|
+
ticks: { display: false },
|
|
42420
|
+
border: { display: false }
|
|
42421
|
+
};
|
|
42422
|
+
if (scales.y) scales.y = {
|
|
42423
|
+
...scales.y,
|
|
42424
|
+
ticks: { display: false },
|
|
42425
|
+
border: { display: false }
|
|
42426
|
+
};
|
|
42427
|
+
if (scales.r) scales.r = {
|
|
42428
|
+
...scales.r,
|
|
42429
|
+
ticks: { display: false },
|
|
42430
|
+
pointLabels: { display: false }
|
|
42431
|
+
};
|
|
42432
|
+
return scales;
|
|
42433
|
+
}
|
|
42434
|
+
scaleBubblePointRadius(pointRadius) {
|
|
42435
|
+
if (Array.isArray(pointRadius)) return pointRadius.map((radius) => radius <= 0 ? radius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, radius * PREVIEW_BUBBLE_RADIUS_RATIO));
|
|
42436
|
+
if (typeof pointRadius === "number") return pointRadius <= 0 ? pointRadius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, pointRadius * PREVIEW_BUBBLE_RADIUS_RATIO);
|
|
42437
|
+
return pointRadius ?? MIN_PREVIEW_BUBBLE_RADIUS;
|
|
42438
|
+
}
|
|
42439
|
+
updateChart() {
|
|
42440
|
+
this.destroyChart();
|
|
42441
|
+
const config = this.getChartConfiguration();
|
|
42442
|
+
if (!config) {
|
|
42443
|
+
this.drawNativeChart();
|
|
42444
|
+
return;
|
|
42445
|
+
}
|
|
42446
|
+
const ctx = this.chartCanvasRef()?.getContext("2d");
|
|
42447
|
+
if (!ctx || !globalThis.Chart) return;
|
|
42448
|
+
this.chart = new globalThis.Chart(ctx, config);
|
|
42449
|
+
}
|
|
42450
|
+
drawNativeChart() {
|
|
42451
|
+
const canvas = this.chartCanvasRef();
|
|
42452
|
+
if (!canvas) return;
|
|
42453
|
+
const getters = this.env.model.getters;
|
|
42454
|
+
let runtime = SpreadsheetChart.fromStrDefinition(getters, getters.getActiveSheetId(), this.props.definition).getRuntime(getters, "myChart");
|
|
42455
|
+
const { type } = this.props.definition;
|
|
42456
|
+
if (type === "scorecard") {
|
|
42457
|
+
const rect = canvas.getBoundingClientRect();
|
|
42458
|
+
runtime = {
|
|
42459
|
+
...runtime,
|
|
42460
|
+
keyValueStyle: {
|
|
42461
|
+
...runtime.keyValueStyle,
|
|
42462
|
+
fontSize: 16
|
|
42463
|
+
}
|
|
42464
|
+
};
|
|
42465
|
+
drawScoreChart(getScorecardConfiguration({
|
|
42466
|
+
width: rect.width || 130,
|
|
42467
|
+
height: rect.height || 120
|
|
42468
|
+
}, runtime), canvas);
|
|
42469
|
+
} else if (type === "gauge") {
|
|
42470
|
+
let gaugeRuntime = runtime;
|
|
42471
|
+
if (gaugeRuntime.title) gaugeRuntime = {
|
|
42472
|
+
...gaugeRuntime,
|
|
42473
|
+
title: { text: "" }
|
|
42474
|
+
};
|
|
42475
|
+
drawGaugeChart(canvas, gaugeRuntime, 1, void 0, { labelFontSize: 8 });
|
|
42476
|
+
}
|
|
42477
|
+
}
|
|
42478
|
+
destroyChart() {
|
|
42479
|
+
this.chart?.destroy();
|
|
42480
|
+
this.chart = void 0;
|
|
42481
|
+
}
|
|
42482
|
+
};
|
|
42483
|
+
|
|
42484
|
+
//#endregion
|
|
42485
|
+
//#region src/helpers/data_analysis.ts
|
|
42486
|
+
function analyzeColumns(zones, getters) {
|
|
42487
|
+
return getZonesByColumns(zones).map((zone) => analyzeColumn(zone, getters));
|
|
42488
|
+
}
|
|
42489
|
+
function analyzeColumn(zone, getters) {
|
|
42490
|
+
const sheetId = getters.getActiveSheetId();
|
|
42491
|
+
const cells = getters.getEvaluatedCellsInZone(sheetId, zone).filter((c) => c.type !== "empty");
|
|
42492
|
+
if (!cells.length) return {
|
|
42493
|
+
zone,
|
|
42494
|
+
type: "empty",
|
|
42495
|
+
hasHeader: false,
|
|
42496
|
+
rowCount: 0,
|
|
42497
|
+
uniqueCount: 0,
|
|
42498
|
+
uniqueRatio: 0,
|
|
42499
|
+
nonEmpty: []
|
|
42500
|
+
};
|
|
42501
|
+
const firstCell = cells[0];
|
|
42502
|
+
const rest = cells.slice(1);
|
|
42503
|
+
const hasHeader = firstCell.type === "text" && rest.some((c) => c.type !== "text");
|
|
42504
|
+
const dataCells = hasHeader ? rest : cells;
|
|
42505
|
+
const numericValues = dataCells.filter((c) => c.type === "number").map((c) => c.value);
|
|
42506
|
+
const allVals = dataCells.map((c) => String(c.value ?? ""));
|
|
42507
|
+
const uniqueCount = new Set(allVals).size;
|
|
42508
|
+
return {
|
|
42509
|
+
zone,
|
|
42510
|
+
type: computeColumnType(dataCells),
|
|
42511
|
+
header: hasHeader ? firstCell.value : void 0,
|
|
42512
|
+
hasHeader,
|
|
42513
|
+
rowCount: dataCells.length,
|
|
42514
|
+
uniqueCount,
|
|
42515
|
+
uniqueRatio: allVals.length > 0 ? uniqueCount / allVals.length : 0,
|
|
42516
|
+
maxValue: numericValues.length ? numericValues.reduce((max, v) => v > max ? v : max, numericValues[0]) : void 0,
|
|
42517
|
+
nonEmpty: dataCells
|
|
42518
|
+
};
|
|
42519
|
+
}
|
|
42520
|
+
function computeColumnType(cells) {
|
|
42521
|
+
if (cells.length === 0) return "empty";
|
|
42522
|
+
if (cells.every((c) => c.type === "error")) return "error";
|
|
42523
|
+
cells = cells.filter((c) => c.type !== "error");
|
|
42524
|
+
if (cells.every((c) => c.type === "boolean")) return "boolean";
|
|
42525
|
+
if (cells.every((c) => c.type === "number" && !!c.format && isDateTimeFormat(c.format))) return "date";
|
|
42526
|
+
else if (cells.every((c) => c.type === "number")) {
|
|
42527
|
+
if (cells.every((c) => c.format?.includes("%"))) return "percentage";
|
|
42528
|
+
return "number";
|
|
42529
|
+
} else {
|
|
42530
|
+
const textVals = cells.filter((c) => c.type === "text").map((c) => c.value);
|
|
42531
|
+
if (textVals.length > 0) {
|
|
42532
|
+
const unique = new Set(textVals).size;
|
|
42533
|
+
return unique / textVals.length < .75 && unique <= 20 ? "categorical" : "label";
|
|
42534
|
+
}
|
|
42535
|
+
}
|
|
42536
|
+
return "empty";
|
|
42537
|
+
}
|
|
42538
|
+
|
|
42539
|
+
//#endregion
|
|
42540
|
+
//#region src/helpers/figures/charts/charts_suggestions_constants.ts
|
|
42541
|
+
/** Above this many rows, shape-based charts (radar, scatter, pyramid) become unreadable. */
|
|
42542
|
+
const MAX_ROWS_FOR_SHAPE_CHART = 10;
|
|
42543
|
+
function barChart(titleText, source, opts = {}) {
|
|
42544
|
+
return {
|
|
42545
|
+
...opts,
|
|
42546
|
+
type: "bar",
|
|
42547
|
+
title: { text: titleText },
|
|
42548
|
+
dataSource: source,
|
|
42549
|
+
dataSetStyles: {},
|
|
42550
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42551
|
+
stacked: opts.stacked ?? false,
|
|
42552
|
+
humanize: true,
|
|
42553
|
+
aggregated: opts.aggregated ?? false
|
|
42554
|
+
};
|
|
42555
|
+
}
|
|
42556
|
+
function lineChart(titleText, source, opts = {}) {
|
|
42557
|
+
return {
|
|
42558
|
+
...opts,
|
|
42559
|
+
type: "line",
|
|
42560
|
+
title: { text: titleText },
|
|
42561
|
+
dataSource: source,
|
|
42562
|
+
dataSetStyles: {},
|
|
42563
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42564
|
+
stacked: opts.stacked ?? false,
|
|
42565
|
+
cumulative: opts.cumulative ?? false,
|
|
42566
|
+
labelsAsText: false,
|
|
42567
|
+
humanize: true
|
|
42568
|
+
};
|
|
42569
|
+
}
|
|
42570
|
+
function pieChart(titleText, source, opts = {}) {
|
|
42571
|
+
return {
|
|
42572
|
+
...opts,
|
|
42573
|
+
type: "pie",
|
|
42574
|
+
title: { text: titleText },
|
|
42575
|
+
dataSource: source,
|
|
42576
|
+
dataSetStyles: {},
|
|
42577
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42578
|
+
humanize: true
|
|
42579
|
+
};
|
|
42580
|
+
}
|
|
42581
|
+
function radarChart(titleText, source, opts = {}) {
|
|
42582
|
+
return {
|
|
42583
|
+
...opts,
|
|
42584
|
+
type: "radar",
|
|
42585
|
+
title: { text: titleText },
|
|
42586
|
+
dataSource: source,
|
|
42587
|
+
dataSetStyles: {},
|
|
42588
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42589
|
+
stacked: false,
|
|
42590
|
+
humanize: true
|
|
42591
|
+
};
|
|
42592
|
+
}
|
|
42593
|
+
function sunburstChart(titleText, source) {
|
|
42594
|
+
return {
|
|
42595
|
+
type: "sunburst",
|
|
42596
|
+
title: { text: titleText },
|
|
42597
|
+
dataSource: source,
|
|
42598
|
+
dataSetStyles: {},
|
|
42599
|
+
legendPosition: "none"
|
|
42600
|
+
};
|
|
42601
|
+
}
|
|
42602
|
+
function treemapChart(titleText, source) {
|
|
42603
|
+
return {
|
|
42604
|
+
type: "treemap",
|
|
42605
|
+
title: { text: titleText },
|
|
42606
|
+
dataSource: source,
|
|
42607
|
+
dataSetStyles: {},
|
|
42608
|
+
legendPosition: "none"
|
|
42609
|
+
};
|
|
42610
|
+
}
|
|
42611
|
+
function calendarChart(titleText, source) {
|
|
42612
|
+
return {
|
|
42613
|
+
type: "calendar",
|
|
42614
|
+
title: { text: titleText },
|
|
42615
|
+
dataSource: source,
|
|
42616
|
+
dataSetStyles: {},
|
|
42617
|
+
legendPosition: "none",
|
|
42618
|
+
horizontalGroupBy: "month_number",
|
|
42619
|
+
verticalGroupBy: "day_of_week",
|
|
42620
|
+
humanize: true
|
|
42621
|
+
};
|
|
42622
|
+
}
|
|
42623
|
+
function scatterChart(titleText, source) {
|
|
42624
|
+
return {
|
|
42625
|
+
type: "scatter",
|
|
42626
|
+
title: { text: titleText },
|
|
42627
|
+
dataSource: source,
|
|
42628
|
+
dataSetStyles: {},
|
|
42629
|
+
legendPosition: "none",
|
|
42630
|
+
labelsAsText: false,
|
|
42631
|
+
humanize: true
|
|
42632
|
+
};
|
|
42633
|
+
}
|
|
42634
|
+
function pyramidChart(titleText, source, opts = {}) {
|
|
42635
|
+
return {
|
|
42636
|
+
type: "pyramid",
|
|
42637
|
+
title: { text: titleText },
|
|
42638
|
+
dataSource: source,
|
|
42639
|
+
dataSetStyles: {},
|
|
42640
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42641
|
+
stacked: false,
|
|
42642
|
+
horizontal: true,
|
|
42643
|
+
humanize: true
|
|
42644
|
+
};
|
|
42645
|
+
}
|
|
42646
|
+
function comboChart(titleText, source, opts = {}) {
|
|
42647
|
+
return {
|
|
42648
|
+
...opts,
|
|
42649
|
+
type: "combo",
|
|
42650
|
+
title: { text: titleText },
|
|
42651
|
+
dataSource: source,
|
|
42652
|
+
dataSetStyles: {},
|
|
42653
|
+
legendPosition: "top",
|
|
42654
|
+
humanize: true
|
|
42655
|
+
};
|
|
42656
|
+
}
|
|
42657
|
+
function bubbleChart(titleText, xRange, yRanges, sizeRange, labelRange, dataSetsHaveTitle) {
|
|
42658
|
+
return {
|
|
42659
|
+
type: "bubble",
|
|
42660
|
+
title: { text: titleText },
|
|
42661
|
+
humanize: true,
|
|
42662
|
+
dataSetsHaveTitle,
|
|
42663
|
+
yRanges,
|
|
42664
|
+
xRange,
|
|
42665
|
+
sizeRange,
|
|
42666
|
+
labelRange,
|
|
42667
|
+
labelsAsText: false,
|
|
42668
|
+
legendPosition: "none",
|
|
42669
|
+
bubbleColor: { color: "multiple" },
|
|
42670
|
+
verticalAxisPosition: "left"
|
|
42671
|
+
};
|
|
42672
|
+
}
|
|
42673
|
+
function scorecardChart(titleText, keyValue, opts = {}) {
|
|
42674
|
+
return {
|
|
42675
|
+
...opts,
|
|
42676
|
+
type: "scorecard",
|
|
42677
|
+
title: { text: titleText },
|
|
42678
|
+
keyValue,
|
|
42679
|
+
baselineMode: opts.baselineMode ?? "difference",
|
|
42680
|
+
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
42681
|
+
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
42682
|
+
humanize: opts.humanize ?? true
|
|
42683
|
+
};
|
|
42684
|
+
}
|
|
42685
|
+
function gaugeChart(titleText, dataRange, rangeMin, rangeMax) {
|
|
42686
|
+
return {
|
|
42687
|
+
type: "gauge",
|
|
42688
|
+
title: { text: titleText },
|
|
42689
|
+
dataRange,
|
|
42690
|
+
sectionRule: {
|
|
42691
|
+
colors: {
|
|
42692
|
+
lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
|
|
42693
|
+
middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
|
|
42694
|
+
upperColor: DEFAULT_GAUGE_UPPER_COLOR
|
|
42695
|
+
},
|
|
42696
|
+
rangeMin,
|
|
42697
|
+
rangeMax,
|
|
42698
|
+
lowerInflectionPoint: {
|
|
42699
|
+
type: "percentage",
|
|
42700
|
+
value: "33",
|
|
42701
|
+
operator: "<="
|
|
42702
|
+
},
|
|
42703
|
+
upperInflectionPoint: {
|
|
42704
|
+
type: "percentage",
|
|
42705
|
+
value: "66",
|
|
42706
|
+
operator: "<="
|
|
42707
|
+
}
|
|
42708
|
+
},
|
|
42709
|
+
humanize: true
|
|
42710
|
+
};
|
|
42711
|
+
}
|
|
42712
|
+
/** Pattern A — Single numeric column */
|
|
42713
|
+
const SINGLE_NUMBER_COLUMN_SUGGESTIONS = [
|
|
42714
|
+
{
|
|
42715
|
+
description: _t("Highlights the most recent value compared to the previous one."),
|
|
42716
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42717
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42718
|
+
baseline: ctx.prevCellXC,
|
|
42719
|
+
baselineMode: "difference"
|
|
42720
|
+
})
|
|
42721
|
+
},
|
|
42722
|
+
{
|
|
42723
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
42724
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42725
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42726
|
+
},
|
|
42727
|
+
{
|
|
42728
|
+
description: _t("Compares individual values side-by-side."),
|
|
42729
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42730
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42731
|
+
},
|
|
42732
|
+
{
|
|
42733
|
+
description: _t("Shows the evolution of all values over the range."),
|
|
42734
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42735
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42736
|
+
},
|
|
42737
|
+
{
|
|
42738
|
+
description: _t("Emphasizes total accumulation over the range."),
|
|
42739
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42740
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42741
|
+
fillArea: true,
|
|
42742
|
+
cumulative: true
|
|
42743
|
+
})
|
|
42744
|
+
}
|
|
42745
|
+
];
|
|
42746
|
+
/** Pattern B — Single percentage column */
|
|
42747
|
+
const SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS = [
|
|
42748
|
+
{
|
|
42749
|
+
description: _t("Shows the last percentage value with its baseline."),
|
|
42750
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42751
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42752
|
+
baseline: ctx.prevCellXC,
|
|
42753
|
+
baselineMode: "percentage"
|
|
42754
|
+
})
|
|
42755
|
+
},
|
|
42756
|
+
{
|
|
42757
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42758
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42759
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, "0", ctx.isAboveOne ? "100" : "1")
|
|
42760
|
+
},
|
|
42761
|
+
{
|
|
42762
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42763
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42764
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42765
|
+
},
|
|
42766
|
+
{
|
|
42767
|
+
description: _t("Shows completion against total."),
|
|
42768
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42769
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { isDoughnut: true })
|
|
42770
|
+
},
|
|
42771
|
+
{
|
|
42772
|
+
description: _t("Compares all percentage values side-by-side."),
|
|
42773
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42774
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42775
|
+
}
|
|
42776
|
+
];
|
|
42777
|
+
/** Pattern C — Single date column */
|
|
42778
|
+
const SINGLE_DATE_COLUMN_SUGGESTIONS = [{
|
|
42779
|
+
description: _t("Shows the last date value."),
|
|
42780
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42781
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC)
|
|
42782
|
+
}];
|
|
42783
|
+
/** Pattern D — Single categorical column */
|
|
42784
|
+
const SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS = [
|
|
42785
|
+
{
|
|
42786
|
+
description: _t("Shows the share of each category."),
|
|
42787
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42788
|
+
aggregated: true,
|
|
42789
|
+
legendPosition: "top"
|
|
42790
|
+
})
|
|
42791
|
+
},
|
|
42792
|
+
{
|
|
42793
|
+
description: _t("Same as pie, cleaner proportional look."),
|
|
42794
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42795
|
+
aggregated: true,
|
|
42796
|
+
isDoughnut: true,
|
|
42797
|
+
legendPosition: "top"
|
|
42798
|
+
})
|
|
42799
|
+
},
|
|
42800
|
+
{
|
|
42801
|
+
description: _t("Absolute count per category."),
|
|
42802
|
+
build: (ctx) => barChart(ctx.title, {
|
|
42803
|
+
...ctx.source,
|
|
42804
|
+
labelRange: ctx.range
|
|
42805
|
+
}, {
|
|
42806
|
+
legendPosition: "none",
|
|
42807
|
+
aggregated: true
|
|
42808
|
+
})
|
|
42809
|
+
}
|
|
42810
|
+
];
|
|
42811
|
+
/** Pattern E — Single label column */
|
|
42812
|
+
const SINGLE_LABEL_COLUMN_SUGGESTIONS = [{
|
|
42813
|
+
description: _t("Displays a key performance indicator."),
|
|
42814
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42815
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42816
|
+
baselineMode: "text",
|
|
42817
|
+
humanize: false
|
|
42818
|
+
})
|
|
42819
|
+
}];
|
|
42820
|
+
/** Pattern F — Categorical + Number */
|
|
42821
|
+
const CATEGORICAL_VS_NUMBER_SUGGESTIONS = [
|
|
42822
|
+
{
|
|
42823
|
+
description: _t("Classic category-vs-value comparison."),
|
|
42824
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { aggregated: true })
|
|
42825
|
+
},
|
|
42826
|
+
{
|
|
42827
|
+
description: _t("Better when category labels are long."),
|
|
42828
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42829
|
+
horizontal: true,
|
|
42830
|
+
aggregated: true
|
|
42831
|
+
})
|
|
42832
|
+
},
|
|
42833
|
+
{
|
|
42834
|
+
description: _t("Share of total per category."),
|
|
42835
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42836
|
+
legendPosition: "top",
|
|
42837
|
+
aggregated: true
|
|
42838
|
+
})
|
|
42839
|
+
},
|
|
42840
|
+
{
|
|
42841
|
+
description: _t("Proportional area — good for many categories."),
|
|
42842
|
+
build: (ctx) => treemapChart(ctx.title, ctx.treemapSource)
|
|
42843
|
+
}
|
|
42844
|
+
];
|
|
42845
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
42846
|
+
const DATE_VS_SERIES_SUGGESTIONS = [
|
|
42847
|
+
{
|
|
42848
|
+
description: _t("Best for visualizing time-series trends."),
|
|
42849
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42850
|
+
},
|
|
42851
|
+
{
|
|
42852
|
+
description: _t("Emphasizes total volume over time."),
|
|
42853
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42854
|
+
fillArea: true,
|
|
42855
|
+
cumulative: true
|
|
42856
|
+
})
|
|
42857
|
+
},
|
|
42858
|
+
{
|
|
42859
|
+
description: _t("Period-by-period comparison."),
|
|
42860
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42861
|
+
},
|
|
42862
|
+
{
|
|
42863
|
+
description: _t("Shows intensity variation across days of the year."),
|
|
42864
|
+
isApplicable: ({ isPercentage }) => !isPercentage,
|
|
42865
|
+
build: (ctx) => calendarChart(ctx.title, ctx.source)
|
|
42866
|
+
}
|
|
42867
|
+
];
|
|
42868
|
+
/** Pattern H — Number + Number */
|
|
42869
|
+
const NUMBER_VS_NUMBER_SUGGESTIONS = [
|
|
42870
|
+
{
|
|
42871
|
+
description: _t("Highlights the second metric compared to the first one."),
|
|
42872
|
+
isApplicable: ({ rowCount1, rowCount2 }) => rowCount1 === 1 && rowCount2 === 1,
|
|
42873
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42874
|
+
baseline: ctx.prevCellXC,
|
|
42875
|
+
baselineMode: "difference"
|
|
42876
|
+
})
|
|
42877
|
+
},
|
|
42878
|
+
{
|
|
42879
|
+
description: _t("Side-by-side comparison of two numeric series."),
|
|
42880
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42881
|
+
},
|
|
42882
|
+
{
|
|
42883
|
+
description: _t("Reveals correlation between two numeric variables."),
|
|
42884
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42885
|
+
build: (ctx) => scatterChart(ctx.title, ctx.source2)
|
|
42886
|
+
},
|
|
42887
|
+
{
|
|
42888
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
42889
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42890
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth)
|
|
42891
|
+
},
|
|
42892
|
+
{
|
|
42893
|
+
description: _t("When both metrics contribute to a total."),
|
|
42894
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42895
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceBoth, {
|
|
42896
|
+
stacked: true,
|
|
42897
|
+
fillArea: true,
|
|
42898
|
+
legendPosition: "top"
|
|
42899
|
+
})
|
|
42900
|
+
},
|
|
42901
|
+
{
|
|
42902
|
+
description: _t("Shape-based comparison when rows represent named entities."),
|
|
42903
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2 && rowCount1 <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42904
|
+
build: (ctx) => radarChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42905
|
+
}
|
|
42906
|
+
];
|
|
42907
|
+
/** Pattern I — Categorical + Percentage */
|
|
42908
|
+
const CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS = [
|
|
42909
|
+
{
|
|
42910
|
+
description: _t("Vertical comparison of rates per category."),
|
|
42911
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42912
|
+
},
|
|
42913
|
+
{
|
|
42914
|
+
description: _t("Progress-bar style per category."),
|
|
42915
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42916
|
+
},
|
|
42917
|
+
{
|
|
42918
|
+
description: _t("Share of total percentage across categories."),
|
|
42919
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42920
|
+
},
|
|
42921
|
+
{
|
|
42922
|
+
description: _t("Comparison of completion rates across categories."),
|
|
42923
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42924
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42925
|
+
}
|
|
42926
|
+
];
|
|
42927
|
+
/** Pattern K — Label + Number */
|
|
42928
|
+
const LABEL_VS_NUMBER_SUGGESTIONS = [
|
|
42929
|
+
{
|
|
42930
|
+
description: _t("Highlights the most recent value for the named entity."),
|
|
42931
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42932
|
+
build: (ctx) => scorecardChart("", ctx.lastCellXC, {
|
|
42933
|
+
humanize: false,
|
|
42934
|
+
baselineMode: "text",
|
|
42935
|
+
baseline: ctx.prevCellXC
|
|
42936
|
+
})
|
|
42937
|
+
},
|
|
42938
|
+
{
|
|
42939
|
+
description: _t("Vertical comparison across named items."),
|
|
42940
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42941
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42942
|
+
},
|
|
42943
|
+
{
|
|
42944
|
+
description: _t("Works well for named entities with long labels."),
|
|
42945
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42946
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42947
|
+
},
|
|
42948
|
+
{
|
|
42949
|
+
description: _t("Share of total per category."),
|
|
42950
|
+
isApplicable: ({ rowCount }) => rowCount > 1 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42951
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42952
|
+
legendPosition: "top",
|
|
42953
|
+
aggregated: true
|
|
42954
|
+
})
|
|
42955
|
+
},
|
|
42956
|
+
{
|
|
42957
|
+
description: _t("Shape-based comparison across labeled items."),
|
|
42958
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42959
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42960
|
+
}
|
|
42961
|
+
];
|
|
42962
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
42963
|
+
const CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42964
|
+
{
|
|
42965
|
+
description: _t("Side-by-side comparison across categories for each series."),
|
|
42966
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42967
|
+
},
|
|
42968
|
+
{
|
|
42969
|
+
description: _t("Shows composition and total per category."),
|
|
42970
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42971
|
+
stacked: true,
|
|
42972
|
+
legendPosition: "top"
|
|
42973
|
+
})
|
|
42974
|
+
},
|
|
42975
|
+
{
|
|
42976
|
+
description: _t("Trend per series across categories."),
|
|
42977
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42978
|
+
},
|
|
42979
|
+
{
|
|
42980
|
+
description: _t("Volume and composition across categories."),
|
|
42981
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42982
|
+
stacked: true,
|
|
42983
|
+
fillArea: true,
|
|
42984
|
+
legendPosition: "top"
|
|
42985
|
+
})
|
|
42986
|
+
},
|
|
42987
|
+
{
|
|
42988
|
+
description: _t("Shape comparison across metrics (best for ≤ 10 rows)."),
|
|
42989
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42990
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42991
|
+
}
|
|
42992
|
+
];
|
|
42993
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
42994
|
+
const DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42995
|
+
{
|
|
42996
|
+
description: _t("Trend comparison across multiple metrics over time."),
|
|
42997
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42998
|
+
},
|
|
42999
|
+
{
|
|
43000
|
+
description: _t("Volume composition over time."),
|
|
43001
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
43002
|
+
stacked: true,
|
|
43003
|
+
fillArea: true,
|
|
43004
|
+
legendPosition: "top"
|
|
43005
|
+
})
|
|
43006
|
+
},
|
|
43007
|
+
{
|
|
43008
|
+
description: _t("Bar for the primary metric, line for the others."),
|
|
43009
|
+
build: (ctx) => comboChart(ctx.title, ctx.source)
|
|
43010
|
+
},
|
|
43011
|
+
{
|
|
43012
|
+
description: _t("Period-by-period grouped comparison."),
|
|
43013
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43014
|
+
}
|
|
43015
|
+
];
|
|
43016
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43017
|
+
const MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS = [
|
|
43018
|
+
{
|
|
43019
|
+
description: _t("Two-level hierarchy weighted by value."),
|
|
43020
|
+
build: (ctx) => sunburstChart(ctx.title, ctx.hierarchySource)
|
|
43021
|
+
},
|
|
43022
|
+
{
|
|
43023
|
+
description: _t("Proportional nested area weighted by value."),
|
|
43024
|
+
build: (ctx) => treemapChart(ctx.title, ctx.hierarchySource)
|
|
43025
|
+
},
|
|
43026
|
+
{
|
|
43027
|
+
description: _t("One series per inner category, grouped by outer category."),
|
|
43028
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, { legendPosition: "top" })
|
|
43029
|
+
},
|
|
43030
|
+
{
|
|
43031
|
+
description: _t("Contribution of inner categories per outer category."),
|
|
43032
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, {
|
|
43033
|
+
stacked: true,
|
|
43034
|
+
legendPosition: "top"
|
|
43035
|
+
})
|
|
43036
|
+
}
|
|
43037
|
+
];
|
|
43038
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43039
|
+
const CATEGORICAL_DATE_NUMBER_SUGGESTIONS = [
|
|
43040
|
+
{
|
|
43041
|
+
description: _t("Trend of values over time."),
|
|
43042
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, { legendPosition: "top" })
|
|
43043
|
+
},
|
|
43044
|
+
{
|
|
43045
|
+
description: _t("Volume contribution over time."),
|
|
43046
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, {
|
|
43047
|
+
stacked: true,
|
|
43048
|
+
fillArea: true,
|
|
43049
|
+
legendPosition: "top"
|
|
43050
|
+
})
|
|
43051
|
+
},
|
|
43052
|
+
{
|
|
43053
|
+
description: _t("Period × category side-by-side comparison."),
|
|
43054
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByCat, { legendPosition: "top" })
|
|
43055
|
+
},
|
|
43056
|
+
{
|
|
43057
|
+
description: _t("Composition per period over time."),
|
|
43058
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByDate, {
|
|
43059
|
+
stacked: true,
|
|
43060
|
+
legendPosition: "top"
|
|
43061
|
+
})
|
|
43062
|
+
}
|
|
43063
|
+
];
|
|
43064
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43065
|
+
const LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
43066
|
+
{
|
|
43067
|
+
description: _t("Side-by-side per entity — works well for long labels."),
|
|
43068
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43069
|
+
horizontal: true,
|
|
43070
|
+
legendPosition: "top"
|
|
43071
|
+
})
|
|
43072
|
+
},
|
|
43073
|
+
{
|
|
43074
|
+
description: _t("Grouped bars per entity for direct metric comparison."),
|
|
43075
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43076
|
+
},
|
|
43077
|
+
{
|
|
43078
|
+
description: _t("Shape/profile comparison across metrics for each entity."),
|
|
43079
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43080
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43081
|
+
},
|
|
43082
|
+
{
|
|
43083
|
+
description: _t("Correlation between the two numeric metrics across entities."),
|
|
43084
|
+
isApplicable: ({ rowCount, numColsCount }) => rowCount > 2 && numColsCount === 2,
|
|
43085
|
+
build: (ctx) => scatterChart(ctx.title, ctx.scatterSource)
|
|
43086
|
+
},
|
|
43087
|
+
{
|
|
43088
|
+
description: _t("Three metrics in one view: X position, Y position and bubble size."),
|
|
43089
|
+
isApplicable: ({ numColsCount }) => numColsCount === 3,
|
|
43090
|
+
build: (ctx) => {
|
|
43091
|
+
const bubble = ctx.bubble;
|
|
43092
|
+
return bubbleChart(ctx.title, bubble.xRange, bubble.yRanges, bubble.sizeRange, bubble.labelRange, bubble.hasTitle);
|
|
43093
|
+
}
|
|
43094
|
+
}
|
|
43095
|
+
];
|
|
43096
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43097
|
+
const CATEGORICAL_TWO_NUMBERS_SUGGESTIONS = [
|
|
43098
|
+
{
|
|
43099
|
+
description: _t("Natural fit for symmetric or opposing values per category."),
|
|
43100
|
+
isApplicable: ({ isPyramid }) => isPyramid,
|
|
43101
|
+
build: (ctx) => pyramidChart(ctx.title, ctx.sourceBoth)
|
|
43102
|
+
},
|
|
43103
|
+
{
|
|
43104
|
+
description: _t("Side-by-side comparison of both metrics per category."),
|
|
43105
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43106
|
+
legendPosition: "top",
|
|
43107
|
+
aggregated: true
|
|
43108
|
+
})
|
|
43109
|
+
},
|
|
43110
|
+
{
|
|
43111
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
43112
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth, { aggregated: true })
|
|
43113
|
+
},
|
|
43114
|
+
{
|
|
43115
|
+
description: _t("Shows total and composition per category."),
|
|
43116
|
+
isApplicable: ({ isPyramid }) => !isPyramid,
|
|
43117
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43118
|
+
stacked: true,
|
|
43119
|
+
legendPosition: "top",
|
|
43120
|
+
aggregated: true
|
|
43121
|
+
})
|
|
43122
|
+
}
|
|
43123
|
+
];
|
|
43124
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43125
|
+
const MANY_NUMBERS_SUGGESTIONS = [
|
|
43126
|
+
{
|
|
43127
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
43128
|
+
isApplicable: ({ colsLength, rowCount }) => colsLength === 3 && rowCount === 1,
|
|
43129
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.secondCellXC}`)
|
|
43130
|
+
},
|
|
43131
|
+
{
|
|
43132
|
+
description: _t("Trend comparison across all metrics."),
|
|
43133
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
43134
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43135
|
+
},
|
|
43136
|
+
{
|
|
43137
|
+
description: _t("Overall shape/profile across all metrics."),
|
|
43138
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43139
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43140
|
+
},
|
|
43141
|
+
{
|
|
43142
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43143
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43144
|
+
},
|
|
43145
|
+
{
|
|
43146
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43147
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43148
|
+
legendPosition: "top",
|
|
43149
|
+
stacked: true
|
|
43150
|
+
})
|
|
43151
|
+
}
|
|
43152
|
+
];
|
|
43153
|
+
|
|
43154
|
+
//#endregion
|
|
43155
|
+
//#region src/helpers/figures/charts/chart_suggestion_engine.ts
|
|
43156
|
+
const PYRAMID_HEADER_KEYWORD_PATTERNS = [
|
|
43157
|
+
"male",
|
|
43158
|
+
"female",
|
|
43159
|
+
"man",
|
|
43160
|
+
"woman",
|
|
43161
|
+
"boy",
|
|
43162
|
+
"girl",
|
|
43163
|
+
"before",
|
|
43164
|
+
"after",
|
|
43165
|
+
"previous",
|
|
43166
|
+
"current",
|
|
43167
|
+
"positive",
|
|
43168
|
+
"negative",
|
|
43169
|
+
"gain",
|
|
43170
|
+
"loss",
|
|
43171
|
+
"income",
|
|
43172
|
+
"expense",
|
|
43173
|
+
"revenue",
|
|
43174
|
+
"cost"
|
|
43175
|
+
].map((keyword) => new RegExp(`\\b${keyword}\\b`, "i"));
|
|
43176
|
+
function matchesColumnType(spec, type) {
|
|
43177
|
+
return Array.isArray(spec) ? spec.includes(type) : spec === type;
|
|
43178
|
+
}
|
|
43179
|
+
function matchesShape(pattern, shape) {
|
|
43180
|
+
const { leadingTypes, extension } = pattern;
|
|
43181
|
+
const expectedLength = leadingTypes.length + (extension?.minSize ?? 0);
|
|
43182
|
+
if (extension ? shape.length < expectedLength : shape.length !== expectedLength) return false;
|
|
43183
|
+
return shape.every((type, i) => {
|
|
43184
|
+
const spec = i < leadingTypes.length ? leadingTypes[i] : extension?.type;
|
|
43185
|
+
return spec !== void 0 && matchesColumnType(spec, type);
|
|
43186
|
+
});
|
|
43187
|
+
}
|
|
43188
|
+
function isPyramidLike(numCol1, numCol2) {
|
|
43189
|
+
const h1 = numCol1.header ?? "";
|
|
43190
|
+
const h2 = numCol2.header ?? "";
|
|
43191
|
+
return PYRAMID_HEADER_KEYWORD_PATTERNS.some((re) => re.test(h1) || re.test(h2));
|
|
43192
|
+
}
|
|
43193
|
+
/** Returns the first and last cell XC coordinates, and the second-to-last cell XC coordinate if it exists. */
|
|
43194
|
+
function interestingCellsXc(col) {
|
|
43195
|
+
if (!col) return {
|
|
43196
|
+
firstCellXC: "",
|
|
43197
|
+
lastCellXC: "",
|
|
43198
|
+
prevCellXC: void 0
|
|
43199
|
+
};
|
|
43200
|
+
const firstCellPosition = col.nonEmpty.at(0)?.position;
|
|
43201
|
+
const firstCellXC = firstCellPosition ? toXC(firstCellPosition?.col, firstCellPosition?.row) : "";
|
|
43202
|
+
const lastCellPosition = col.nonEmpty.at(-1)?.position;
|
|
43203
|
+
const lastCellXC = lastCellPosition ? toXC(lastCellPosition?.col, lastCellPosition?.row) : "";
|
|
43204
|
+
const prevCellPosition = col.nonEmpty.at(-2)?.position;
|
|
43205
|
+
return {
|
|
43206
|
+
firstCellXC,
|
|
43207
|
+
lastCellXC,
|
|
43208
|
+
prevCellXC: prevCellPosition ? toXC(prevCellPosition?.col, prevCellPosition?.row) : void 0
|
|
43209
|
+
};
|
|
43210
|
+
}
|
|
43211
|
+
/** Pattern A — Single numeric column */
|
|
43212
|
+
function buildSingleNumberContext([col], getters) {
|
|
43213
|
+
const title = col.header ?? _t("Value");
|
|
43214
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43215
|
+
return {
|
|
43216
|
+
title,
|
|
43217
|
+
source: rangeSource([dataset(col.zone, getters)], col.hasHeader),
|
|
43218
|
+
rowCount: col.rowCount,
|
|
43219
|
+
firstCellXC,
|
|
43220
|
+
lastCellXC,
|
|
43221
|
+
prevCellXC
|
|
43222
|
+
};
|
|
43223
|
+
}
|
|
43224
|
+
/** Pattern B — Single percentage column */
|
|
43225
|
+
function buildSinglePercentageContext([col], getters) {
|
|
43226
|
+
const hasTitle = col.hasHeader;
|
|
43227
|
+
const title = col.header ?? _t("Rate");
|
|
43228
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43229
|
+
const source = rangeSource([dataset(col.zone, getters)], hasTitle);
|
|
43230
|
+
const isAboveOne = (col.maxValue ?? 0) > 1;
|
|
43231
|
+
return {
|
|
43232
|
+
title,
|
|
43233
|
+
source,
|
|
43234
|
+
rowCount: col.rowCount,
|
|
43235
|
+
firstCellXC,
|
|
43236
|
+
lastCellXC,
|
|
43237
|
+
prevCellXC,
|
|
43238
|
+
isAboveOne
|
|
43239
|
+
};
|
|
43240
|
+
}
|
|
43241
|
+
/** Pattern C — Single date column */
|
|
43242
|
+
function buildSingleDateContext([col]) {
|
|
43243
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43244
|
+
return {
|
|
43245
|
+
title: col.header ?? _t("Date"),
|
|
43246
|
+
lastCellXC,
|
|
43247
|
+
rowCount: col.rowCount
|
|
43248
|
+
};
|
|
43249
|
+
}
|
|
43250
|
+
/** Pattern D — Single categorical column */
|
|
43251
|
+
function buildSingleCategoricalContext([col], getters) {
|
|
43252
|
+
const hasTitle = col.hasHeader;
|
|
43253
|
+
const title = col.header ?? _t("Category");
|
|
43254
|
+
const range = getUnboundRange(getters, col.zone);
|
|
43255
|
+
return {
|
|
43256
|
+
title,
|
|
43257
|
+
source: rangeSource([dataset(col.zone, getters)], hasTitle, range),
|
|
43258
|
+
range
|
|
43259
|
+
};
|
|
43260
|
+
}
|
|
43261
|
+
/** Pattern E — Single label column */
|
|
43262
|
+
function buildSingleLabelContext([col]) {
|
|
43263
|
+
const title = col.header ?? _t("Label");
|
|
43264
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43265
|
+
return {
|
|
43266
|
+
title,
|
|
43267
|
+
lastCellXC,
|
|
43268
|
+
rowCount: col.rowCount
|
|
43269
|
+
};
|
|
43270
|
+
}
|
|
43271
|
+
/** Pattern F — Categorical + Number */
|
|
43272
|
+
function buildCategoricalVsNumberContext([catCol, numCol], getters) {
|
|
43273
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43274
|
+
const hasTitle = numCol.hasHeader;
|
|
43275
|
+
return {
|
|
43276
|
+
title: numCol.header ? _t("%(numberHeader)s by %(categoryHeader)s", {
|
|
43277
|
+
numberHeader: numCol.header,
|
|
43278
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43279
|
+
}) : _t("By Category"),
|
|
43280
|
+
source: rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange),
|
|
43281
|
+
treemapSource: rangeSource([dataset(catCol.zone, getters)], hasTitle, getUnboundRange(getters, numCol.zone))
|
|
43282
|
+
};
|
|
43283
|
+
}
|
|
43284
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
43285
|
+
function buildDateVsSeriesContext([dateCol, seriesCol], getters) {
|
|
43286
|
+
const isPercentage = seriesCol.type === "percentage";
|
|
43287
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43288
|
+
const hasTitle = isDatasetTitled(getters, dateCol.zone);
|
|
43289
|
+
return {
|
|
43290
|
+
title: seriesCol.header ? _t("%(seriesHeader)s over time", { seriesHeader: seriesCol.header }) : isPercentage ? _t("Rate over Time") : _t("Over Time"),
|
|
43291
|
+
source: rangeSource([dataset(seriesCol.zone, getters)], hasTitle, labelRange),
|
|
43292
|
+
isPercentage
|
|
43293
|
+
};
|
|
43294
|
+
}
|
|
43295
|
+
/** Pattern H — Number + Number */
|
|
43296
|
+
function buildNumberVsNumberContext([col1, col2], getters) {
|
|
43297
|
+
const title = col1.header && col2.header ? _t("%(col2Header)s vs %(col1Header)s", {
|
|
43298
|
+
col2Header: col2.header,
|
|
43299
|
+
col1Header: col1.header
|
|
43300
|
+
}) : _t("Correlation");
|
|
43301
|
+
const hasTitle = col1.hasHeader || col2.hasHeader;
|
|
43302
|
+
const source2 = rangeSource([dataset(col2.zone, getters)], col2.hasHeader, getUnboundRange(getters, col1.zone));
|
|
43303
|
+
const sourceBoth = rangeSource([dataset(col1.zone, getters, "0"), dataset(col2.zone, getters, "1")], hasTitle);
|
|
43304
|
+
const { lastCellXC } = interestingCellsXc(col2);
|
|
43305
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(col1);
|
|
43306
|
+
return {
|
|
43307
|
+
title,
|
|
43308
|
+
source2,
|
|
43309
|
+
sourceBoth,
|
|
43310
|
+
rowCount1: col1.rowCount,
|
|
43311
|
+
rowCount2: col2.rowCount,
|
|
43312
|
+
lastCellXC,
|
|
43313
|
+
prevCellXC
|
|
43314
|
+
};
|
|
43315
|
+
}
|
|
43316
|
+
/** Pattern I — Categorical + Percentage */
|
|
43317
|
+
function buildCategoricalVsPercentageContext([catCol, pctCol], getters) {
|
|
43318
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43319
|
+
const hasTitle = pctCol.hasHeader;
|
|
43320
|
+
return {
|
|
43321
|
+
title: pctCol.header ? _t("%(percentageHeader)s by %(categoryHeader)s", {
|
|
43322
|
+
percentageHeader: pctCol.header,
|
|
43323
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43324
|
+
}) : _t("Rates by Category"),
|
|
43325
|
+
source: rangeSource([dataset(pctCol.zone, getters)], hasTitle, labelRange),
|
|
43326
|
+
rowCount: catCol.rowCount
|
|
43327
|
+
};
|
|
43328
|
+
}
|
|
43329
|
+
/** Pattern K — Label + Number */
|
|
43330
|
+
function buildLabelVsNumberContext([labelCol, numCol], getters) {
|
|
43331
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43332
|
+
const hasTitle = numCol.hasHeader;
|
|
43333
|
+
const title = numCol.header ? _t("%(numberHeader)s by %(labelHeader)s", {
|
|
43334
|
+
numberHeader: numCol.header,
|
|
43335
|
+
labelHeader: labelCol.header ?? _t("Name")
|
|
43336
|
+
}) : _t("By Name");
|
|
43337
|
+
const source = rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange);
|
|
43338
|
+
const { lastCellXC } = interestingCellsXc(numCol);
|
|
43339
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(labelCol);
|
|
43340
|
+
return {
|
|
43341
|
+
title,
|
|
43342
|
+
source,
|
|
43343
|
+
rowCount: labelCol.rowCount,
|
|
43344
|
+
lastCellXC,
|
|
43345
|
+
prevCellXC
|
|
43346
|
+
};
|
|
43347
|
+
}
|
|
43348
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
43349
|
+
function buildCategoricalVsMultipleNumbersContext([catCol, ...numCols], getters) {
|
|
43350
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43351
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43352
|
+
return {
|
|
43353
|
+
title: catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Multi-series"),
|
|
43354
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange),
|
|
43355
|
+
rowCount: catCol.rowCount
|
|
43356
|
+
};
|
|
43357
|
+
}
|
|
43358
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
43359
|
+
function buildDateVsMultipleNumbersContext([dateCol, ...numCols], getters) {
|
|
43360
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43361
|
+
const hasTitle = numCols.some((c) => c.hasHeader) || isDatasetTitled(getters, dateCol.zone);
|
|
43362
|
+
return {
|
|
43363
|
+
title: numCols.length === 1 && numCols[0].header ? _t("%(header)s over time", { header: numCols[0].header }) : _t("Multi-series over Time"),
|
|
43364
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange)
|
|
43365
|
+
};
|
|
43366
|
+
}
|
|
43367
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43368
|
+
function buildMultipleCategoricalsVsNumberContext([cat1, cat2, numCol], getters) {
|
|
43369
|
+
const title = numCol.header ? _t("%(numHeader)s by %(cat1Header)s and %(cat2Header)s", {
|
|
43370
|
+
numHeader: numCol.header,
|
|
43371
|
+
cat1Header: cat1.header ?? _t("Level 1"),
|
|
43372
|
+
cat2Header: cat2.header ?? _t("Level 2")
|
|
43373
|
+
}) : _t("Two-level hierarchy");
|
|
43374
|
+
const hasTitle = numCol.hasHeader;
|
|
43375
|
+
return {
|
|
43376
|
+
title,
|
|
43377
|
+
hierarchySource: rangeSource([dataset(cat1.zone, getters, "0"), dataset(cat2.zone, getters, "1")], hasTitle, getUnboundRange(getters, numCol.zone)),
|
|
43378
|
+
barSource: rangeSource([dataset(numCol.zone, getters)], hasTitle, getUnboundRange(getters, cat1.zone))
|
|
43379
|
+
};
|
|
43380
|
+
}
|
|
43381
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43382
|
+
function buildCategoricalDateNumberContext([catCol, dateCol, numCol], getters) {
|
|
43383
|
+
const dateRange = getUnboundRange(getters, dateCol.zone);
|
|
43384
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43385
|
+
const hasTitle = numCol.hasHeader;
|
|
43386
|
+
return {
|
|
43387
|
+
title: numCol.header ? _t("%(numHeader)s by %(catHeader)s over %(dateHeader)s", {
|
|
43388
|
+
numHeader: numCol.header,
|
|
43389
|
+
catHeader: catCol.header ?? _t("Category"),
|
|
43390
|
+
dateHeader: dateCol.header ?? _t("Time")
|
|
43391
|
+
}) : _t("Multi-series over Time"),
|
|
43392
|
+
sourceByDate: rangeSource([dataset(numCol.zone, getters)], hasTitle, dateRange),
|
|
43393
|
+
sourceByCat: rangeSource([dataset(numCol.zone, getters)], hasTitle, catRange)
|
|
43394
|
+
};
|
|
43395
|
+
}
|
|
43396
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43397
|
+
function buildLabelVsMultipleNumbersContext([labelCol, ...numCols], getters) {
|
|
43398
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43399
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43400
|
+
const title = labelCol.header ? _t("By %(header)s", { header: labelCol.header }) : _t("Profile Comparison");
|
|
43401
|
+
const source = rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange);
|
|
43402
|
+
const scatterSource = rangeSource([dataset(numCols[1].zone, getters)], numCols[1].hasHeader, getUnboundRange(getters, numCols[0].zone));
|
|
43403
|
+
const bubble = numCols.length === 3 ? {
|
|
43404
|
+
xRange: getUnboundRange(getters, numCols[0].zone),
|
|
43405
|
+
yRanges: [getUnboundRange(getters, numCols[1].zone)],
|
|
43406
|
+
sizeRange: getUnboundRange(getters, numCols[2].zone),
|
|
43407
|
+
labelRange,
|
|
43408
|
+
hasTitle
|
|
43409
|
+
} : void 0;
|
|
43410
|
+
return {
|
|
43411
|
+
title,
|
|
43412
|
+
source,
|
|
43413
|
+
rowCount: labelCol.rowCount,
|
|
43414
|
+
numColsCount: numCols.length,
|
|
43415
|
+
scatterSource,
|
|
43416
|
+
bubble
|
|
43417
|
+
};
|
|
43418
|
+
}
|
|
43419
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43420
|
+
function buildCategoricalTwoNumbersContext([catCol, numCol1, numCol2], getters) {
|
|
43421
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43422
|
+
const hasTitle = numCol1.hasHeader || numCol2.hasHeader;
|
|
43423
|
+
const title = numCol1.header && numCol2.header ? _t("%(num1Header)s vs %(num2Header)s by %(catHeader)s", {
|
|
43424
|
+
num1Header: numCol1.header,
|
|
43425
|
+
num2Header: numCol2.header,
|
|
43426
|
+
catHeader: catCol.header ?? _t("Category")
|
|
43427
|
+
}) : catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Category Comparison");
|
|
43428
|
+
const isPyramid = isPyramidLike(numCol1, numCol2);
|
|
43429
|
+
return {
|
|
43430
|
+
title,
|
|
43431
|
+
sourceBoth: rangeSource([dataset(numCol1.zone, getters, "0"), dataset(numCol2.zone, getters, "1")], hasTitle, catRange),
|
|
43432
|
+
isPyramid
|
|
43433
|
+
};
|
|
43434
|
+
}
|
|
43435
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43436
|
+
function buildManyNumbersContext(cols, getters) {
|
|
43437
|
+
const title = cols.every((c) => c.hasHeader) ? cols.map((c) => c.header).join(" / ") : _t("KPI Overview");
|
|
43438
|
+
const hasTitle = cols.some((c) => c.hasHeader);
|
|
43439
|
+
const source = rangeSource(cols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle);
|
|
43440
|
+
const { lastCellXC } = interestingCellsXc(cols[2]);
|
|
43441
|
+
const { lastCellXC: firstCellXC } = interestingCellsXc(cols[0]);
|
|
43442
|
+
const { lastCellXC: secondCellXC } = interestingCellsXc(cols[1]);
|
|
43443
|
+
return {
|
|
43444
|
+
title,
|
|
43445
|
+
source,
|
|
43446
|
+
colsLength: cols.length,
|
|
43447
|
+
rowCount: cols[0].rowCount,
|
|
43448
|
+
lastCellXC,
|
|
43449
|
+
firstCellXC,
|
|
43450
|
+
secondCellXC
|
|
43451
|
+
};
|
|
43452
|
+
}
|
|
43453
|
+
const NUMBER_OR_PERCENTAGE = ["number", "percentage"];
|
|
43454
|
+
const EXACT_PATTERNS = [
|
|
43455
|
+
{
|
|
43456
|
+
pattern: { leadingTypes: ["number"] },
|
|
43457
|
+
buildContext: buildSingleNumberContext,
|
|
43458
|
+
suggestions: SINGLE_NUMBER_COLUMN_SUGGESTIONS
|
|
43459
|
+
},
|
|
43460
|
+
{
|
|
43461
|
+
pattern: { leadingTypes: ["percentage"] },
|
|
43462
|
+
buildContext: buildSinglePercentageContext,
|
|
43463
|
+
suggestions: SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS
|
|
43464
|
+
},
|
|
43465
|
+
{
|
|
43466
|
+
pattern: { leadingTypes: ["date"] },
|
|
43467
|
+
buildContext: buildSingleDateContext,
|
|
43468
|
+
suggestions: SINGLE_DATE_COLUMN_SUGGESTIONS
|
|
43469
|
+
},
|
|
43470
|
+
{
|
|
43471
|
+
pattern: { leadingTypes: ["categorical"] },
|
|
43472
|
+
buildContext: buildSingleCategoricalContext,
|
|
43473
|
+
suggestions: SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS
|
|
43474
|
+
},
|
|
43475
|
+
{
|
|
43476
|
+
pattern: { leadingTypes: ["label"] },
|
|
43477
|
+
buildContext: buildSingleLabelContext,
|
|
43478
|
+
suggestions: SINGLE_LABEL_COLUMN_SUGGESTIONS
|
|
43479
|
+
},
|
|
43480
|
+
{
|
|
43481
|
+
pattern: { leadingTypes: ["categorical", "number"] },
|
|
43482
|
+
buildContext: buildCategoricalVsNumberContext,
|
|
43483
|
+
suggestions: CATEGORICAL_VS_NUMBER_SUGGESTIONS
|
|
43484
|
+
},
|
|
43485
|
+
{
|
|
43486
|
+
pattern: { leadingTypes: ["date", NUMBER_OR_PERCENTAGE] },
|
|
43487
|
+
buildContext: buildDateVsSeriesContext,
|
|
43488
|
+
suggestions: DATE_VS_SERIES_SUGGESTIONS
|
|
43489
|
+
},
|
|
43490
|
+
{
|
|
43491
|
+
pattern: { leadingTypes: ["number", "number"] },
|
|
43492
|
+
buildContext: buildNumberVsNumberContext,
|
|
43493
|
+
suggestions: NUMBER_VS_NUMBER_SUGGESTIONS
|
|
43494
|
+
},
|
|
43495
|
+
{
|
|
43496
|
+
pattern: { leadingTypes: ["categorical", "percentage"] },
|
|
43497
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43498
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43499
|
+
},
|
|
43500
|
+
{
|
|
43501
|
+
pattern: { leadingTypes: ["label", "number"] },
|
|
43502
|
+
buildContext: buildLabelVsNumberContext,
|
|
43503
|
+
suggestions: LABEL_VS_NUMBER_SUGGESTIONS
|
|
43504
|
+
},
|
|
43505
|
+
{
|
|
43506
|
+
pattern: { leadingTypes: ["label", "percentage"] },
|
|
43507
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43508
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43509
|
+
},
|
|
43510
|
+
{
|
|
43511
|
+
pattern: { leadingTypes: [
|
|
43512
|
+
"categorical",
|
|
43513
|
+
"categorical",
|
|
43514
|
+
"number"
|
|
43515
|
+
] },
|
|
43516
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43517
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43518
|
+
},
|
|
43519
|
+
{
|
|
43520
|
+
pattern: { leadingTypes: [
|
|
43521
|
+
"categorical",
|
|
43522
|
+
"label",
|
|
43523
|
+
"number"
|
|
43524
|
+
] },
|
|
43525
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43526
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43527
|
+
},
|
|
43528
|
+
{
|
|
43529
|
+
pattern: { leadingTypes: [
|
|
43530
|
+
"categorical",
|
|
43531
|
+
"date",
|
|
43532
|
+
NUMBER_OR_PERCENTAGE
|
|
43533
|
+
] },
|
|
43534
|
+
buildContext: buildCategoricalDateNumberContext,
|
|
43535
|
+
suggestions: CATEGORICAL_DATE_NUMBER_SUGGESTIONS
|
|
43536
|
+
},
|
|
43537
|
+
{
|
|
43538
|
+
pattern: { leadingTypes: [
|
|
43539
|
+
"categorical",
|
|
43540
|
+
NUMBER_OR_PERCENTAGE,
|
|
43541
|
+
NUMBER_OR_PERCENTAGE
|
|
43542
|
+
] },
|
|
43543
|
+
buildContext: buildCategoricalTwoNumbersContext,
|
|
43544
|
+
suggestions: CATEGORICAL_TWO_NUMBERS_SUGGESTIONS
|
|
43545
|
+
}
|
|
43546
|
+
];
|
|
43547
|
+
const EXTENDABLE_PATTERNS = [
|
|
43548
|
+
{
|
|
43549
|
+
pattern: {
|
|
43550
|
+
leadingTypes: [],
|
|
43551
|
+
extension: {
|
|
43552
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43553
|
+
minSize: 3
|
|
43554
|
+
}
|
|
43555
|
+
},
|
|
43556
|
+
buildContext: buildManyNumbersContext,
|
|
43557
|
+
suggestions: MANY_NUMBERS_SUGGESTIONS
|
|
43558
|
+
},
|
|
43559
|
+
{
|
|
43560
|
+
pattern: {
|
|
43561
|
+
leadingTypes: ["label"],
|
|
43562
|
+
extension: {
|
|
43563
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43564
|
+
minSize: 2
|
|
43565
|
+
}
|
|
43566
|
+
},
|
|
43567
|
+
buildContext: buildLabelVsMultipleNumbersContext,
|
|
43568
|
+
suggestions: LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43569
|
+
},
|
|
43570
|
+
{
|
|
43571
|
+
pattern: {
|
|
43572
|
+
leadingTypes: ["categorical"],
|
|
43573
|
+
extension: {
|
|
43574
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43575
|
+
minSize: 2
|
|
43576
|
+
}
|
|
43577
|
+
},
|
|
43578
|
+
buildContext: buildCategoricalVsMultipleNumbersContext,
|
|
43579
|
+
suggestions: CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43580
|
+
},
|
|
43581
|
+
{
|
|
43582
|
+
pattern: {
|
|
43583
|
+
leadingTypes: ["date"],
|
|
43584
|
+
extension: {
|
|
43585
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43586
|
+
minSize: 2
|
|
43587
|
+
}
|
|
43588
|
+
},
|
|
43589
|
+
buildContext: buildDateVsMultipleNumbersContext,
|
|
43590
|
+
suggestions: DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43591
|
+
}
|
|
43592
|
+
];
|
|
43593
|
+
function getChartSuggestions(zones, getters) {
|
|
43594
|
+
const cols = analyzeColumns(zones, getters);
|
|
43595
|
+
if (cols.some((c) => c.type === "error")) return [];
|
|
43596
|
+
const nonEmpty = cols.filter((c) => c.type !== "empty");
|
|
43597
|
+
if (!nonEmpty.length) return [];
|
|
43598
|
+
const shape = nonEmpty.map((c) => c.type);
|
|
43599
|
+
const rule = EXACT_PATTERNS.find((rule) => matchesShape(rule.pattern, shape)) ?? EXTENDABLE_PATTERNS.find((rule) => matchesShape(rule.pattern, shape));
|
|
43600
|
+
if (!rule) return [];
|
|
43601
|
+
const ctx = rule.buildContext(nonEmpty, getters);
|
|
43602
|
+
return rule.suggestions.filter((suggestion) => suggestion.isApplicable?.(ctx) ?? true).map((suggestion) => ({
|
|
43603
|
+
description: suggestion.description.toString(),
|
|
43604
|
+
definition: suggestion.build(ctx)
|
|
43605
|
+
}));
|
|
43606
|
+
}
|
|
43607
|
+
|
|
43608
|
+
//#endregion
|
|
43609
|
+
//#region src/components/side_panel/data_analysis/data_analysis_store.ts
|
|
43610
|
+
var DataAnalysisStore = class extends SpreadsheetStore {
|
|
43611
|
+
mutators = [];
|
|
43612
|
+
hasData = false;
|
|
43613
|
+
chartSuggestions = [];
|
|
43614
|
+
isDirty = false;
|
|
43615
|
+
ranges;
|
|
43616
|
+
constructor(get) {
|
|
43617
|
+
super(get);
|
|
43618
|
+
this.model.selection.observe(this, { handleEvent: () => this.refresh() });
|
|
43619
|
+
this.onDispose(() => {
|
|
43620
|
+
this.model.selection.unobserve(this);
|
|
43621
|
+
});
|
|
43622
|
+
this.refresh();
|
|
43623
|
+
}
|
|
43624
|
+
handle(cmd) {
|
|
43625
|
+
if (invalidateEvaluationCommands.has(cmd.type) || cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd)) this.isDirty = true;
|
|
43626
|
+
switch (cmd.type) {
|
|
43627
|
+
case "HIDE_COLUMNS_ROWS":
|
|
43628
|
+
case "UNHIDE_COLUMNS_ROWS":
|
|
43629
|
+
case "GROUP_HEADERS":
|
|
43630
|
+
case "UNGROUP_HEADERS":
|
|
43631
|
+
case "ACTIVATE_SHEET":
|
|
43632
|
+
case "ACTIVATE_NEXT_SHEET":
|
|
43633
|
+
case "ACTIVATE_PREVIOUS_SHEET":
|
|
43634
|
+
case "EVALUATE_CELLS":
|
|
43635
|
+
case "SET_FORMATTING":
|
|
43636
|
+
case "CLEAR_FORMATTING":
|
|
43637
|
+
this.isDirty = true;
|
|
43638
|
+
break;
|
|
43639
|
+
}
|
|
43640
|
+
}
|
|
43641
|
+
finalize() {
|
|
43642
|
+
if (this.isDirty) {
|
|
43643
|
+
this.refresh();
|
|
43644
|
+
this.isDirty = false;
|
|
43645
|
+
}
|
|
43646
|
+
}
|
|
43647
|
+
refresh() {
|
|
43648
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
43649
|
+
const zones = this.getters.getSelectedZones();
|
|
43650
|
+
this.ranges = zones.map(zoneToXc);
|
|
43651
|
+
this.hasData = zones.some((zone) => this.getters.getEvaluatedCellsInZone(sheetId, zone).some((cell) => cell.type !== "empty"));
|
|
43652
|
+
const suggestions = this.hasData ? getChartSuggestions(zones, this.getters) : [];
|
|
43653
|
+
this.chartSuggestions = suggestions;
|
|
43654
|
+
}
|
|
43655
|
+
};
|
|
43656
|
+
|
|
43657
|
+
//#endregion
|
|
43658
|
+
//#region src/components/side_panel/data_analysis/data_analysis_panel.ts
|
|
43659
|
+
var DataAnalysisPanel = class extends Component {
|
|
43660
|
+
static template = "o-spreadsheet-DataAnalysisPanel";
|
|
43661
|
+
static components = {
|
|
43662
|
+
Section,
|
|
43663
|
+
ChartSuggestionPreview
|
|
43664
|
+
};
|
|
43665
|
+
store;
|
|
43666
|
+
setup() {
|
|
43667
|
+
this.store = useLocalStore(DataAnalysisStore);
|
|
43668
|
+
}
|
|
43669
|
+
onStartChartSuggestionDrag(definition, ev) {
|
|
43670
|
+
startChartDragAndDrop(this.env, definition, ev);
|
|
43671
|
+
}
|
|
43672
|
+
};
|
|
43673
|
+
|
|
42146
43674
|
//#endregion
|
|
42147
43675
|
//#region src/registries/data_source_registry.ts
|
|
42148
43676
|
const unusedDataSourceRegistry = new Registry();
|
|
@@ -42219,7 +43747,7 @@ set(value) {
|
|
|
42219
43747
|
var DataValidationPreview = class extends Component {
|
|
42220
43748
|
static template = "o-spreadsheet-DataValidationPreview";
|
|
42221
43749
|
props = (0, _odoo_owl.useProps)({ rule: types.DataValidationRule() });
|
|
42222
|
-
dvPreviewRef =
|
|
43750
|
+
dvPreviewRef = _odoo_owl.signal.ref();
|
|
42223
43751
|
setup() {
|
|
42224
43752
|
useHighlightsOnHover(this.dvPreviewRef, this);
|
|
42225
43753
|
}
|
|
@@ -43718,7 +45246,7 @@ set(value) {
|
|
|
43718
45246
|
};
|
|
43719
45247
|
props = (0, _odoo_owl.useProps)({ namedRange: types.NamedRange() });
|
|
43720
45248
|
state = (0, _odoo_owl.proxy)({});
|
|
43721
|
-
namedRangePreviewRef =
|
|
45249
|
+
namedRangePreviewRef = _odoo_owl.signal.ref();
|
|
43722
45250
|
setup() {
|
|
43723
45251
|
useHighlightsOnHover(this.namedRangePreviewRef, this);
|
|
43724
45252
|
}
|
|
@@ -44209,7 +45737,7 @@ set(value) {
|
|
|
44209
45737
|
onClick: types.function(),
|
|
44210
45738
|
scrolledTo: types.or([types.literal("top"), types.literal("bottom")]).optional()
|
|
44211
45739
|
});
|
|
44212
|
-
itemRef =
|
|
45740
|
+
itemRef = _odoo_owl.signal.ref();
|
|
44213
45741
|
setup() {
|
|
44214
45742
|
(0, _odoo_owl.onWillPatch)(() => {
|
|
44215
45743
|
if (this.props.scrolledTo) this.scrollListToSelectedValue();
|
|
@@ -44449,7 +45977,7 @@ set(value) {
|
|
|
44449
45977
|
onFiltersUpdated: types.function()
|
|
44450
45978
|
});
|
|
44451
45979
|
state;
|
|
44452
|
-
buttonFilter =
|
|
45980
|
+
buttonFilter = _odoo_owl.signal.ref();
|
|
44453
45981
|
popover;
|
|
44454
45982
|
setup() {
|
|
44455
45983
|
useExternalListener(window, "click", (ev) => {
|
|
@@ -44578,11 +46106,11 @@ set(value) {
|
|
|
44578
46106
|
onFieldPicked: types.function(),
|
|
44579
46107
|
fields: types.array()
|
|
44580
46108
|
});
|
|
44581
|
-
buttonRef =
|
|
46109
|
+
buttonRef = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
44582
46110
|
popover = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
44583
46111
|
search = (0, _odoo_owl.proxy)({ input: "" });
|
|
44584
46112
|
autoComplete;
|
|
44585
|
-
autofocusRef =
|
|
46113
|
+
autofocusRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
44586
46114
|
setup() {
|
|
44587
46115
|
this.autoComplete = useLocalStore(AutoCompleteStore);
|
|
44588
46116
|
this.autoComplete.useProvider(this.getProvider());
|
|
@@ -44991,7 +46519,7 @@ set(value) {
|
|
|
44991
46519
|
getScrollableContainerEl: types.function().optional(),
|
|
44992
46520
|
pivotId: types.UID()
|
|
44993
46521
|
});
|
|
44994
|
-
dimensionsRef =
|
|
46522
|
+
dimensionsRef = _odoo_owl.signal.ref();
|
|
44995
46523
|
dragAndDrop = useDragAndDropListItems();
|
|
44996
46524
|
AGGREGATORS = AGGREGATORS;
|
|
44997
46525
|
isDateOrDatetimeField = isDateOrDatetimeField;
|
|
@@ -45062,16 +46590,7 @@ set(value) {
|
|
|
45062
46590
|
});
|
|
45063
46591
|
}
|
|
45064
46592
|
getDimensionElementsRects() {
|
|
45065
|
-
return Array.from(this.dimensionsRef().children).map((el) =>
|
|
45066
|
-
const style = getComputedStyle(el);
|
|
45067
|
-
const rect = el.getBoundingClientRect();
|
|
45068
|
-
return {
|
|
45069
|
-
x: rect.x,
|
|
45070
|
-
y: rect.y,
|
|
45071
|
-
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
45072
|
-
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
45073
|
-
};
|
|
45074
|
-
});
|
|
46593
|
+
return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
|
|
45075
46594
|
}
|
|
45076
46595
|
removeDimension(dimension) {
|
|
45077
46596
|
const { columns, rows } = this.props.definition;
|
|
@@ -45636,7 +47155,7 @@ set(value) {
|
|
|
45636
47155
|
});
|
|
45637
47156
|
store;
|
|
45638
47157
|
state;
|
|
45639
|
-
pivotSidePanelRef =
|
|
47158
|
+
pivotSidePanelRef = _odoo_owl.signal.ref();
|
|
45640
47159
|
setup() {
|
|
45641
47160
|
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
|
|
45642
47161
|
this.state = (0, _odoo_owl.proxy)({
|
|
@@ -46876,7 +48395,7 @@ set(value) {
|
|
|
46876
48395
|
selected: types.boolean().optional(),
|
|
46877
48396
|
onClick: types.function().optional()
|
|
46878
48397
|
});
|
|
46879
|
-
canvasRef =
|
|
48398
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
46880
48399
|
menu = (0, _odoo_owl.proxy)({
|
|
46881
48400
|
isOpen: false,
|
|
46882
48401
|
anchorRect: null,
|
|
@@ -46970,7 +48489,7 @@ set(value) {
|
|
|
46970
48489
|
tableStyles: types.RecordOf(),
|
|
46971
48490
|
type: types.or([types.literal("table"), types.literal("pivot")])
|
|
46972
48491
|
});
|
|
46973
|
-
tableStyleListRef =
|
|
48492
|
+
tableStyleListRef = _odoo_owl.signal.ref();
|
|
46974
48493
|
state = (0, _odoo_owl.proxy)({ selectedCategory: this.initialSelectedCategory });
|
|
46975
48494
|
setup() {
|
|
46976
48495
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
@@ -48341,6 +49860,10 @@ set(value) {
|
|
|
48341
49860
|
title: _t("Column statistics"),
|
|
48342
49861
|
Body: ColumnStatsPanel
|
|
48343
49862
|
});
|
|
49863
|
+
sidePanelRegistry.add("DataAnalysisPanel", {
|
|
49864
|
+
title: _t("Data analysis"),
|
|
49865
|
+
Body: DataAnalysisPanel
|
|
49866
|
+
});
|
|
48344
49867
|
sidePanelRegistry.add("TableSidePanel", {
|
|
48345
49868
|
title: _t("Edit table"),
|
|
48346
49869
|
Body: TablePanel,
|
|
@@ -48448,6 +49971,7 @@ set(value) {
|
|
|
48448
49971
|
"changePanelSize",
|
|
48449
49972
|
"resetPanelSize",
|
|
48450
49973
|
"togglePinPanel",
|
|
49974
|
+
"togglePinnedSidePanel",
|
|
48451
49975
|
"closeMainPanel",
|
|
48452
49976
|
"changeSpreadsheetWidth",
|
|
48453
49977
|
"toggleCollapsePanel"
|
|
@@ -48599,6 +50123,19 @@ set(value) {
|
|
|
48599
50123
|
this.secondaryPanel = void 0;
|
|
48600
50124
|
}
|
|
48601
50125
|
}
|
|
50126
|
+
togglePinnedSidePanel(componentTag, panelProps = {}) {
|
|
50127
|
+
if (this.mainPanel?.componentTag === componentTag && this.isMainPanelOpen) {
|
|
50128
|
+
this.closeMainPanel();
|
|
50129
|
+
return;
|
|
50130
|
+
}
|
|
50131
|
+
if (this.secondaryPanel?.componentTag === componentTag && this.isSecondaryPanelOpen) {
|
|
50132
|
+
this.secondaryPanel?.currentPanelProps.onCloseSidePanel?.();
|
|
50133
|
+
this.secondaryPanel = void 0;
|
|
50134
|
+
return;
|
|
50135
|
+
}
|
|
50136
|
+
this.open(componentTag, panelProps);
|
|
50137
|
+
if (!this.mainPanel?.isPinned) this.togglePinPanel();
|
|
50138
|
+
}
|
|
48602
50139
|
toggleCollapsePanel(panel) {
|
|
48603
50140
|
const panelInfo = this[panel];
|
|
48604
50141
|
if (!panelInfo) return;
|
|
@@ -48977,6 +50514,219 @@ set(value) {
|
|
|
48977
50514
|
}
|
|
48978
50515
|
};
|
|
48979
50516
|
|
|
50517
|
+
//#endregion
|
|
50518
|
+
//#region src/helpers/sort_interactive.ts
|
|
50519
|
+
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
50520
|
+
let multiColumns = zone.right > zone.left;
|
|
50521
|
+
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
50522
|
+
multiColumns = false;
|
|
50523
|
+
let table;
|
|
50524
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
50525
|
+
table = [];
|
|
50526
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
50527
|
+
const merge = env.model.getters.getMerge({
|
|
50528
|
+
sheetId,
|
|
50529
|
+
col,
|
|
50530
|
+
row
|
|
50531
|
+
});
|
|
50532
|
+
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
50533
|
+
}
|
|
50534
|
+
if (table.length >= 2) {
|
|
50535
|
+
multiColumns = true;
|
|
50536
|
+
break;
|
|
50537
|
+
}
|
|
50538
|
+
}
|
|
50539
|
+
}
|
|
50540
|
+
if (multiColumns) {
|
|
50541
|
+
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50542
|
+
return;
|
|
50543
|
+
}
|
|
50544
|
+
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
50545
|
+
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50546
|
+
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));
|
|
50547
|
+
}
|
|
50548
|
+
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
50549
|
+
const result = env.model.dispatch("SORT_CELLS", {
|
|
50550
|
+
sheetId,
|
|
50551
|
+
col: anchor.col,
|
|
50552
|
+
row: anchor.row,
|
|
50553
|
+
zone,
|
|
50554
|
+
sortDirection,
|
|
50555
|
+
sortOptions
|
|
50556
|
+
});
|
|
50557
|
+
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
50558
|
+
const { col, row } = anchor;
|
|
50559
|
+
env.model.selection.selectZone({
|
|
50560
|
+
cell: {
|
|
50561
|
+
col,
|
|
50562
|
+
row
|
|
50563
|
+
},
|
|
50564
|
+
zone
|
|
50565
|
+
});
|
|
50566
|
+
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
50567
|
+
}
|
|
50568
|
+
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
50569
|
+
const { col, row } = anchor;
|
|
50570
|
+
env.model.selection.selectZone({
|
|
50571
|
+
cell: {
|
|
50572
|
+
col,
|
|
50573
|
+
row
|
|
50574
|
+
},
|
|
50575
|
+
zone
|
|
50576
|
+
});
|
|
50577
|
+
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
50578
|
+
}
|
|
50579
|
+
}
|
|
50580
|
+
|
|
50581
|
+
//#endregion
|
|
50582
|
+
//#region src/stores/trim_whitespace_store.ts
|
|
50583
|
+
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
50584
|
+
mutators = ["trimWhitespace"];
|
|
50585
|
+
notificationStore = this.get(NotificationStore);
|
|
50586
|
+
trimWhitespace() {
|
|
50587
|
+
this.model.dispatch("TRIM_WHITESPACE");
|
|
50588
|
+
}
|
|
50589
|
+
handle(cmd) {
|
|
50590
|
+
switch (cmd.type) {
|
|
50591
|
+
case "TRIM_WHITESPACE":
|
|
50592
|
+
this._trimWhitespace();
|
|
50593
|
+
break;
|
|
50594
|
+
}
|
|
50595
|
+
}
|
|
50596
|
+
_trimWhitespace() {
|
|
50597
|
+
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
50598
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
50599
|
+
let count = 0;
|
|
50600
|
+
for (const { col, row } of zones.map(positions).flat()) {
|
|
50601
|
+
const cell = this.getters.getCell({
|
|
50602
|
+
col,
|
|
50603
|
+
row,
|
|
50604
|
+
sheetId
|
|
50605
|
+
});
|
|
50606
|
+
if (!cell) continue;
|
|
50607
|
+
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
50608
|
+
const trimmedContent = trimContent(currentContent);
|
|
50609
|
+
if (trimmedContent !== currentContent) {
|
|
50610
|
+
count += 1;
|
|
50611
|
+
this.model.dispatch("UPDATE_CELL", {
|
|
50612
|
+
sheetId,
|
|
50613
|
+
col,
|
|
50614
|
+
row,
|
|
50615
|
+
content: trimmedContent
|
|
50616
|
+
});
|
|
50617
|
+
}
|
|
50618
|
+
}
|
|
50619
|
+
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
50620
|
+
this.notificationStore.notifyUser({
|
|
50621
|
+
type: "info",
|
|
50622
|
+
text,
|
|
50623
|
+
sticky: false
|
|
50624
|
+
});
|
|
50625
|
+
}
|
|
50626
|
+
};
|
|
50627
|
+
|
|
50628
|
+
//#endregion
|
|
50629
|
+
//#region src/actions/data_actions.ts
|
|
50630
|
+
const sortRange = {
|
|
50631
|
+
name: _t("Sort range"),
|
|
50632
|
+
isVisible: IS_ONLY_ONE_RANGE,
|
|
50633
|
+
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
50634
|
+
};
|
|
50635
|
+
const sortAscending = {
|
|
50636
|
+
name: _t("Ascending (A ⟶ Z)"),
|
|
50637
|
+
execute: (env) => {
|
|
50638
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50639
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
50640
|
+
},
|
|
50641
|
+
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
50642
|
+
};
|
|
50643
|
+
const dataCleanup = {
|
|
50644
|
+
name: _t("Data cleanup"),
|
|
50645
|
+
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
50646
|
+
};
|
|
50647
|
+
const removeDuplicates = {
|
|
50648
|
+
name: _t("Remove duplicates"),
|
|
50649
|
+
execute: (env) => {
|
|
50650
|
+
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
50651
|
+
env.openSidePanel("RemoveDuplicates", {});
|
|
50652
|
+
},
|
|
50653
|
+
isEnabled: (env) => !env.isSmall
|
|
50654
|
+
};
|
|
50655
|
+
const trimWhitespace = {
|
|
50656
|
+
name: _t("Trim whitespace"),
|
|
50657
|
+
execute: (env) => {
|
|
50658
|
+
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
50659
|
+
}
|
|
50660
|
+
};
|
|
50661
|
+
const cleanupDataSources = {
|
|
50662
|
+
name: _t("Remove unused data sources"),
|
|
50663
|
+
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
50664
|
+
isEnabled: (env) => !env.isSmall
|
|
50665
|
+
};
|
|
50666
|
+
const sortDescending = {
|
|
50667
|
+
name: _t("Descending (Z ⟶ A)"),
|
|
50668
|
+
execute: (env) => {
|
|
50669
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50670
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
50671
|
+
},
|
|
50672
|
+
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
50673
|
+
};
|
|
50674
|
+
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
50675
|
+
const createRemoveFilterTool = {
|
|
50676
|
+
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
50677
|
+
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
50678
|
+
};
|
|
50679
|
+
const splitToColumns = {
|
|
50680
|
+
name: _t("Split text to columns"),
|
|
50681
|
+
sequence: 1,
|
|
50682
|
+
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
50683
|
+
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
50684
|
+
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
50685
|
+
};
|
|
50686
|
+
const dataAnalysis = {
|
|
50687
|
+
name: _t("Data analysis"),
|
|
50688
|
+
execute: (env) => {
|
|
50689
|
+
env.getStore(SidePanelStore).togglePinnedSidePanel("DataAnalysisPanel");
|
|
50690
|
+
},
|
|
50691
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS",
|
|
50692
|
+
isEnabled: (env) => !env.isSmall
|
|
50693
|
+
};
|
|
50694
|
+
const columnStatistics = {
|
|
50695
|
+
name: _t("Column statistics"),
|
|
50696
|
+
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
50697
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
50698
|
+
};
|
|
50699
|
+
const reinsertDynamicPivotMenu = {
|
|
50700
|
+
id: "reinsert_dynamic_pivot",
|
|
50701
|
+
name: _t("Re-insert dynamic pivot"),
|
|
50702
|
+
sequence: 60,
|
|
50703
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50704
|
+
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
50705
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50706
|
+
};
|
|
50707
|
+
const reinsertStaticPivotMenu = {
|
|
50708
|
+
id: "reinsert_static_pivot",
|
|
50709
|
+
name: _t("Re-insert static pivot"),
|
|
50710
|
+
sequence: 70,
|
|
50711
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50712
|
+
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
50713
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50714
|
+
};
|
|
50715
|
+
const calculationMode = {
|
|
50716
|
+
name: _t("Calculation"),
|
|
50717
|
+
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
50718
|
+
};
|
|
50719
|
+
const automaticCalculation = {
|
|
50720
|
+
name: _t("Automatic"),
|
|
50721
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
50722
|
+
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
50723
|
+
};
|
|
50724
|
+
const manualCalculation = {
|
|
50725
|
+
name: _t("Manual"),
|
|
50726
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
50727
|
+
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
50728
|
+
};
|
|
50729
|
+
|
|
48980
50730
|
//#endregion
|
|
48981
50731
|
//#region src/helpers/ui/merge_interactive.ts
|
|
48982
50732
|
const AddMergeInteractiveContent = {
|
|
@@ -49191,6 +50941,10 @@ set(value) {
|
|
|
49191
50941
|
}).addChild("paste_format_only", ["paste_special"], {
|
|
49192
50942
|
...pasteSpecialFormat,
|
|
49193
50943
|
sequence: 20
|
|
50944
|
+
}).add("data_analysis", {
|
|
50945
|
+
...dataAnalysis,
|
|
50946
|
+
sequence: 50,
|
|
50947
|
+
separator: true
|
|
49194
50948
|
}).add("add_row_before", {
|
|
49195
50949
|
...cellInsertRowsBefore,
|
|
49196
50950
|
sequence: 70
|
|
@@ -49298,211 +51052,6 @@ set(value) {
|
|
|
49298
51052
|
sequence: 30
|
|
49299
51053
|
});
|
|
49300
51054
|
|
|
49301
|
-
//#endregion
|
|
49302
|
-
//#region src/helpers/sort_interactive.ts
|
|
49303
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
49304
|
-
let multiColumns = zone.right > zone.left;
|
|
49305
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
49306
|
-
multiColumns = false;
|
|
49307
|
-
let table;
|
|
49308
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
49309
|
-
table = [];
|
|
49310
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
49311
|
-
const merge = env.model.getters.getMerge({
|
|
49312
|
-
sheetId,
|
|
49313
|
-
col,
|
|
49314
|
-
row
|
|
49315
|
-
});
|
|
49316
|
-
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
49317
|
-
}
|
|
49318
|
-
if (table.length >= 2) {
|
|
49319
|
-
multiColumns = true;
|
|
49320
|
-
break;
|
|
49321
|
-
}
|
|
49322
|
-
}
|
|
49323
|
-
}
|
|
49324
|
-
if (multiColumns) {
|
|
49325
|
-
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49326
|
-
return;
|
|
49327
|
-
}
|
|
49328
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
49329
|
-
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49330
|
-
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));
|
|
49331
|
-
}
|
|
49332
|
-
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
49333
|
-
const result = env.model.dispatch("SORT_CELLS", {
|
|
49334
|
-
sheetId,
|
|
49335
|
-
col: anchor.col,
|
|
49336
|
-
row: anchor.row,
|
|
49337
|
-
zone,
|
|
49338
|
-
sortDirection,
|
|
49339
|
-
sortOptions
|
|
49340
|
-
});
|
|
49341
|
-
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
49342
|
-
const { col, row } = anchor;
|
|
49343
|
-
env.model.selection.selectZone({
|
|
49344
|
-
cell: {
|
|
49345
|
-
col,
|
|
49346
|
-
row
|
|
49347
|
-
},
|
|
49348
|
-
zone
|
|
49349
|
-
});
|
|
49350
|
-
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
49351
|
-
}
|
|
49352
|
-
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
49353
|
-
const { col, row } = anchor;
|
|
49354
|
-
env.model.selection.selectZone({
|
|
49355
|
-
cell: {
|
|
49356
|
-
col,
|
|
49357
|
-
row
|
|
49358
|
-
},
|
|
49359
|
-
zone
|
|
49360
|
-
});
|
|
49361
|
-
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
49362
|
-
}
|
|
49363
|
-
}
|
|
49364
|
-
|
|
49365
|
-
//#endregion
|
|
49366
|
-
//#region src/stores/trim_whitespace_store.ts
|
|
49367
|
-
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
49368
|
-
mutators = ["trimWhitespace"];
|
|
49369
|
-
notificationStore = this.get(NotificationStore);
|
|
49370
|
-
trimWhitespace() {
|
|
49371
|
-
this.model.dispatch("TRIM_WHITESPACE");
|
|
49372
|
-
}
|
|
49373
|
-
handle(cmd) {
|
|
49374
|
-
switch (cmd.type) {
|
|
49375
|
-
case "TRIM_WHITESPACE":
|
|
49376
|
-
this._trimWhitespace();
|
|
49377
|
-
break;
|
|
49378
|
-
}
|
|
49379
|
-
}
|
|
49380
|
-
_trimWhitespace() {
|
|
49381
|
-
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
49382
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
49383
|
-
let count = 0;
|
|
49384
|
-
for (const { col, row } of zones.map(positions).flat()) {
|
|
49385
|
-
const cell = this.getters.getCell({
|
|
49386
|
-
col,
|
|
49387
|
-
row,
|
|
49388
|
-
sheetId
|
|
49389
|
-
});
|
|
49390
|
-
if (!cell) continue;
|
|
49391
|
-
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
49392
|
-
const trimmedContent = trimContent(currentContent);
|
|
49393
|
-
if (trimmedContent !== currentContent) {
|
|
49394
|
-
count += 1;
|
|
49395
|
-
this.model.dispatch("UPDATE_CELL", {
|
|
49396
|
-
sheetId,
|
|
49397
|
-
col,
|
|
49398
|
-
row,
|
|
49399
|
-
content: trimmedContent
|
|
49400
|
-
});
|
|
49401
|
-
}
|
|
49402
|
-
}
|
|
49403
|
-
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
49404
|
-
this.notificationStore.notifyUser({
|
|
49405
|
-
type: "info",
|
|
49406
|
-
text,
|
|
49407
|
-
sticky: false
|
|
49408
|
-
});
|
|
49409
|
-
}
|
|
49410
|
-
};
|
|
49411
|
-
|
|
49412
|
-
//#endregion
|
|
49413
|
-
//#region src/actions/data_actions.ts
|
|
49414
|
-
const sortRange = {
|
|
49415
|
-
name: _t("Sort range"),
|
|
49416
|
-
isVisible: IS_ONLY_ONE_RANGE,
|
|
49417
|
-
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
49418
|
-
};
|
|
49419
|
-
const sortAscending = {
|
|
49420
|
-
name: _t("Ascending (A ⟶ Z)"),
|
|
49421
|
-
execute: (env) => {
|
|
49422
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49423
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
49424
|
-
},
|
|
49425
|
-
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
49426
|
-
};
|
|
49427
|
-
const dataCleanup = {
|
|
49428
|
-
name: _t("Data cleanup"),
|
|
49429
|
-
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
49430
|
-
};
|
|
49431
|
-
const removeDuplicates = {
|
|
49432
|
-
name: _t("Remove duplicates"),
|
|
49433
|
-
execute: (env) => {
|
|
49434
|
-
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
49435
|
-
env.openSidePanel("RemoveDuplicates", {});
|
|
49436
|
-
},
|
|
49437
|
-
isEnabled: (env) => !env.isSmall
|
|
49438
|
-
};
|
|
49439
|
-
const trimWhitespace = {
|
|
49440
|
-
name: _t("Trim whitespace"),
|
|
49441
|
-
execute: (env) => {
|
|
49442
|
-
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
49443
|
-
}
|
|
49444
|
-
};
|
|
49445
|
-
const cleanupDataSources = {
|
|
49446
|
-
name: _t("Remove unused data sources"),
|
|
49447
|
-
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
49448
|
-
isEnabled: (env) => !env.isSmall
|
|
49449
|
-
};
|
|
49450
|
-
const sortDescending = {
|
|
49451
|
-
name: _t("Descending (Z ⟶ A)"),
|
|
49452
|
-
execute: (env) => {
|
|
49453
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49454
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
49455
|
-
},
|
|
49456
|
-
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
49457
|
-
};
|
|
49458
|
-
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
49459
|
-
const createRemoveFilterTool = {
|
|
49460
|
-
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
49461
|
-
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
49462
|
-
};
|
|
49463
|
-
const splitToColumns = {
|
|
49464
|
-
name: _t("Split text to columns"),
|
|
49465
|
-
sequence: 1,
|
|
49466
|
-
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
49467
|
-
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
49468
|
-
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
49469
|
-
};
|
|
49470
|
-
const columnStatistics = {
|
|
49471
|
-
name: _t("Column statistics"),
|
|
49472
|
-
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
49473
|
-
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
49474
|
-
};
|
|
49475
|
-
const reinsertDynamicPivotMenu = {
|
|
49476
|
-
id: "reinsert_dynamic_pivot",
|
|
49477
|
-
name: _t("Re-insert dynamic pivot"),
|
|
49478
|
-
sequence: 60,
|
|
49479
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49480
|
-
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
49481
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49482
|
-
};
|
|
49483
|
-
const reinsertStaticPivotMenu = {
|
|
49484
|
-
id: "reinsert_static_pivot",
|
|
49485
|
-
name: _t("Re-insert static pivot"),
|
|
49486
|
-
sequence: 70,
|
|
49487
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49488
|
-
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
49489
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49490
|
-
};
|
|
49491
|
-
const calculationMode = {
|
|
49492
|
-
name: _t("Calculation"),
|
|
49493
|
-
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
49494
|
-
};
|
|
49495
|
-
const automaticCalculation = {
|
|
49496
|
-
name: _t("Automatic"),
|
|
49497
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
49498
|
-
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
49499
|
-
};
|
|
49500
|
-
const manualCalculation = {
|
|
49501
|
-
name: _t("Manual"),
|
|
49502
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
49503
|
-
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
49504
|
-
};
|
|
49505
|
-
|
|
49506
51055
|
//#endregion
|
|
49507
51056
|
//#region src/registries/menus/col_menu_registry.ts
|
|
49508
51057
|
const colMenuRegistry = new MenuItemRegistry();
|
|
@@ -49536,6 +51085,9 @@ set(value) {
|
|
|
49536
51085
|
}).addChild("sort_descending", ["sort_columns"], {
|
|
49537
51086
|
...sortDescending,
|
|
49538
51087
|
sequence: 20
|
|
51088
|
+
}).add("data_analysis", {
|
|
51089
|
+
...dataAnalysis,
|
|
51090
|
+
sequence: 55
|
|
49539
51091
|
}).add("column_statistics", {
|
|
49540
51092
|
...columnStatistics,
|
|
49541
51093
|
sequence: 60,
|
|
@@ -52351,14 +53903,15 @@ set(value) {
|
|
|
52351
53903
|
selectedRect: void 0,
|
|
52352
53904
|
horizontalSnap: void 0,
|
|
52353
53905
|
verticalSnap: void 0,
|
|
52354
|
-
cancelDnd: void 0
|
|
52355
|
-
overlappingChartOrCarousel: void 0
|
|
53906
|
+
cancelDnd: void 0
|
|
52356
53907
|
});
|
|
52357
53908
|
viewStore;
|
|
52358
53909
|
zoomStore;
|
|
53910
|
+
chartDragStore;
|
|
52359
53911
|
setup() {
|
|
52360
53912
|
this.viewStore = useStore(ViewportsStore);
|
|
52361
53913
|
this.zoomStore = useStore(ZoomStore);
|
|
53914
|
+
this.chartDragStore = useStore(ChartDragStore);
|
|
52362
53915
|
(0, _odoo_owl.onMounted)(() => {
|
|
52363
53916
|
this.render();
|
|
52364
53917
|
});
|
|
@@ -52372,7 +53925,7 @@ set(value) {
|
|
|
52372
53925
|
this.dnd.selectedRect = void 0;
|
|
52373
53926
|
this.dnd.horizontalSnap = void 0;
|
|
52374
53927
|
this.dnd.verticalSnap = void 0;
|
|
52375
|
-
this.
|
|
53928
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52376
53929
|
this.dnd.cancelDnd = void 0;
|
|
52377
53930
|
}
|
|
52378
53931
|
});
|
|
@@ -52498,6 +54051,7 @@ set(value) {
|
|
|
52498
54051
|
maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
|
|
52499
54052
|
};
|
|
52500
54053
|
let hasStartedDnd = false;
|
|
54054
|
+
let overlappingChartOrCarousel = void 0;
|
|
52501
54055
|
const onMouseMove = (ev) => {
|
|
52502
54056
|
const currentMousePosition = {
|
|
52503
54057
|
x: ev.clientX / zoom,
|
|
@@ -52509,10 +54063,10 @@ set(value) {
|
|
|
52509
54063
|
hasStartedDnd = true;
|
|
52510
54064
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
|
|
52511
54065
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
52512
|
-
|
|
54066
|
+
overlappingChartOrCarousel = void 0;
|
|
52513
54067
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
52514
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel =
|
|
52515
|
-
this.
|
|
54068
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
|
|
54069
|
+
this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
|
|
52516
54070
|
if (!overlappingChartOrCarousel) {
|
|
52517
54071
|
const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
|
|
52518
54072
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
@@ -52534,7 +54088,7 @@ set(value) {
|
|
|
52534
54088
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
52535
54089
|
return;
|
|
52536
54090
|
}
|
|
52537
|
-
if (!
|
|
54091
|
+
if (!overlappingChartOrCarousel) {
|
|
52538
54092
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
52539
54093
|
return {
|
|
52540
54094
|
sheetId,
|
|
@@ -52544,14 +54098,14 @@ set(value) {
|
|
|
52544
54098
|
}) || [];
|
|
52545
54099
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
52546
54100
|
} else {
|
|
52547
|
-
const overlappingFigureId =
|
|
54101
|
+
const overlappingFigureId = overlappingChartOrCarousel.id;
|
|
52548
54102
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
52549
|
-
if (
|
|
54103
|
+
if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
52550
54104
|
sheetId,
|
|
52551
54105
|
carouselFigureId: overlappingFigureId,
|
|
52552
54106
|
chartFigureIds
|
|
52553
54107
|
});
|
|
52554
|
-
else if (
|
|
54108
|
+
else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
52555
54109
|
sheetId,
|
|
52556
54110
|
baseFigureId: overlappingFigureId,
|
|
52557
54111
|
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
@@ -52562,7 +54116,7 @@ set(value) {
|
|
|
52562
54116
|
this.dnd.selectedRect = void 0;
|
|
52563
54117
|
this.dnd.horizontalSnap = void 0;
|
|
52564
54118
|
this.dnd.verticalSnap = void 0;
|
|
52565
|
-
this.
|
|
54119
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52566
54120
|
};
|
|
52567
54121
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52568
54122
|
}
|
|
@@ -52618,7 +54172,6 @@ set(value) {
|
|
|
52618
54172
|
this.dnd.selectedRect = void 0;
|
|
52619
54173
|
this.dnd.horizontalSnap = void 0;
|
|
52620
54174
|
this.dnd.verticalSnap = void 0;
|
|
52621
|
-
this.dnd.overlappingChartOrCarousel = void 0;
|
|
52622
54175
|
};
|
|
52623
54176
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52624
54177
|
}
|
|
@@ -52628,12 +54181,12 @@ set(value) {
|
|
|
52628
54181
|
getFigureStyle(figureUI) {
|
|
52629
54182
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
52630
54183
|
return cssPropertiesToCss({
|
|
52631
|
-
opacity: this.
|
|
54184
|
+
opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
|
|
52632
54185
|
cursor: "grabbing"
|
|
52633
54186
|
});
|
|
52634
54187
|
}
|
|
52635
54188
|
getFigureClass(figureUI) {
|
|
52636
|
-
if (figureUI.id !== this.
|
|
54189
|
+
if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
|
|
52637
54190
|
return "o-add-to-carousel";
|
|
52638
54191
|
}
|
|
52639
54192
|
getSnap(snapLine) {
|
|
@@ -52676,26 +54229,6 @@ set(value) {
|
|
|
52676
54229
|
height: `100%`
|
|
52677
54230
|
});
|
|
52678
54231
|
}
|
|
52679
|
-
getOverlappingFigure(figureUI, otherFigures) {
|
|
52680
|
-
if (figureUI.tag !== "chart") return;
|
|
52681
|
-
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
52682
|
-
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
52683
|
-
let bestMatch;
|
|
52684
|
-
let smallestDistance = Infinity;
|
|
52685
|
-
for (const figure of otherFigures) {
|
|
52686
|
-
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
52687
|
-
const targetCenterX = figure.x + figure.width / 2;
|
|
52688
|
-
const targetCenterY = figure.y + figure.height / 2;
|
|
52689
|
-
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
52690
|
-
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
52691
|
-
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
52692
|
-
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
52693
|
-
smallestDistance = squaredDistance;
|
|
52694
|
-
bestMatch = figure;
|
|
52695
|
-
}
|
|
52696
|
-
}
|
|
52697
|
-
return bestMatch;
|
|
52698
|
-
}
|
|
52699
54232
|
};
|
|
52700
54233
|
|
|
52701
54234
|
//#endregion
|
|
@@ -52995,7 +54528,7 @@ set(value) {
|
|
|
52995
54528
|
var ColResizer = class extends AbstractResizer {
|
|
52996
54529
|
static template = "o-spreadsheet-ColResizer";
|
|
52997
54530
|
static components = { UnhideColumnHeaders };
|
|
52998
|
-
colResizerRef =
|
|
54531
|
+
colResizerRef = _odoo_owl.signal.ref();
|
|
52999
54532
|
setup() {
|
|
53000
54533
|
super.setup();
|
|
53001
54534
|
this.PADDING = 15;
|
|
@@ -53129,7 +54662,7 @@ set(value) {
|
|
|
53129
54662
|
var RowResizer = class extends AbstractResizer {
|
|
53130
54663
|
static template = "o-spreadsheet-RowResizer";
|
|
53131
54664
|
static components = { UnhideRowHeaders };
|
|
53132
|
-
rowResizerRef =
|
|
54665
|
+
rowResizerRef = _odoo_owl.signal.ref();
|
|
53133
54666
|
setup() {
|
|
53134
54667
|
super.setup();
|
|
53135
54668
|
this.PADDING = 5;
|
|
@@ -53738,9 +55271,8 @@ set(value) {
|
|
|
53738
55271
|
useStore(TableResizeStore);
|
|
53739
55272
|
}
|
|
53740
55273
|
get containerStyle() {
|
|
53741
|
-
const tableZone = this.props.table.range.zone;
|
|
53742
55274
|
const sheetId = this.props.table.range.sheetId;
|
|
53743
|
-
|
|
55275
|
+
const tableZone = this.props.table.range.zone;
|
|
53744
55276
|
const bottomRight = {
|
|
53745
55277
|
...tableZone,
|
|
53746
55278
|
left: tableZone.right,
|
|
@@ -53827,8 +55359,8 @@ set(value) {
|
|
|
53827
55359
|
HEADER_HEIGHT = 26;
|
|
53828
55360
|
HEADER_WIDTH = 48;
|
|
53829
55361
|
menuState;
|
|
53830
|
-
gridRef =
|
|
53831
|
-
canvasRef =
|
|
55362
|
+
gridRef = _odoo_owl.signal.ref();
|
|
55363
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
53832
55364
|
highlightStore;
|
|
53833
55365
|
viewStore;
|
|
53834
55366
|
zoomStore;
|
|
@@ -54459,6 +55991,9 @@ set(value) {
|
|
|
54459
55991
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
54460
55992
|
return this.env.model.getters.getClientsToDisplay(sheetId);
|
|
54461
55993
|
}
|
|
55994
|
+
get displayTableResizer() {
|
|
55995
|
+
return this.env.model.getters.isGridSelectionActive() && !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
55996
|
+
}
|
|
54462
55997
|
};
|
|
54463
55998
|
|
|
54464
55999
|
//#endregion
|
|
@@ -54477,7 +56012,7 @@ set(value) {
|
|
|
54477
56012
|
static template = "o-spreadsheet-FullScreenFigure";
|
|
54478
56013
|
static components = { ChartFigure };
|
|
54479
56014
|
fullScreenFigureStore;
|
|
54480
|
-
fullScreenFigureRef =
|
|
56015
|
+
fullScreenFigureRef = _odoo_owl.signal.ref();
|
|
54481
56016
|
spreadsheetRect = useSpreadsheetRect();
|
|
54482
56017
|
figureRegistry = figureRegistry;
|
|
54483
56018
|
setup() {
|
|
@@ -54725,6 +56260,41 @@ set(value) {
|
|
|
54725
56260
|
}
|
|
54726
56261
|
};
|
|
54727
56262
|
|
|
56263
|
+
//#endregion
|
|
56264
|
+
//#region src/components/tables/hovered_table_store.ts
|
|
56265
|
+
var HoveredTableStore = class extends SpreadsheetStore {
|
|
56266
|
+
mutators = ["hover"];
|
|
56267
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
56268
|
+
position;
|
|
56269
|
+
constructor(get) {
|
|
56270
|
+
super(get);
|
|
56271
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
56272
|
+
cellHoverOverlayStore.register(this);
|
|
56273
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
56274
|
+
}
|
|
56275
|
+
getHighlightedPositions(hoveredPosition) {
|
|
56276
|
+
const highlightedPositions = [];
|
|
56277
|
+
const { sheetId, row } = hoveredPosition;
|
|
56278
|
+
const table = this.getters.getTable(hoveredPosition);
|
|
56279
|
+
if (!table) return highlightedPositions;
|
|
56280
|
+
const { left, right, top } = table.range.zone;
|
|
56281
|
+
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
56282
|
+
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
56283
|
+
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
56284
|
+
sheetId,
|
|
56285
|
+
col,
|
|
56286
|
+
row
|
|
56287
|
+
}).formattedValue;
|
|
56288
|
+
});
|
|
56289
|
+
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) highlightedPositions.push({
|
|
56290
|
+
sheetId,
|
|
56291
|
+
col,
|
|
56292
|
+
row
|
|
56293
|
+
});
|
|
56294
|
+
return highlightedPositions;
|
|
56295
|
+
}
|
|
56296
|
+
};
|
|
56297
|
+
|
|
54728
56298
|
//#endregion
|
|
54729
56299
|
//#region src/helpers/figures/charts/runtime/helpers_index.ts
|
|
54730
56300
|
var helpers_index_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -55515,7 +57085,8 @@ set(value) {
|
|
|
55515
57085
|
* The result of this method is:
|
|
55516
57086
|
* - if the cell is not a formula, returns the content as is and resets the base formula
|
|
55517
57087
|
* - if the cell is a formula:
|
|
55518
|
-
* - if there is no previous formula, or the normalized formula is different from the previous one
|
|
57088
|
+
* - if there is no previous formula, or the normalized formula is different from the previous one or it contains blacklisted functions (see NonSquishableFunctionRegistry),
|
|
57089
|
+
* resets the base formula to this one and returns the full formula string
|
|
55519
57090
|
* - else, compares the literal values and range dependencies to the previous formula, and for each parameter:
|
|
55520
57091
|
* - for numbers: returns a relative change (+N or -N) if possible, else the full number or "=" if unchanged
|
|
55521
57092
|
* - for strings: returns the full string if changed, else "="
|
|
@@ -55523,6 +57094,10 @@ set(value) {
|
|
|
55523
57094
|
* */
|
|
55524
57095
|
squish(cell, forSheetId) {
|
|
55525
57096
|
if (cell.isFormula) {
|
|
57097
|
+
if (NonSquishableFunctionRegistry.getAll().some((functionName) => cell.compiledFormula.usesSymbol(functionName))) {
|
|
57098
|
+
this.resetBaseFormula();
|
|
57099
|
+
return cell.compiledFormula.toFormulaString(this.getters, this.rangeToStringOptions);
|
|
57100
|
+
}
|
|
55526
57101
|
let numbers = [];
|
|
55527
57102
|
let strings = [];
|
|
55528
57103
|
let references = [];
|
|
@@ -65325,7 +66900,7 @@ set(value) {
|
|
|
65325
66900
|
}
|
|
65326
66901
|
message = {
|
|
65327
66902
|
...message,
|
|
65328
|
-
commands:
|
|
66903
|
+
commands: revision.commands
|
|
65329
66904
|
};
|
|
65330
66905
|
}
|
|
65331
66906
|
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.
|
|
@@ -67822,6 +69397,12 @@ set(value) {
|
|
|
67822
69397
|
const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
|
|
67823
69398
|
const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
67824
69399
|
if (figures.some((f) => f === void 0 || f.tag !== "chart") || !baseFigureId || baseFigureId.tag !== "chart") return "FigureDoesNotExist";
|
|
69400
|
+
break;
|
|
69401
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69402
|
+
const baseFigure = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69403
|
+
if (this.getters.getFigure(cmd.sheetId, cmd.figureId) || !baseFigure) return "InvalidFigureId";
|
|
69404
|
+
if (baseFigure.tag !== "chart") return "FigureDoesNotExist";
|
|
69405
|
+
break;
|
|
67825
69406
|
}
|
|
67826
69407
|
return "Success";
|
|
67827
69408
|
}
|
|
@@ -67857,6 +69438,29 @@ set(value) {
|
|
|
67857
69438
|
carouselFigureId,
|
|
67858
69439
|
chartFigureIds: cmd.chartFigureIds
|
|
67859
69440
|
});
|
|
69441
|
+
break;
|
|
69442
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69443
|
+
const baseFigureToMerge = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69444
|
+
if (!baseFigureToMerge) throw new Error(`Figure ${cmd.baseFigureId} does not exists.`);
|
|
69445
|
+
this.dispatch("CREATE_CHART", {
|
|
69446
|
+
chartId: cmd.chartId,
|
|
69447
|
+
figureId: cmd.figureId,
|
|
69448
|
+
sheetId: cmd.sheetId,
|
|
69449
|
+
definition: cmd.definition,
|
|
69450
|
+
col: baseFigureToMerge.col,
|
|
69451
|
+
row: baseFigureToMerge.row,
|
|
69452
|
+
offset: baseFigureToMerge.offset,
|
|
69453
|
+
size: {
|
|
69454
|
+
width: baseFigureToMerge.width,
|
|
69455
|
+
height: baseFigureToMerge.height
|
|
69456
|
+
}
|
|
69457
|
+
});
|
|
69458
|
+
this.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
69459
|
+
sheetId: cmd.sheetId,
|
|
69460
|
+
baseFigureId: cmd.baseFigureId,
|
|
69461
|
+
chartFigureIds: [cmd.baseFigureId, cmd.figureId]
|
|
69462
|
+
});
|
|
69463
|
+
break;
|
|
67860
69464
|
}
|
|
67861
69465
|
}
|
|
67862
69466
|
getFigureUI(sheetId, figure) {
|
|
@@ -69546,6 +71150,9 @@ set(value) {
|
|
|
69546
71150
|
}).addChild("split_to_columns", ["data"], {
|
|
69547
71151
|
...splitToColumns,
|
|
69548
71152
|
sequence: 20
|
|
71153
|
+
}).addChild("data_analysis", ["data"], {
|
|
71154
|
+
...dataAnalysis,
|
|
71155
|
+
sequence: 24
|
|
69549
71156
|
}).addChild("column_statistics", ["data"], {
|
|
69550
71157
|
...columnStatistics,
|
|
69551
71158
|
sequence: 25
|
|
@@ -69947,7 +71554,7 @@ set(value) {
|
|
|
69947
71554
|
onAnimationEnd: types.function(),
|
|
69948
71555
|
style: types.string()
|
|
69949
71556
|
});
|
|
69950
|
-
rippleRef =
|
|
71557
|
+
rippleRef = _odoo_owl.signal.ref();
|
|
69951
71558
|
setup() {
|
|
69952
71559
|
let animation = void 0;
|
|
69953
71560
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -70001,7 +71608,7 @@ set(value) {
|
|
|
70001
71608
|
onAnimationEnd: types.function().optional(() => () => {}),
|
|
70002
71609
|
class: types.string().optional("")
|
|
70003
71610
|
});
|
|
70004
|
-
childContainerRef =
|
|
71611
|
+
childContainerRef = _odoo_owl.signal.ref();
|
|
70005
71612
|
state = (0, _odoo_owl.proxy)({ ripples: [] });
|
|
70006
71613
|
currentId = 1;
|
|
70007
71614
|
onClick(ev) {
|
|
@@ -70272,9 +71879,9 @@ set(value) {
|
|
|
70272
71879
|
isEditing: false,
|
|
70273
71880
|
openedPicker: void 0
|
|
70274
71881
|
});
|
|
70275
|
-
sheetDivRef =
|
|
70276
|
-
iconRef =
|
|
70277
|
-
sheetNameRef =
|
|
71882
|
+
sheetDivRef = _odoo_owl.signal.ref();
|
|
71883
|
+
iconRef = _odoo_owl.signal.ref();
|
|
71884
|
+
sheetNameRef = _odoo_owl.signal.ref();
|
|
70278
71885
|
editionState = "initializing";
|
|
70279
71886
|
DOMFocusableElementStore;
|
|
70280
71887
|
setup() {
|
|
@@ -70558,8 +72165,10 @@ set(value) {
|
|
|
70558
72165
|
});
|
|
70559
72166
|
state = (0, _odoo_owl.proxy)({ selectedStatisticFn: "" });
|
|
70560
72167
|
store;
|
|
72168
|
+
sidePanelStore;
|
|
70561
72169
|
setup() {
|
|
70562
72170
|
this.store = useStore(AggregateStatisticsStore);
|
|
72171
|
+
this.sidePanelStore = useStore(SidePanelStore);
|
|
70563
72172
|
(0, _odoo_owl.onWillUpdateProps)(() => {
|
|
70564
72173
|
if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
|
|
70565
72174
|
});
|
|
@@ -70599,6 +72208,12 @@ set(value) {
|
|
|
70599
72208
|
format: fnValue.format()
|
|
70600
72209
|
});
|
|
70601
72210
|
}
|
|
72211
|
+
get isDataAnalysisOpen() {
|
|
72212
|
+
return this.sidePanelStore.mainPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isMainPanelOpen || this.sidePanelStore.secondaryPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isSecondaryPanelOpen;
|
|
72213
|
+
}
|
|
72214
|
+
toggleDataAnalysisPanel() {
|
|
72215
|
+
this.sidePanelStore.togglePinnedSidePanel("DataAnalysisPanel");
|
|
72216
|
+
}
|
|
70602
72217
|
};
|
|
70603
72218
|
|
|
70604
72219
|
//#endregion
|
|
@@ -70613,8 +72228,8 @@ set(value) {
|
|
|
70613
72228
|
BottomBarSheet,
|
|
70614
72229
|
BottomBarStatistic
|
|
70615
72230
|
};
|
|
70616
|
-
bottomBarRef =
|
|
70617
|
-
sheetListRef =
|
|
72231
|
+
bottomBarRef = _odoo_owl.signal.ref();
|
|
72232
|
+
sheetListRef = _odoo_owl.signal.ref();
|
|
70618
72233
|
dragAndDrop = useDragAndDropListItems();
|
|
70619
72234
|
targetScroll = void 0;
|
|
70620
72235
|
state = (0, _odoo_owl.proxy)({
|
|
@@ -70827,12 +72442,13 @@ set(value) {
|
|
|
70827
72442
|
hoveredCell;
|
|
70828
72443
|
viewStore;
|
|
70829
72444
|
zoomStore;
|
|
70830
|
-
gridRef =
|
|
70831
|
-
canvasRef =
|
|
72445
|
+
gridRef = _odoo_owl.signal.ref();
|
|
72446
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
70832
72447
|
setup() {
|
|
70833
72448
|
this.hoveredCell = useStore(DelayedHoveredCellStore);
|
|
70834
72449
|
this.viewStore = useStore(ViewportsStore);
|
|
70835
72450
|
this.zoomStore = useStore(ZoomStore);
|
|
72451
|
+
useLocalStore(HoveredTableStore);
|
|
70836
72452
|
const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
|
|
70837
72453
|
useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
|
|
70838
72454
|
useGridDrawing({
|
|
@@ -71250,8 +72866,8 @@ set(value) {
|
|
|
71250
72866
|
static components = { Menu };
|
|
71251
72867
|
props = (0, _odoo_owl.useProps)({ onClose: types.function() });
|
|
71252
72868
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
71253
|
-
menuRef =
|
|
71254
|
-
containerRef =
|
|
72869
|
+
menuRef = _odoo_owl.signal.ref();
|
|
72870
|
+
containerRef = _odoo_owl.signal.ref();
|
|
71255
72871
|
state = (0, _odoo_owl.proxy)({
|
|
71256
72872
|
menuItems: this.rootItems,
|
|
71257
72873
|
title: _t("Menu Bar"),
|
|
@@ -71324,7 +72940,7 @@ set(value) {
|
|
|
71324
72940
|
composerStore;
|
|
71325
72941
|
viewStore;
|
|
71326
72942
|
composerInterface;
|
|
71327
|
-
composerRef =
|
|
72943
|
+
composerRef = _odoo_owl.signal.ref();
|
|
71328
72944
|
menuState = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
71329
72945
|
setup() {
|
|
71330
72946
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -71403,6 +73019,49 @@ set(value) {
|
|
|
71403
73019
|
}
|
|
71404
73020
|
};
|
|
71405
73021
|
|
|
73022
|
+
//#endregion
|
|
73023
|
+
//#region src/components/standalone_grid_canvas/carousel_data_view_print.ts
|
|
73024
|
+
var CarouselDataViewPrint = class extends Component {
|
|
73025
|
+
static template = _odoo_owl.xml``;
|
|
73026
|
+
props = (0, _odoo_owl.useProps)({
|
|
73027
|
+
range: types.Range(),
|
|
73028
|
+
columnWeights: types.array().optional(),
|
|
73029
|
+
rect: types.object(),
|
|
73030
|
+
rendererStore: types.object()
|
|
73031
|
+
});
|
|
73032
|
+
viewportsStore;
|
|
73033
|
+
gridRender;
|
|
73034
|
+
setup() {
|
|
73035
|
+
useChildStoreProvider([ViewportsStore]);
|
|
73036
|
+
const store = useLocalStore(StandaloneViewportStore, this.props.range, this.props.columnWeights);
|
|
73037
|
+
this.gridRender = useLocalStore(GridRenderer, null);
|
|
73038
|
+
this.viewportsStore = useStore(ViewportsStore);
|
|
73039
|
+
(0, _odoo_owl.useEffect)(() => {
|
|
73040
|
+
store.setContainerSize(this.props.rect.width, this.props.rect.height);
|
|
73041
|
+
});
|
|
73042
|
+
(0, _odoo_owl.onMounted)(() => this.props.rendererStore.register(this));
|
|
73043
|
+
(0, _odoo_owl.onWillUnmount)(() => this.props.rendererStore.unRegister(this));
|
|
73044
|
+
}
|
|
73045
|
+
get renderingLayers() {
|
|
73046
|
+
return ["Chart"];
|
|
73047
|
+
}
|
|
73048
|
+
drawLayer(renderingContext) {
|
|
73049
|
+
const { ctx } = renderingContext;
|
|
73050
|
+
ctx.save();
|
|
73051
|
+
ctx.translate(this.props.rect.x, this.props.rect.y);
|
|
73052
|
+
ctx.beginPath();
|
|
73053
|
+
ctx.rect(0, 0, this.props.rect.width, this.props.rect.height);
|
|
73054
|
+
ctx.clip();
|
|
73055
|
+
const subRenderingCtx = {
|
|
73056
|
+
...renderingContext,
|
|
73057
|
+
sheetId: this.viewportsStore.displayedSheetId,
|
|
73058
|
+
viewports: this.viewportsStore.viewports
|
|
73059
|
+
};
|
|
73060
|
+
this.gridRender.drawLayer(subRenderingCtx, "Background", void 0);
|
|
73061
|
+
ctx.restore();
|
|
73062
|
+
}
|
|
73063
|
+
};
|
|
73064
|
+
|
|
71406
73065
|
//#endregion
|
|
71407
73066
|
//#region src/components/standalone_grid_canvas/figure_renderer_store.ts
|
|
71408
73067
|
/** Store that draws the figures directly onto the canvas */
|
|
@@ -71428,29 +73087,25 @@ set(value) {
|
|
|
71428
73087
|
drawLayer(renderingCtx) {
|
|
71429
73088
|
const { viewports, ctx } = renderingCtx;
|
|
71430
73089
|
const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
|
|
71431
|
-
const
|
|
73090
|
+
const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
|
|
71432
73091
|
for (const figure of visibleFigures) {
|
|
71433
|
-
const
|
|
71434
|
-
|
|
73092
|
+
const figureRect = {
|
|
73093
|
+
...figure,
|
|
73094
|
+
x: figure.x - scrollOffset.x,
|
|
73095
|
+
y: figure.y - scrollOffset.y
|
|
73096
|
+
};
|
|
73097
|
+
const { x, y, width, height } = figureRect;
|
|
71435
73098
|
if (figure.tag === "chart") {
|
|
71436
73099
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71437
|
-
if (chartId)
|
|
71438
|
-
const chartRect = {
|
|
71439
|
-
x,
|
|
71440
|
-
y,
|
|
71441
|
-
width: figure.width,
|
|
71442
|
-
height: figure.height
|
|
71443
|
-
};
|
|
71444
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71445
|
-
}
|
|
73100
|
+
if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
|
|
71446
73101
|
} else if (figure.tag === "image") {
|
|
71447
73102
|
const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
|
|
71448
|
-
if (loadedImage) ctx.drawImage(loadedImage, x, y,
|
|
71449
|
-
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure);
|
|
73103
|
+
if (loadedImage) ctx.drawImage(loadedImage, x, y, width, height);
|
|
73104
|
+
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure, figureRect);
|
|
71450
73105
|
if (!this.getters.isDashboard()) {
|
|
71451
73106
|
ctx.strokeStyle = GRAY_400;
|
|
71452
73107
|
ctx.lineWidth = 1;
|
|
71453
|
-
ctx.strokeRect(x, y,
|
|
73108
|
+
ctx.strokeRect(x, y, width, height);
|
|
71454
73109
|
}
|
|
71455
73110
|
}
|
|
71456
73111
|
}
|
|
@@ -71468,53 +73123,37 @@ set(value) {
|
|
|
71468
73123
|
renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
|
|
71469
73124
|
cleanUp();
|
|
71470
73125
|
}
|
|
71471
|
-
drawCarousel(renderingCtx, figure) {
|
|
71472
|
-
const {
|
|
71473
|
-
const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
|
|
71474
|
-
const x = figure.x - offsetX;
|
|
71475
|
-
const y = figure.y - offsetY;
|
|
73126
|
+
drawCarousel(renderingCtx, figure, figureRect) {
|
|
73127
|
+
const { ctx } = renderingCtx;
|
|
71476
73128
|
const carousel = this.getters.getCarousel(figure.id);
|
|
71477
73129
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71478
73130
|
if (!carousel) return;
|
|
73131
|
+
const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
|
|
71479
73132
|
const chartDefinition = chartId ? this.getters.getChartDefinition(chartId) : void 0;
|
|
73133
|
+
ctx.save();
|
|
73134
|
+
ctx.beginPath();
|
|
73135
|
+
ctx.rect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
73136
|
+
ctx.clip();
|
|
73137
|
+
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
73138
|
+
ctx.fillRect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
71480
73139
|
const title = {
|
|
71481
73140
|
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
71482
73141
|
...carousel.title
|
|
71483
73142
|
};
|
|
71484
|
-
|
|
71485
|
-
|
|
71486
|
-
if (!title.text && chartId) {
|
|
71487
|
-
const chartRect = {
|
|
71488
|
-
x,
|
|
71489
|
-
y,
|
|
71490
|
-
width: figure.width,
|
|
71491
|
-
height: figure.height
|
|
71492
|
-
};
|
|
71493
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71494
|
-
} else if (title.text) {
|
|
71495
|
-
ctx.save();
|
|
71496
|
-
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
71497
|
-
ctx.fillRect(x, y, figure.width, headerSize);
|
|
71498
|
-
ctx.font = computeTextFont(chartStyleToCellStyle(title));
|
|
73143
|
+
if (title.text) {
|
|
73144
|
+
ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
|
|
71499
73145
|
ctx.fillStyle = title.color;
|
|
71500
|
-
ctx.
|
|
71501
|
-
|
|
71502
|
-
|
|
71503
|
-
|
|
71504
|
-
|
|
71505
|
-
|
|
71506
|
-
|
|
71507
|
-
|
|
71508
|
-
if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
|
|
71509
|
-
else if (!this.getters.isDashboard()) {
|
|
71510
|
-
ctx.strokeStyle = GRAY_400;
|
|
71511
|
-
ctx.lineWidth = 1;
|
|
71512
|
-
ctx.beginPath();
|
|
71513
|
-
ctx.moveTo(x, y + headerSize);
|
|
71514
|
-
ctx.lineTo(x + figure.width, y + headerSize);
|
|
71515
|
-
ctx.stroke();
|
|
71516
|
-
}
|
|
73146
|
+
ctx.textBaseline = "middle";
|
|
73147
|
+
const textY = Math.ceil(layout.headerRect.y + layout.headerRect.height / 2);
|
|
73148
|
+
ctx.fillText(title.text, layout.headerRect.x, textY);
|
|
73149
|
+
}
|
|
73150
|
+
const separator = layout.separatorRect;
|
|
73151
|
+
if (separator) {
|
|
73152
|
+
ctx.fillStyle = GRAY_400;
|
|
73153
|
+
ctx.fillRect(separator.x, separator.y, separator.width, separator.height);
|
|
71517
73154
|
}
|
|
73155
|
+
ctx.restore();
|
|
73156
|
+
if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
|
|
71518
73157
|
}
|
|
71519
73158
|
};
|
|
71520
73159
|
|
|
@@ -71522,12 +73161,13 @@ set(value) {
|
|
|
71522
73161
|
//#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
|
|
71523
73162
|
var StandaloneGridCanvas = class extends Component {
|
|
71524
73163
|
static template = "o-spreadsheet-StandaloneGridCanvas";
|
|
73164
|
+
static components = { CarouselDataViewPrint };
|
|
71525
73165
|
props = (0, _odoo_owl.useProps)({
|
|
71526
73166
|
sheetId: types.UID(),
|
|
71527
73167
|
zone: types.Zone(),
|
|
71528
73168
|
renderingCtx: types.object()
|
|
71529
73169
|
});
|
|
71530
|
-
canvasRef =
|
|
73170
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
71531
73171
|
rendererStore;
|
|
71532
73172
|
figureRendererStore;
|
|
71533
73173
|
setup() {
|
|
@@ -71549,6 +73189,28 @@ set(value) {
|
|
|
71549
73189
|
this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
|
|
71550
73190
|
}));
|
|
71551
73191
|
}
|
|
73192
|
+
get carouselDataViews() {
|
|
73193
|
+
const { sheetId, viewports } = this.props.renderingCtx;
|
|
73194
|
+
return viewports.getVisibleFigures(sheetId).filter((figure) => figure.tag === "carousel").map((figure) => {
|
|
73195
|
+
const carouselItem = this.env.model.getters.getSelectedCarouselItem(figure.id);
|
|
73196
|
+
if (carouselItem?.type !== "carouselDataView" || !carouselItem.range) return;
|
|
73197
|
+
const carousel = this.env.model.getters.getCarousel(figure.id);
|
|
73198
|
+
const scroll = viewports.getViewportOffset(sheetId);
|
|
73199
|
+
const { x, y, width, height } = figure;
|
|
73200
|
+
const layout = getCarouselLayout({
|
|
73201
|
+
x: x - scroll.x,
|
|
73202
|
+
y: y - scroll.y,
|
|
73203
|
+
width,
|
|
73204
|
+
height
|
|
73205
|
+
}, carousel, carouselItem);
|
|
73206
|
+
return {
|
|
73207
|
+
range: carouselItem.range,
|
|
73208
|
+
columnWeights: carouselItem.columnWeights,
|
|
73209
|
+
rect: layout.contentRect,
|
|
73210
|
+
rendererStore: this.rendererStore
|
|
73211
|
+
};
|
|
73212
|
+
}).filter(isDefined);
|
|
73213
|
+
}
|
|
71552
73214
|
};
|
|
71553
73215
|
|
|
71554
73216
|
//#endregion
|
|
@@ -72010,7 +73672,7 @@ set(value) {
|
|
|
72010
73672
|
anchorRect: null,
|
|
72011
73673
|
menuItems: []
|
|
72012
73674
|
});
|
|
72013
|
-
namedRangeSelectorRef =
|
|
73675
|
+
namedRangeSelectorRef = _odoo_owl.signal.ref();
|
|
72014
73676
|
setup() {
|
|
72015
73677
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72016
73678
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -72208,7 +73870,7 @@ set(value) {
|
|
|
72208
73870
|
anchorRect: types.Rect()
|
|
72209
73871
|
});
|
|
72210
73872
|
BORDER_POSITIONS = BORDER_POSITIONS;
|
|
72211
|
-
lineStyleButtonRef =
|
|
73873
|
+
lineStyleButtonRef = _odoo_owl.signal.ref();
|
|
72212
73874
|
borderStyles = borderStyles;
|
|
72213
73875
|
state = (0, _odoo_owl.proxy)({ activeTool: void 0 });
|
|
72214
73876
|
toggleDropdownTool(tool) {
|
|
@@ -72275,7 +73937,7 @@ set(value) {
|
|
|
72275
73937
|
});
|
|
72276
73938
|
topBarToolStore;
|
|
72277
73939
|
viewStore;
|
|
72278
|
-
borderEditorButtonRef =
|
|
73940
|
+
borderEditorButtonRef = _odoo_owl.signal.ref();
|
|
72279
73941
|
state = (0, _odoo_owl.proxy)({
|
|
72280
73942
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
72281
73943
|
currentStyle: DEFAULT_BORDER_DESC.style,
|
|
@@ -72505,7 +74167,7 @@ set(value) {
|
|
|
72505
74167
|
childClass: types.string()
|
|
72506
74168
|
});
|
|
72507
74169
|
topBarToolStore;
|
|
72508
|
-
actionRef =
|
|
74170
|
+
actionRef = _odoo_owl.signal.ref();
|
|
72509
74171
|
setup() {
|
|
72510
74172
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72511
74173
|
}
|
|
@@ -72568,7 +74230,7 @@ set(value) {
|
|
|
72568
74230
|
props = (0, _odoo_owl.useProps)({ class: types.string() });
|
|
72569
74231
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
72570
74232
|
topBarToolStore;
|
|
72571
|
-
buttonRef =
|
|
74233
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
72572
74234
|
state = (0, _odoo_owl.proxy)({
|
|
72573
74235
|
anchorRect: {
|
|
72574
74236
|
x: 0,
|
|
@@ -72857,12 +74519,12 @@ set(value) {
|
|
|
72857
74519
|
composerFocusStore;
|
|
72858
74520
|
fingerprints;
|
|
72859
74521
|
topBarToolStore;
|
|
72860
|
-
toolBarContainerRef =
|
|
72861
|
-
toolbarRef =
|
|
72862
|
-
namedRangesRef =
|
|
72863
|
-
topBarTopRef =
|
|
72864
|
-
moreToolsContainerRef =
|
|
72865
|
-
moreToolsButtonRef =
|
|
74522
|
+
toolBarContainerRef = _odoo_owl.signal.ref();
|
|
74523
|
+
toolbarRef = _odoo_owl.signal.ref();
|
|
74524
|
+
namedRangesRef = _odoo_owl.signal.ref();
|
|
74525
|
+
topBarTopRef = _odoo_owl.signal.ref();
|
|
74526
|
+
moreToolsContainerRef = _odoo_owl.signal.ref();
|
|
74527
|
+
moreToolsButtonRef = _odoo_owl.signal.ref();
|
|
72866
74528
|
spreadsheetRect = useSpreadsheetRect();
|
|
72867
74529
|
setup() {
|
|
72868
74530
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -73124,7 +74786,7 @@ set(value) {
|
|
|
73124
74786
|
SpreadsheetPrint
|
|
73125
74787
|
};
|
|
73126
74788
|
sidePanel;
|
|
73127
|
-
spreadsheetRef =
|
|
74789
|
+
spreadsheetRef = _odoo_owl.signal.ref();
|
|
73128
74790
|
spreadsheetRect = useSpreadsheetRect();
|
|
73129
74791
|
state = (0, _odoo_owl.proxy)({ printModeEnabled: false });
|
|
73130
74792
|
_focusGrid;
|
|
@@ -79132,12 +80794,13 @@ set(value) {
|
|
|
79132
80794
|
}
|
|
79133
80795
|
sortFilterZone(sortDirection) {
|
|
79134
80796
|
const filterPosition = this.props.filterPosition;
|
|
79135
|
-
const
|
|
80797
|
+
const table = this.table;
|
|
80798
|
+
const tableZone = table?.range.zone;
|
|
79136
80799
|
if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
|
|
79137
80800
|
const sheetId = this.viewStore.displayedSheetId;
|
|
79138
80801
|
const contentZone = {
|
|
79139
80802
|
...tableZone,
|
|
79140
|
-
top: tableZone.top +
|
|
80803
|
+
top: tableZone.top + table.config.numberOfHeaders
|
|
79141
80804
|
};
|
|
79142
80805
|
const sortAnchor = {
|
|
79143
80806
|
col: filterPosition.col,
|
|
@@ -79295,9 +80958,9 @@ set(value) {
|
|
|
79295
80958
|
onClosed: types.function().optional()
|
|
79296
80959
|
});
|
|
79297
80960
|
static size = { maxHeight: 500 };
|
|
79298
|
-
urlInput =
|
|
79299
|
-
suggestionListRef =
|
|
79300
|
-
urlInputContainer =
|
|
80961
|
+
urlInput = _odoo_owl.signal.ref(HTMLInputElement);
|
|
80962
|
+
suggestionListRef = _odoo_owl.signal.ref();
|
|
80963
|
+
urlInputContainer = _odoo_owl.signal.ref();
|
|
79301
80964
|
state;
|
|
79302
80965
|
viewStore;
|
|
79303
80966
|
setup() {
|
|
@@ -81415,7 +83078,7 @@ set(value) {
|
|
|
81415
83078
|
else if (_format === 0) {
|
|
81416
83079
|
const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
|
|
81417
83080
|
return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
|
|
81418
|
-
return isEvaluationError(value.value) ? value.value :
|
|
83081
|
+
return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
|
|
81419
83082
|
})).join(rowSeparator) };
|
|
81420
83083
|
} else return new EvaluationError(_t("Format must be 0 or 1"));
|
|
81421
83084
|
},
|
|
@@ -87463,7 +89126,7 @@ set(value) {
|
|
|
87463
89126
|
description: _t("Concatenation of two values."),
|
|
87464
89127
|
args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
|
|
87465
89128
|
compute: function(value1, value2) {
|
|
87466
|
-
return { value:
|
|
89129
|
+
return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
|
|
87467
89130
|
},
|
|
87468
89131
|
isExported: true
|
|
87469
89132
|
};
|
|
@@ -88346,7 +90009,7 @@ set(value) {
|
|
|
88346
90009
|
description: _t("Remove non-printable characters from a piece of text."),
|
|
88347
90010
|
args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
|
|
88348
90011
|
compute: function(text) {
|
|
88349
|
-
const _text =
|
|
90012
|
+
const _text = toLocaleString(text, this.locale);
|
|
88350
90013
|
let cleanedStr = "";
|
|
88351
90014
|
for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
|
|
88352
90015
|
return { value: cleanedStr };
|
|
@@ -88357,7 +90020,7 @@ set(value) {
|
|
|
88357
90020
|
description: _t("Appends strings to one another."),
|
|
88358
90021
|
args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
|
|
88359
90022
|
compute: function(...datas) {
|
|
88360
|
-
return { value: reduceAny(datas, (acc, a) => acc +
|
|
90023
|
+
return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
|
|
88361
90024
|
},
|
|
88362
90025
|
isExported: true
|
|
88363
90026
|
};
|
|
@@ -88365,7 +90028,7 @@ set(value) {
|
|
|
88365
90028
|
description: _t("Tests whether two strings are identical."),
|
|
88366
90029
|
args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
|
|
88367
90030
|
compute: function(string1, string2) {
|
|
88368
|
-
return { value:
|
|
90031
|
+
return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
|
|
88369
90032
|
},
|
|
88370
90033
|
isExported: true
|
|
88371
90034
|
};
|
|
@@ -88377,8 +90040,8 @@ set(value) {
|
|
|
88377
90040
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88378
90041
|
],
|
|
88379
90042
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88380
|
-
const _searchFor =
|
|
88381
|
-
const _textToSearch =
|
|
90043
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
90044
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
88382
90045
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88383
90046
|
if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
88384
90047
|
if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
@@ -88415,8 +90078,8 @@ set(value) {
|
|
|
88415
90078
|
description: _t("Concatenates elements of arrays with delimiter."),
|
|
88416
90079
|
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."))],
|
|
88417
90080
|
compute: function(delimiter, ...valuesOrArrays) {
|
|
88418
|
-
const _delimiter =
|
|
88419
|
-
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") +
|
|
90081
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
90082
|
+
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "") };
|
|
88420
90083
|
}
|
|
88421
90084
|
};
|
|
88422
90085
|
const LEFT = {
|
|
@@ -88425,7 +90088,7 @@ set(value) {
|
|
|
88425
90088
|
compute: function(text, ...args) {
|
|
88426
90089
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88427
90090
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
88428
|
-
return { value:
|
|
90091
|
+
return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
|
|
88429
90092
|
},
|
|
88430
90093
|
isExported: true
|
|
88431
90094
|
};
|
|
@@ -88433,7 +90096,7 @@ set(value) {
|
|
|
88433
90096
|
description: _t("Length of a string."),
|
|
88434
90097
|
args: [arg("text (string)", _t("The string whose length will be returned."))],
|
|
88435
90098
|
compute: function(text) {
|
|
88436
|
-
return { value:
|
|
90099
|
+
return { value: toLocaleString(text, this.locale).length };
|
|
88437
90100
|
},
|
|
88438
90101
|
isExported: true
|
|
88439
90102
|
};
|
|
@@ -88441,7 +90104,7 @@ set(value) {
|
|
|
88441
90104
|
description: _t("Converts a specified string to lowercase."),
|
|
88442
90105
|
args: [arg("text (string)", _t("The string to convert to lowercase."))],
|
|
88443
90106
|
compute: function(text) {
|
|
88444
|
-
return { value:
|
|
90107
|
+
return { value: toLocaleString(text, this.locale).toLowerCase() };
|
|
88445
90108
|
},
|
|
88446
90109
|
isExported: true
|
|
88447
90110
|
};
|
|
@@ -88453,7 +90116,7 @@ set(value) {
|
|
|
88453
90116
|
arg("extract_length (number)", _t("The length of the segment to extract."))
|
|
88454
90117
|
],
|
|
88455
90118
|
compute: function(text, starting_at, extract_length) {
|
|
88456
|
-
const _text =
|
|
90119
|
+
const _text = toLocaleString(text, this.locale);
|
|
88457
90120
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
88458
90121
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
88459
90122
|
if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
@@ -88466,7 +90129,7 @@ set(value) {
|
|
|
88466
90129
|
description: _t("Capitalizes each word in a specified string."),
|
|
88467
90130
|
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."))],
|
|
88468
90131
|
compute: function(text) {
|
|
88469
|
-
return { value:
|
|
90132
|
+
return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
|
|
88470
90133
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
88471
90134
|
}) };
|
|
88472
90135
|
},
|
|
@@ -88616,8 +90279,8 @@ set(value) {
|
|
|
88616
90279
|
if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
88617
90280
|
const _length = toNumber(length, this.locale);
|
|
88618
90281
|
if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
|
|
88619
|
-
const _text =
|
|
88620
|
-
const _newText =
|
|
90282
|
+
const _text = toLocaleString(text, this.locale);
|
|
90283
|
+
const _newText = toLocaleString(newText, this.locale);
|
|
88621
90284
|
return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
|
|
88622
90285
|
},
|
|
88623
90286
|
isExported: true
|
|
@@ -88628,7 +90291,7 @@ set(value) {
|
|
|
88628
90291
|
compute: function(text, ...args) {
|
|
88629
90292
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88630
90293
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
|
|
88631
|
-
const _text =
|
|
90294
|
+
const _text = toLocaleString(text, this.locale);
|
|
88632
90295
|
const stringLength = _text.length;
|
|
88633
90296
|
return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
|
|
88634
90297
|
},
|
|
@@ -88642,8 +90305,8 @@ set(value) {
|
|
|
88642
90305
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88643
90306
|
],
|
|
88644
90307
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88645
|
-
const _searchFor =
|
|
88646
|
-
const _textToSearch =
|
|
90308
|
+
const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
|
|
90309
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
|
|
88647
90310
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88648
90311
|
if (_textToSearch === "") return {
|
|
88649
90312
|
value: CellErrorType.GenericError,
|
|
@@ -88673,8 +90336,8 @@ set(value) {
|
|
|
88673
90336
|
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."))
|
|
88674
90337
|
],
|
|
88675
90338
|
computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
|
|
88676
|
-
const _text =
|
|
88677
|
-
const _delimiter = escapeRegExp(
|
|
90339
|
+
const _text = toLocaleString(text, this.locale);
|
|
90340
|
+
const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
|
|
88678
90341
|
const _splitByEach = toBoolean(splitByEach);
|
|
88679
90342
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
88680
90343
|
if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
@@ -88696,10 +90359,10 @@ set(value) {
|
|
|
88696
90359
|
compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
88697
90360
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
88698
90361
|
if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
88699
|
-
const _textToSearch =
|
|
88700
|
-
const _searchFor =
|
|
90362
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
90363
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
88701
90364
|
if (_searchFor === "") return { value: _textToSearch };
|
|
88702
|
-
const _replaceWith =
|
|
90365
|
+
const _replaceWith = toLocaleString(replaceWith, this.locale);
|
|
88703
90366
|
const reg = new RegExp(escapeRegExp(_searchFor), "g");
|
|
88704
90367
|
if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
|
|
88705
90368
|
let n = 0;
|
|
@@ -88722,10 +90385,10 @@ set(value) {
|
|
|
88722
90385
|
arg("texts (string, range<string>, repeating)", _t("Text item to join."))
|
|
88723
90386
|
],
|
|
88724
90387
|
compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
|
|
88725
|
-
const _delimiter =
|
|
90388
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
88726
90389
|
const _ignoreEmpty = toBoolean(ignoreEmpty);
|
|
88727
90390
|
let n = 0;
|
|
88728
|
-
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty &&
|
|
90391
|
+
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "") };
|
|
88729
90392
|
},
|
|
88730
90393
|
isExported: true
|
|
88731
90394
|
};
|
|
@@ -88778,7 +90441,7 @@ set(value) {
|
|
|
88778
90441
|
description: _t("Removes space characters."),
|
|
88779
90442
|
args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
|
|
88780
90443
|
compute: function(text) {
|
|
88781
|
-
return { value: trimContent(
|
|
90444
|
+
return { value: trimContent(toLocaleString(text, this.locale)) };
|
|
88782
90445
|
},
|
|
88783
90446
|
isExported: true
|
|
88784
90447
|
};
|
|
@@ -88786,7 +90449,7 @@ set(value) {
|
|
|
88786
90449
|
description: _t("Converts a specified string to uppercase."),
|
|
88787
90450
|
args: [arg("text (string)", _t("The string to convert to uppercase."))],
|
|
88788
90451
|
compute: function(text) {
|
|
88789
|
-
return { value:
|
|
90452
|
+
return { value: toLocaleString(text, this.locale).toUpperCase() };
|
|
88790
90453
|
},
|
|
88791
90454
|
isExported: true
|
|
88792
90455
|
};
|
|
@@ -88883,7 +90546,7 @@ set(value) {
|
|
|
88883
90546
|
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."))],
|
|
88884
90547
|
compute: function(url, linkLabel) {
|
|
88885
90548
|
const processedUrl = toString(url).trim();
|
|
88886
|
-
const processedLabel =
|
|
90549
|
+
const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
|
|
88887
90550
|
if (processedUrl === "") return { value: processedLabel };
|
|
88888
90551
|
return { value: markdownLink(processedLabel, processedUrl) };
|
|
88889
90552
|
},
|
|
@@ -89030,7 +90693,8 @@ set(value) {
|
|
|
89030
90693
|
chartJsExtensionRegistry,
|
|
89031
90694
|
onIterationEndEvaluationRegistry,
|
|
89032
90695
|
specificRangeTransformRegistry,
|
|
89033
|
-
unusedDataSourceRegistry
|
|
90696
|
+
unusedDataSourceRegistry,
|
|
90697
|
+
NonSquishableFunctionRegistry
|
|
89034
90698
|
};
|
|
89035
90699
|
const helpers = {
|
|
89036
90700
|
arg,
|
|
@@ -89112,7 +90776,9 @@ set(value) {
|
|
|
89112
90776
|
computeCachedTextDimension,
|
|
89113
90777
|
createComputeFunction,
|
|
89114
90778
|
isMobileOS,
|
|
89115
|
-
drawHighlight
|
|
90779
|
+
drawHighlight,
|
|
90780
|
+
getBoundingRectWithMargins,
|
|
90781
|
+
hasInteractiveElementInEventTree
|
|
89116
90782
|
};
|
|
89117
90783
|
const links = {
|
|
89118
90784
|
isMarkdownLink,
|
|
@@ -89344,8 +91010,8 @@ exports.stores = stores;
|
|
|
89344
91010
|
exports.tokenColors = tokenColors;
|
|
89345
91011
|
exports.tokenize = tokenize;
|
|
89346
91012
|
|
|
89347
|
-
__info__.version = "19.5.0-alpha.
|
|
89348
|
-
__info__.date = "2026-08-
|
|
89349
|
-
__info__.hash = "
|
|
91013
|
+
__info__.version = "19.5.0-alpha.12";
|
|
91014
|
+
__info__.date = "2026-08-19T09:47:03.774Z";
|
|
91015
|
+
__info__.hash = "68ca68e";
|
|
89350
91016
|
|
|
89351
91017
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|