@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
package/dist/o_spreadsheet.cjs
CHANGED
|
@@ -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
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
@@ -207,6 +207,16 @@ function getBoundingRectAsPOJO(el) {
|
|
|
207
207
|
height: rect.height
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
|
+
function getBoundingRectWithMargins(el) {
|
|
211
|
+
const style = getComputedStyle(el);
|
|
212
|
+
const rect = el.getBoundingClientRect();
|
|
213
|
+
return {
|
|
214
|
+
x: rect.x,
|
|
215
|
+
y: rect.y,
|
|
216
|
+
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
217
|
+
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
218
|
+
};
|
|
219
|
+
}
|
|
210
220
|
/**
|
|
211
221
|
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
212
222
|
*/
|
|
@@ -650,6 +660,14 @@ const DEFAULT_CAROUSEL_TITLE_STYLE = {
|
|
|
650
660
|
fontSize: 18,
|
|
651
661
|
color: TEXT_BODY
|
|
652
662
|
};
|
|
663
|
+
const CAROUSEL_LAYOUT = {
|
|
664
|
+
paddingX: 24,
|
|
665
|
+
paddingY: 8,
|
|
666
|
+
headerPaddingTop: 4,
|
|
667
|
+
separatorWidth: 1,
|
|
668
|
+
headerLineHeight: 1.5,
|
|
669
|
+
minHeaderHeight: 25
|
|
670
|
+
};
|
|
653
671
|
const DEFAULT_TOKEN_COLOR = "light-dark(#000000, #ffffff)";
|
|
654
672
|
const functionColor = DEFAULT_TOKEN_COLOR;
|
|
655
673
|
const operatorColor = "#3da4ab";
|
|
@@ -2634,7 +2652,7 @@ function unregisterChartJsExtensions() {
|
|
|
2634
2652
|
*
|
|
2635
2653
|
* The end goal is to eliminate all compatibility shims.
|
|
2636
2654
|
*/
|
|
2637
|
-
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$
|
|
2655
|
+
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;
|
|
2638
2656
|
const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
|
|
2639
2657
|
var _Component = class extends OwlComponent {
|
|
2640
2658
|
static template = "";
|
|
@@ -2655,8 +2673,8 @@ function _useComponent() {
|
|
|
2655
2673
|
function _useExternalListener(target, eventName, handler, eventParams) {
|
|
2656
2674
|
const node = useScope();
|
|
2657
2675
|
const boundHandler = handler.bind(node.component);
|
|
2658
|
-
onMounted$
|
|
2659
|
-
onWillUnmount$
|
|
2676
|
+
onMounted$26(() => target.addEventListener(eventName, boundHandler, eventParams));
|
|
2677
|
+
onWillUnmount$20(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
|
2660
2678
|
}
|
|
2661
2679
|
function onWillRender(cb) {
|
|
2662
2680
|
const node = useScope();
|
|
@@ -2671,7 +2689,7 @@ function _useLayoutEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
2671
2689
|
let cleanup;
|
|
2672
2690
|
/** @type {any[]} */
|
|
2673
2691
|
let dependencies;
|
|
2674
|
-
onMounted$
|
|
2692
|
+
onMounted$26(() => {
|
|
2675
2693
|
dependencies = computeDependencies();
|
|
2676
2694
|
cleanup = effect(...dependencies);
|
|
2677
2695
|
});
|
|
@@ -2688,7 +2706,7 @@ function _useLayoutEffect(effect, computeDependencies = () => [NaN]) {
|
|
|
2688
2706
|
cleanup = effect(...dependencies);
|
|
2689
2707
|
}
|
|
2690
2708
|
});
|
|
2691
|
-
onWillUnmount$
|
|
2709
|
+
onWillUnmount$20(() => cleanup && cleanup());
|
|
2692
2710
|
}
|
|
2693
2711
|
var _EnvPlugin = class extends Plugin {
|
|
2694
2712
|
static id = "__ENV__";
|
|
@@ -2761,7 +2779,7 @@ var VPortal = class extends blockDom$1.text("").constructor {
|
|
|
2761
2779
|
}
|
|
2762
2780
|
};
|
|
2763
2781
|
var Portal = class extends OwlComponent {
|
|
2764
|
-
static template = xml$
|
|
2782
|
+
static template = xml$5`<t t-call-slot="default"/>`;
|
|
2765
2783
|
constructor(node) {
|
|
2766
2784
|
super(node);
|
|
2767
2785
|
this.props = props();
|
|
@@ -2771,7 +2789,7 @@ var Portal = class extends OwlComponent {
|
|
|
2771
2789
|
const node = this.__owl__;
|
|
2772
2790
|
const renderContent = node.renderFn;
|
|
2773
2791
|
node.renderFn = (...args) => new VPortal(node.props.selector, renderContent(...args));
|
|
2774
|
-
onMounted$
|
|
2792
|
+
onMounted$26(() => {
|
|
2775
2793
|
const portal = node.bdom;
|
|
2776
2794
|
if (!portal.target) {
|
|
2777
2795
|
const target = document.querySelector(node.props.selector);
|
|
@@ -2779,7 +2797,7 @@ var Portal = class extends OwlComponent {
|
|
|
2779
2797
|
else throw new Error("invalid portal target");
|
|
2780
2798
|
}
|
|
2781
2799
|
});
|
|
2782
|
-
onWillUnmount$
|
|
2800
|
+
onWillUnmount$20(() => {
|
|
2783
2801
|
node.bdom.remove();
|
|
2784
2802
|
});
|
|
2785
2803
|
}
|
|
@@ -4072,6 +4090,18 @@ function toString(data) {
|
|
|
4072
4090
|
default: return "";
|
|
4073
4091
|
}
|
|
4074
4092
|
}
|
|
4093
|
+
/**
|
|
4094
|
+
* Only converts the decimal separator, ignore number format such as % or dates
|
|
4095
|
+
*/
|
|
4096
|
+
function toLocaleString(data, locale) {
|
|
4097
|
+
const value = toValue(data);
|
|
4098
|
+
switch (typeof value) {
|
|
4099
|
+
case "string": return value;
|
|
4100
|
+
case "number": return value.toString().replace(".", locale.decimalSeparator);
|
|
4101
|
+
case "boolean": return value ? "TRUE" : "FALSE";
|
|
4102
|
+
default: return "";
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4075
4105
|
/** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
|
|
4076
4106
|
const normalizeString = memoize(function normalizeString(str) {
|
|
4077
4107
|
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -5986,6 +6016,9 @@ var FunctionRegistry = class extends Registry {
|
|
|
5986
6016
|
}
|
|
5987
6017
|
};
|
|
5988
6018
|
const functionRegistry = new FunctionRegistry();
|
|
6019
|
+
/** Formulas using one of these functions are never squished: they are always exported
|
|
6020
|
+
* as a full formula string, and they don't become the base formula of the next cells. */
|
|
6021
|
+
const NonSquishableFunctionRegistry = new Registry();
|
|
5989
6022
|
|
|
5990
6023
|
//#endregion
|
|
5991
6024
|
//#region src/helpers/references.ts
|
|
@@ -9245,8 +9278,8 @@ function computeCachedTextDimension(context, text, font) {
|
|
|
9245
9278
|
function fontSizeInPixels(fontSize) {
|
|
9246
9279
|
return Math.round(fontSize * 96 / 72);
|
|
9247
9280
|
}
|
|
9248
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
9249
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" :
|
|
9281
|
+
function computeTextFont(style, fontUnit = "pt", fontWeight = 400) {
|
|
9282
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : fontWeight} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9250
9283
|
}
|
|
9251
9284
|
function computeTextFontSizeInPixels(style) {
|
|
9252
9285
|
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
@@ -11880,7 +11913,7 @@ var ChartJsComponent = class extends Component {
|
|
|
11880
11913
|
chartId: types.string(),
|
|
11881
11914
|
isFullScreen: types.boolean().optional()
|
|
11882
11915
|
});
|
|
11883
|
-
canvas =
|
|
11916
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
11884
11917
|
chart;
|
|
11885
11918
|
currentRuntime;
|
|
11886
11919
|
animationStore;
|
|
@@ -12603,7 +12636,7 @@ var ScorecardChart = class extends Component {
|
|
|
12603
12636
|
chartId: types.string(),
|
|
12604
12637
|
isFullScreen: types.boolean().optional()
|
|
12605
12638
|
});
|
|
12606
|
-
canvas =
|
|
12639
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
12607
12640
|
zoomStore;
|
|
12608
12641
|
get runtime() {
|
|
12609
12642
|
return this.env.model.getters.getChartRuntime(this.props.chartId);
|
|
@@ -13988,6 +14021,44 @@ function getCarouselItemTitle(getters, item) {
|
|
|
13988
14021
|
const definition = getters.getChartDefinition(item.chartId);
|
|
13989
14022
|
return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
|
|
13990
14023
|
}
|
|
14024
|
+
function getCarouselLayout(figureRect, carousel, selectedItem) {
|
|
14025
|
+
const layout = CAROUSEL_LAYOUT;
|
|
14026
|
+
const title = {
|
|
14027
|
+
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
14028
|
+
...carousel.title
|
|
14029
|
+
};
|
|
14030
|
+
const x = figureRect.x + layout.paddingX;
|
|
14031
|
+
const width = Math.max(0, figureRect.width - 2 * layout.paddingX);
|
|
14032
|
+
const titleHeight = title.text ? title.fontSize * layout.headerLineHeight : 0;
|
|
14033
|
+
const headerRect = {
|
|
14034
|
+
x,
|
|
14035
|
+
y: figureRect.y + layout.paddingY + layout.headerPaddingTop,
|
|
14036
|
+
width,
|
|
14037
|
+
height: Math.max(layout.minHeaderHeight, titleHeight)
|
|
14038
|
+
};
|
|
14039
|
+
const headerBottom = headerRect.y + headerRect.height;
|
|
14040
|
+
const separatorRect = title.text ? {
|
|
14041
|
+
x,
|
|
14042
|
+
y: headerBottom + layout.paddingY,
|
|
14043
|
+
width,
|
|
14044
|
+
height: layout.separatorWidth
|
|
14045
|
+
} : void 0;
|
|
14046
|
+
const contentPaddingTop = selectedItem?.type === "carouselDataView" ? layout.paddingY : 0;
|
|
14047
|
+
const rectAboveContent = separatorRect || headerRect;
|
|
14048
|
+
const contentY = rectAboveContent.y + rectAboveContent.height + contentPaddingTop;
|
|
14049
|
+
const contentBottom = figureRect.y + figureRect.height - layout.paddingY;
|
|
14050
|
+
return {
|
|
14051
|
+
headerRect,
|
|
14052
|
+
separatorRect,
|
|
14053
|
+
contentRect: {
|
|
14054
|
+
x,
|
|
14055
|
+
y: contentY,
|
|
14056
|
+
width,
|
|
14057
|
+
height: Math.max(0, contentBottom - contentY)
|
|
14058
|
+
},
|
|
14059
|
+
headerLineHeight: layout.headerLineHeight
|
|
14060
|
+
};
|
|
14061
|
+
}
|
|
13991
14062
|
|
|
13992
14063
|
//#endregion
|
|
13993
14064
|
//#region src/helpers/figures/charts/gauge_chart_rendering.ts
|
|
@@ -13999,7 +14070,7 @@ const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
|
13999
14070
|
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
14000
14071
|
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
14001
14072
|
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
14002
|
-
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
14073
|
+
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
|
|
14003
14074
|
const size = dimensions ?? getCanvasSize(canvas);
|
|
14004
14075
|
const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
14005
14076
|
canvas.width = size.width * dpr;
|
|
@@ -14012,7 +14083,7 @@ function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
|
14012
14083
|
height: size.height / zoom,
|
|
14013
14084
|
x: 0,
|
|
14014
14085
|
y: 0
|
|
14015
|
-
}, runtime, ctx);
|
|
14086
|
+
}, runtime, ctx, options);
|
|
14016
14087
|
drawBackground(ctx, config);
|
|
14017
14088
|
drawGauge(ctx, config);
|
|
14018
14089
|
drawInflectionValues(ctx, config);
|
|
@@ -14100,7 +14171,8 @@ function drawTitle(ctx, config) {
|
|
|
14100
14171
|
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
14101
14172
|
ctx.restore();
|
|
14102
14173
|
}
|
|
14103
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
14174
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
|
|
14175
|
+
const labelFontSize = options?.labelFontSize ?? 12;
|
|
14104
14176
|
const maxValue = runtime.maxValue;
|
|
14105
14177
|
const minValue = runtime.minValue;
|
|
14106
14178
|
const gaugeValue = getGaugeValue(runtime, "animated");
|
|
@@ -14118,14 +14190,14 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14118
14190
|
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
14119
14191
|
const minLabelPosition = {
|
|
14120
14192
|
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
14121
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14193
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14122
14194
|
};
|
|
14123
14195
|
const maxLabelPosition = {
|
|
14124
14196
|
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
14125
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14197
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14126
14198
|
};
|
|
14127
14199
|
const textColor = chartMutedFontColor(runtime.background);
|
|
14128
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
14200
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
|
|
14129
14201
|
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
14130
14202
|
if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
|
|
14131
14203
|
fontSize: 16,
|
|
@@ -14173,13 +14245,13 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14173
14245
|
minLabel: {
|
|
14174
14246
|
label: runtime.minValue.label,
|
|
14175
14247
|
textPosition: minLabelPosition,
|
|
14176
|
-
fontSize:
|
|
14248
|
+
fontSize: labelFontSize,
|
|
14177
14249
|
color: textColor
|
|
14178
14250
|
},
|
|
14179
14251
|
maxLabel: {
|
|
14180
14252
|
label: runtime.maxValue.label,
|
|
14181
14253
|
textPosition: maxLabelPosition,
|
|
14182
|
-
fontSize:
|
|
14254
|
+
fontSize: labelFontSize,
|
|
14183
14255
|
color: textColor
|
|
14184
14256
|
}
|
|
14185
14257
|
};
|
|
@@ -14213,27 +14285,27 @@ function getGaugeRect(boundingRect, title) {
|
|
|
14213
14285
|
*
|
|
14214
14286
|
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
14215
14287
|
*/
|
|
14216
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
14288
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
|
|
14217
14289
|
const maxValue = runtime.maxValue;
|
|
14218
14290
|
const minValue = runtime.minValue;
|
|
14219
14291
|
const gaugeCircleCenter = {
|
|
14220
14292
|
x: gaugeRect.x + gaugeRect.width / 2,
|
|
14221
14293
|
y: gaugeRect.y + gaugeRect.height
|
|
14222
14294
|
};
|
|
14223
|
-
const textStyle = { fontSize:
|
|
14295
|
+
const textStyle = { fontSize: labelFontSize };
|
|
14224
14296
|
const inflectionValues = [];
|
|
14225
14297
|
const inflectionValuesTextRects = [];
|
|
14226
14298
|
for (const inflectionValue of runtime.inflectionValues) {
|
|
14227
14299
|
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
14228
14300
|
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
14229
14301
|
const angle = Math.PI - Math.PI * percentage;
|
|
14230
|
-
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2,
|
|
14231
|
-
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ?
|
|
14302
|
+
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2, labelFontSize);
|
|
14303
|
+
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ? labelFontSize : 0;
|
|
14232
14304
|
inflectionValuesTextRects.push(textRect);
|
|
14233
14305
|
inflectionValues.push({
|
|
14234
14306
|
rotation: angle,
|
|
14235
14307
|
label: inflectionValue.label,
|
|
14236
|
-
fontSize:
|
|
14308
|
+
fontSize: labelFontSize,
|
|
14237
14309
|
color: textColor,
|
|
14238
14310
|
offset
|
|
14239
14311
|
});
|
|
@@ -14353,6 +14425,44 @@ const CHART_COMMON_OPTIONS = {
|
|
|
14353
14425
|
"mouseup"
|
|
14354
14426
|
]
|
|
14355
14427
|
};
|
|
14428
|
+
/**
|
|
14429
|
+
* Sample down a Chart.js configuration's data points to at most `maxPoints`, evenly spaced.
|
|
14430
|
+
* Used for lightweight previews (drag preview, chart suggestions) where rendering every point
|
|
14431
|
+
* of a large dataset would make Chart.js slow without any visible benefit on a small canvas.
|
|
14432
|
+
*/
|
|
14433
|
+
function limitChartConfigDataPoints(config, maxPoints = 150) {
|
|
14434
|
+
const data = config.data;
|
|
14435
|
+
if (!data) return config;
|
|
14436
|
+
return {
|
|
14437
|
+
...config,
|
|
14438
|
+
data: {
|
|
14439
|
+
...data,
|
|
14440
|
+
labels: limitArrayDataPoints(data.labels, maxPoints),
|
|
14441
|
+
datasets: data.datasets?.map((dataset) => limitDatasetDataPoints(dataset, maxPoints))
|
|
14442
|
+
}
|
|
14443
|
+
};
|
|
14444
|
+
}
|
|
14445
|
+
function limitDatasetDataPoints(dataset, maxPoints) {
|
|
14446
|
+
const values = dataset.data;
|
|
14447
|
+
if (!Array.isArray(values) || values.length <= maxPoints) return dataset;
|
|
14448
|
+
const length = values.length;
|
|
14449
|
+
const indices = sampleIndices(length, maxPoints);
|
|
14450
|
+
const limited = { ...dataset };
|
|
14451
|
+
for (const key of Object.keys(limited)) {
|
|
14452
|
+
const value = limited[key];
|
|
14453
|
+
if (Array.isArray(value) && value.length === length) limited[key] = indices.map((i) => value[i]);
|
|
14454
|
+
}
|
|
14455
|
+
return limited;
|
|
14456
|
+
}
|
|
14457
|
+
function limitArrayDataPoints(arr, maxPoints) {
|
|
14458
|
+
if (!Array.isArray(arr) || arr.length <= maxPoints) return arr;
|
|
14459
|
+
return sampleIndices(arr.length, maxPoints).map((i) => arr[i]);
|
|
14460
|
+
}
|
|
14461
|
+
/** Evenly spaced indices sampled from [0, length). */
|
|
14462
|
+
function sampleIndices(length, maxPoints) {
|
|
14463
|
+
const step = length / maxPoints;
|
|
14464
|
+
return Array.from({ length: maxPoints }, (_, i) => Math.min(length - 1, Math.floor(i * step)));
|
|
14465
|
+
}
|
|
14356
14466
|
async function chartToImageUrl(runtime, figure, type) {
|
|
14357
14467
|
try {
|
|
14358
14468
|
const canvas = createRenderingSurface(figure.width, figure.height);
|
|
@@ -14400,7 +14510,7 @@ async function canvasToObjectUrl(canvas) {
|
|
|
14400
14510
|
*
|
|
14401
14511
|
* @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
|
|
14402
14512
|
*/
|
|
14403
|
-
function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
14513
|
+
function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
|
|
14404
14514
|
if ("chartJsConfig" in runtime) {
|
|
14405
14515
|
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
14406
14516
|
const extensionsLoaded = areChartJSExtensionsLoaded();
|
|
@@ -14416,8 +14526,8 @@ function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
|
14416
14526
|
chart.destroy();
|
|
14417
14527
|
if (!extensionsLoaded) unregisterChartJsExtensions();
|
|
14418
14528
|
};
|
|
14419
|
-
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
|
|
14420
|
-
else if (type === "gauge") drawGaugeChart(canvas, runtime,
|
|
14529
|
+
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas, zoom);
|
|
14530
|
+
else if (type === "gauge") drawGaugeChart(canvas, runtime, zoom, size);
|
|
14421
14531
|
return () => {};
|
|
14422
14532
|
}
|
|
14423
14533
|
|
|
@@ -14955,7 +15065,7 @@ var ZoomableChartStore = class extends SpreadsheetStore {
|
|
|
14955
15065
|
var ZoomableChartJsComponent = class extends ChartJsComponent {
|
|
14956
15066
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
14957
15067
|
store;
|
|
14958
|
-
masterChartCanvas =
|
|
15068
|
+
masterChartCanvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
14959
15069
|
masterChart;
|
|
14960
15070
|
mode;
|
|
14961
15071
|
hasLinearScale;
|
|
@@ -15709,7 +15819,7 @@ var GaugeChartComponent = class extends Component {
|
|
|
15709
15819
|
chartId: types.string(),
|
|
15710
15820
|
isFullScreen: types.boolean().optional()
|
|
15711
15821
|
});
|
|
15712
|
-
canvas =
|
|
15822
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
15713
15823
|
animationStore;
|
|
15714
15824
|
zoomStore;
|
|
15715
15825
|
get runtime() {
|
|
@@ -15946,7 +16056,7 @@ var Menu = class extends Component {
|
|
|
15946
16056
|
onKeyDown: types.function().optional(),
|
|
15947
16057
|
disableKeyboardNavigation: types.boolean().optional()
|
|
15948
16058
|
});
|
|
15949
|
-
menuRef =
|
|
16059
|
+
menuRef = _odoo_owl.signal.ref();
|
|
15950
16060
|
setup() {
|
|
15951
16061
|
useLayoutEffect(() => {
|
|
15952
16062
|
const menuEl = this.menuRef();
|
|
@@ -16066,8 +16176,8 @@ var Popover = class extends Component {
|
|
|
16066
16176
|
zIndex: types.number().optional(),
|
|
16067
16177
|
class: types.string().optional()
|
|
16068
16178
|
});
|
|
16069
|
-
popoverRef =
|
|
16070
|
-
popoverContentRef =
|
|
16179
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
16180
|
+
popoverContentRef = _odoo_owl.signal.ref();
|
|
16071
16181
|
currentPosition = void 0;
|
|
16072
16182
|
currentDisplayValue = void 0;
|
|
16073
16183
|
spreadsheetRect = useSpreadsheetRect();
|
|
@@ -16255,7 +16365,7 @@ var MenuPopover = class MenuPopover extends Component {
|
|
|
16255
16365
|
isHoveringChild: false
|
|
16256
16366
|
});
|
|
16257
16367
|
state = (0, _odoo_owl.proxy)({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
|
|
16258
|
-
menuRef =
|
|
16368
|
+
menuRef = _odoo_owl.signal.ref();
|
|
16259
16369
|
openingTimeOut = useTimeOut();
|
|
16260
16370
|
setup() {
|
|
16261
16371
|
const domFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -16544,43 +16654,29 @@ var PositionMap = class {
|
|
|
16544
16654
|
};
|
|
16545
16655
|
|
|
16546
16656
|
//#endregion
|
|
16547
|
-
//#region src/
|
|
16548
|
-
var
|
|
16549
|
-
mutators = [
|
|
16550
|
-
|
|
16657
|
+
//#region src/stores/cell_hover_overlay_store.ts
|
|
16658
|
+
var CellHoverOverlayStore = class extends SpreadsheetStore {
|
|
16659
|
+
mutators = [
|
|
16660
|
+
"hover",
|
|
16661
|
+
"register",
|
|
16662
|
+
"unRegister"
|
|
16663
|
+
];
|
|
16664
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
16665
|
+
providers = [];
|
|
16551
16666
|
overlayColors = new PositionMap();
|
|
16552
|
-
hover(
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
this.
|
|
16667
|
+
hover(hoveredPosition) {
|
|
16668
|
+
this.overlayColors = new PositionMap();
|
|
16669
|
+
if (!hoveredPosition) return;
|
|
16670
|
+
for (const provider of this.providers) {
|
|
16671
|
+
const positions = provider.getHighlightedPositions(hoveredPosition);
|
|
16672
|
+
for (const position of positions) this.overlayColors.set(position, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16673
|
+
}
|
|
16556
16674
|
}
|
|
16557
|
-
|
|
16558
|
-
this.
|
|
16675
|
+
register(highlightProvider) {
|
|
16676
|
+
this.providers.push(highlightProvider);
|
|
16559
16677
|
}
|
|
16560
|
-
|
|
16561
|
-
this.
|
|
16562
|
-
if (!this.position) return;
|
|
16563
|
-
const { sheetId, col, row } = this.position;
|
|
16564
|
-
const table = this.getters.getTable({
|
|
16565
|
-
sheetId,
|
|
16566
|
-
col,
|
|
16567
|
-
row
|
|
16568
|
-
});
|
|
16569
|
-
if (!table) return;
|
|
16570
|
-
const { left, right, top } = table.range.zone;
|
|
16571
|
-
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
16572
|
-
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
16573
|
-
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
16574
|
-
sheetId,
|
|
16575
|
-
col,
|
|
16576
|
-
row
|
|
16577
|
-
}).formattedValue;
|
|
16578
|
-
});
|
|
16579
|
-
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
|
|
16580
|
-
sheetId,
|
|
16581
|
-
col,
|
|
16582
|
-
row
|
|
16583
|
-
}, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16678
|
+
unRegister(highlightProvider) {
|
|
16679
|
+
this.providers = this.providers.filter((h) => h !== highlightProvider);
|
|
16584
16680
|
}
|
|
16585
16681
|
};
|
|
16586
16682
|
|
|
@@ -16592,9 +16688,9 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16592
16688
|
position: types.CellPosition(),
|
|
16593
16689
|
sortDirection: types.or([types.SortDirection, types.literal("none")])
|
|
16594
16690
|
});
|
|
16595
|
-
|
|
16691
|
+
hoveredCellOverlayStore;
|
|
16596
16692
|
setup() {
|
|
16597
|
-
this.
|
|
16693
|
+
this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
|
|
16598
16694
|
}
|
|
16599
16695
|
get style() {
|
|
16600
16696
|
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
|
|
@@ -16614,7 +16710,7 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16614
16710
|
}
|
|
16615
16711
|
}
|
|
16616
16712
|
getBackgroundColor(cellStyle) {
|
|
16617
|
-
const overlayColor = this.
|
|
16713
|
+
const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
|
|
16618
16714
|
if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
|
|
16619
16715
|
return cellStyle.fillColor || "#FFFFFF";
|
|
16620
16716
|
}
|
|
@@ -21353,7 +21449,7 @@ var HoveredIconStore = class extends SpreadsheetStore {
|
|
|
21353
21449
|
//#region src/components/grid_overlay/grid_overlay.ts
|
|
21354
21450
|
function useCellHovered(env, gridRef) {
|
|
21355
21451
|
const delayedHoveredCell = useStore(DelayedHoveredCellStore);
|
|
21356
|
-
const
|
|
21452
|
+
const cellHoverOverlay = useStore(CellHoverOverlayStore);
|
|
21357
21453
|
const viewStore = useStore(ViewportsStore);
|
|
21358
21454
|
const hoveredPosition = {
|
|
21359
21455
|
col: void 0,
|
|
@@ -21397,7 +21493,9 @@ function useCellHovered(env, gridRef) {
|
|
|
21397
21493
|
if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
|
|
21398
21494
|
({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
|
|
21399
21495
|
lastMoved = Date.now();
|
|
21400
|
-
|
|
21496
|
+
const position = getPosition();
|
|
21497
|
+
if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
|
|
21498
|
+
else cellHoverOverlay.hover(getPosition());
|
|
21401
21499
|
}
|
|
21402
21500
|
}
|
|
21403
21501
|
function recompute() {
|
|
@@ -21408,7 +21506,7 @@ function useCellHovered(env, gridRef) {
|
|
|
21408
21506
|
const zoomedMouseEvent = withZoom(env, e);
|
|
21409
21507
|
const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
|
|
21410
21508
|
const gridRect = getElBoundingRect(gridRef());
|
|
21411
|
-
if (y
|
|
21509
|
+
if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
|
|
21412
21510
|
else return pause();
|
|
21413
21511
|
}
|
|
21414
21512
|
(0, _odoo_owl.useListener)(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
|
|
@@ -21445,7 +21543,7 @@ var GridOverlay = class extends Component {
|
|
|
21445
21543
|
gridOverlayDimensions: types.string(),
|
|
21446
21544
|
hasFooter: types.boolean().optional(() => true)
|
|
21447
21545
|
});
|
|
21448
|
-
gridOverlayRef =
|
|
21546
|
+
gridOverlayRef = _odoo_owl.signal.ref();
|
|
21449
21547
|
cellPopovers;
|
|
21450
21548
|
paintFormatStore;
|
|
21451
21549
|
hoveredIconStore;
|
|
@@ -21472,7 +21570,7 @@ var GridOverlay = class extends Component {
|
|
|
21472
21570
|
return el;
|
|
21473
21571
|
}
|
|
21474
21572
|
get style() {
|
|
21475
|
-
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "
|
|
21573
|
+
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
|
|
21476
21574
|
}
|
|
21477
21575
|
get isPaintingFormat() {
|
|
21478
21576
|
return this.paintFormatStore.isActive;
|
|
@@ -22112,8 +22210,9 @@ var FormulaFingerprintStore = class extends SpreadsheetStore {
|
|
|
22112
22210
|
const CELL_ANIMATION_DURATION = 200;
|
|
22113
22211
|
var GridRenderer = class extends DisposableStore {
|
|
22114
22212
|
renderer;
|
|
22213
|
+
storeGetters = ["drawLayer"];
|
|
22115
22214
|
fingerprints;
|
|
22116
|
-
|
|
22215
|
+
cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
22117
22216
|
hoveredIcon;
|
|
22118
22217
|
lastRenderSheetId = void 0;
|
|
22119
22218
|
lastRenderBoxes = /* @__PURE__ */ new Map();
|
|
@@ -22127,15 +22226,14 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22127
22226
|
const model = get(ModelStore);
|
|
22128
22227
|
this.getters = model.getters;
|
|
22129
22228
|
this.fingerprints = get(FormulaFingerprintStore);
|
|
22130
|
-
this.hoveredTables = get(HoveredTableStore);
|
|
22131
22229
|
this.hoveredIcon = get(HoveredIconStore);
|
|
22132
22230
|
model.on("command-dispatched", this, this.handle);
|
|
22133
22231
|
model.on("command-finalized", this, this.finalize);
|
|
22134
|
-
this.renderer.register(this);
|
|
22232
|
+
if (this.renderer) this.renderer.register(this);
|
|
22135
22233
|
this.onDispose(() => {
|
|
22136
22234
|
model.off("command-dispatched", this);
|
|
22137
22235
|
model.off("command-finalized", this);
|
|
22138
|
-
this.renderer.unRegister(this);
|
|
22236
|
+
if (this.renderer) this.renderer.unRegister(this);
|
|
22139
22237
|
});
|
|
22140
22238
|
}
|
|
22141
22239
|
handle(cmd) {
|
|
@@ -22679,7 +22777,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22679
22777
|
border: this.getters.getCellComputedBorder(position) || void 0,
|
|
22680
22778
|
style,
|
|
22681
22779
|
dataBarFill,
|
|
22682
|
-
overlayColor: this.
|
|
22780
|
+
overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
|
|
22683
22781
|
isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
|
|
22684
22782
|
icons: cellIcons,
|
|
22685
22783
|
disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
|
|
@@ -22855,6 +22953,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22855
22953
|
return boxes;
|
|
22856
22954
|
}
|
|
22857
22955
|
getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
|
|
22956
|
+
if (!this.renderer) return boxes;
|
|
22858
22957
|
this.updateAnimationsProgress(timeStamp);
|
|
22859
22958
|
this.addNewAnimations(boxes, oldBoxes, timeStamp);
|
|
22860
22959
|
if (this.animations.size > 0) {
|
|
@@ -22999,7 +23098,7 @@ var ScrollBar = class extends Component {
|
|
|
22999
23098
|
offset: types.Pixel(),
|
|
23000
23099
|
onScroll: types.function()
|
|
23001
23100
|
});
|
|
23002
|
-
scrollbarRef =
|
|
23101
|
+
scrollbarRef = _odoo_owl.signal.ref();
|
|
23003
23102
|
scrollbar;
|
|
23004
23103
|
setup() {
|
|
23005
23104
|
this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
|
|
@@ -23101,6 +23200,9 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23101
23200
|
zoneToDisplay: this.range.zone,
|
|
23102
23201
|
getFooterSize: () => 0
|
|
23103
23202
|
});
|
|
23203
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
23204
|
+
cellHoverOverlayStore.register(this);
|
|
23205
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
23104
23206
|
}
|
|
23105
23207
|
handle(cmd) {
|
|
23106
23208
|
this.cachedColDimensions = void 0;
|
|
@@ -23249,6 +23351,16 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23249
23351
|
}
|
|
23250
23352
|
return normalizedWeights;
|
|
23251
23353
|
}
|
|
23354
|
+
getHighlightedPositions(position) {
|
|
23355
|
+
const highlightedPositions = [];
|
|
23356
|
+
const { col, row, sheetId } = position;
|
|
23357
|
+
if (sheetId !== this.range.sheetId || !isInside(col, row, this.range.zone)) return highlightedPositions;
|
|
23358
|
+
for (let col = this.range.zone.left; col <= this.range.zone.right; col++) highlightedPositions.push({
|
|
23359
|
+
...position,
|
|
23360
|
+
col
|
|
23361
|
+
});
|
|
23362
|
+
return highlightedPositions;
|
|
23363
|
+
}
|
|
23252
23364
|
};
|
|
23253
23365
|
|
|
23254
23366
|
//#endregion
|
|
@@ -23265,10 +23377,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23265
23377
|
range: types.Range(),
|
|
23266
23378
|
canResizeColumns: types.boolean().optional(true),
|
|
23267
23379
|
onResizeColumns: types.function().optional(),
|
|
23268
|
-
columnWeights: types.array().optional()
|
|
23380
|
+
columnWeights: types.array().optional(),
|
|
23381
|
+
size: types.object()
|
|
23269
23382
|
});
|
|
23270
|
-
canvasRef =
|
|
23271
|
-
containerRef =
|
|
23383
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
23384
|
+
containerRef = _odoo_owl.signal.ref();
|
|
23272
23385
|
store;
|
|
23273
23386
|
dndState = (0, _odoo_owl.proxy)({ col: void 0 });
|
|
23274
23387
|
onMouseWheel;
|
|
@@ -23280,8 +23393,8 @@ var StandaloneViewport = class extends Component {
|
|
|
23280
23393
|
useChildStoreProvider([
|
|
23281
23394
|
ViewportsStore,
|
|
23282
23395
|
HoveredIconStore,
|
|
23283
|
-
HoveredTableStore,
|
|
23284
23396
|
ClickableCellsStore,
|
|
23397
|
+
CellHoverOverlayStore,
|
|
23285
23398
|
DelayedHoveredCellStore,
|
|
23286
23399
|
CellPopoverStore
|
|
23287
23400
|
]);
|
|
@@ -23290,18 +23403,10 @@ var StandaloneViewport = class extends Component {
|
|
|
23290
23403
|
this.zoomStore = useStore(ZoomStore);
|
|
23291
23404
|
this.cellPopoverStore = useStore(CellPopoverStore);
|
|
23292
23405
|
this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
|
|
23293
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
23294
|
-
this.store.setContainerSize(this.containerWidth, this.containerHeight);
|
|
23295
|
-
});
|
|
23296
|
-
(0, _odoo_owl.useEffect)(() => {
|
|
23297
|
-
const el = this.containerRef();
|
|
23298
|
-
if (el) resizeObserver.observe(el);
|
|
23299
|
-
return () => resizeObserver.disconnect();
|
|
23300
|
-
});
|
|
23301
23406
|
(0, _odoo_owl.useEffect)(() => {
|
|
23302
23407
|
this.store.setRange(this.props.range);
|
|
23303
23408
|
if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
|
|
23304
|
-
this.store.setContainerSize(this.
|
|
23409
|
+
this.store.setContainerSize(this.contentWidth, this.props.size.height);
|
|
23305
23410
|
});
|
|
23306
23411
|
useGridDrawing({
|
|
23307
23412
|
canvasRef: this.canvasRef,
|
|
@@ -23320,14 +23425,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23320
23425
|
}
|
|
23321
23426
|
});
|
|
23322
23427
|
}
|
|
23323
|
-
get
|
|
23324
|
-
return
|
|
23325
|
-
}
|
|
23326
|
-
get containerHeight() {
|
|
23327
|
-
return Math.floor(getElBoundingRect(this.containerRef()).height / this.zoomStore.zoomLevel);
|
|
23428
|
+
get contentWidth() {
|
|
23429
|
+
return this.hasVerticalScrollBar ? this.props.size.width - this.zoomStore.scrollBarWidth : this.props.size.width;
|
|
23328
23430
|
}
|
|
23329
23431
|
get hasVerticalScrollBar() {
|
|
23330
|
-
return this.
|
|
23432
|
+
return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
|
|
23331
23433
|
}
|
|
23332
23434
|
get scrollBarContainerStyle() {
|
|
23333
23435
|
return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
|
|
@@ -23379,7 +23481,7 @@ var StandaloneViewport = class extends Component {
|
|
|
23379
23481
|
};
|
|
23380
23482
|
const onMouseMove = (ev) => {
|
|
23381
23483
|
deltaX = withZoom(this.env, ev).clientX - initialX;
|
|
23382
|
-
const weightDelta = deltaX / this.
|
|
23484
|
+
const weightDelta = deltaX / this.props.size.width * totalWeight;
|
|
23383
23485
|
this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
|
|
23384
23486
|
};
|
|
23385
23487
|
startDnd(onMouseMove, onMouseUp);
|
|
@@ -23409,8 +23511,8 @@ var Select = class extends Component {
|
|
|
23409
23511
|
name: types.string().optional(),
|
|
23410
23512
|
title: types.string().optional()
|
|
23411
23513
|
});
|
|
23412
|
-
selectRef =
|
|
23413
|
-
dropdownRef =
|
|
23514
|
+
selectRef = _odoo_owl.signal.ref();
|
|
23515
|
+
dropdownRef = _odoo_owl.signal.ref();
|
|
23414
23516
|
state = (0, _odoo_owl.proxy)({
|
|
23415
23517
|
isPopoverOpen: false,
|
|
23416
23518
|
hoveredValue: void 0
|
|
@@ -23520,7 +23622,7 @@ var InfoPopover = class extends Component {
|
|
|
23520
23622
|
setup() {
|
|
23521
23623
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
23522
23624
|
}
|
|
23523
|
-
infoRef =
|
|
23625
|
+
infoRef = _odoo_owl.signal.ref();
|
|
23524
23626
|
get popoverProps() {
|
|
23525
23627
|
return {
|
|
23526
23628
|
anchorRect: this.props.anchorRect,
|
|
@@ -23665,8 +23767,8 @@ var CarouselFigure = class extends Component {
|
|
|
23665
23767
|
isFullScreen: types.boolean().optional(),
|
|
23666
23768
|
openContextMenu: types.function().optional()
|
|
23667
23769
|
});
|
|
23668
|
-
carouselTabsRef =
|
|
23669
|
-
carouselTabsDropdownRef =
|
|
23770
|
+
carouselTabsRef = _odoo_owl.signal.ref();
|
|
23771
|
+
carouselTabsDropdownRef = _odoo_owl.signal.ref();
|
|
23670
23772
|
menuState = (0, _odoo_owl.proxy)({
|
|
23671
23773
|
isOpen: false,
|
|
23672
23774
|
anchorRect: null,
|
|
@@ -23734,6 +23836,19 @@ var CarouselFigure = class extends Component {
|
|
|
23734
23836
|
} else cssProperties["background-color"] = backgroundColor;
|
|
23735
23837
|
return cssPropertiesToCss(cssProperties);
|
|
23736
23838
|
}
|
|
23839
|
+
get contentStyle() {
|
|
23840
|
+
return cssPropertiesToCss(this.rectToCss(this.carouselLayout.contentRect));
|
|
23841
|
+
}
|
|
23842
|
+
get headerStyle() {
|
|
23843
|
+
return cssPropertiesToCss({
|
|
23844
|
+
...this.rectToCss(this.carouselLayout.headerRect),
|
|
23845
|
+
"line-height": String(this.carouselLayout.headerLineHeight)
|
|
23846
|
+
});
|
|
23847
|
+
}
|
|
23848
|
+
get separatorStyle() {
|
|
23849
|
+
const separatorRect = this.carouselLayout.separatorRect;
|
|
23850
|
+
return separatorRect ? cssPropertiesToCss(this.rectToCss(separatorRect)) : "";
|
|
23851
|
+
}
|
|
23737
23852
|
get title() {
|
|
23738
23853
|
return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
|
|
23739
23854
|
}
|
|
@@ -23743,6 +23858,14 @@ var CarouselFigure = class extends Component {
|
|
|
23743
23858
|
...this.carousel.title
|
|
23744
23859
|
})));
|
|
23745
23860
|
}
|
|
23861
|
+
rectToCss(rect) {
|
|
23862
|
+
return {
|
|
23863
|
+
top: `${rect.y}px`,
|
|
23864
|
+
left: `${rect.x}px`,
|
|
23865
|
+
width: `${rect.width}px`,
|
|
23866
|
+
height: `${rect.height}px`
|
|
23867
|
+
};
|
|
23868
|
+
}
|
|
23746
23869
|
updateTabsVisibility() {
|
|
23747
23870
|
const tabsContainerEl = this.carouselTabsRef();
|
|
23748
23871
|
const dropDownEl = this.carouselTabsDropdownRef();
|
|
@@ -23834,6 +23957,13 @@ var CarouselFigure = class extends Component {
|
|
|
23834
23957
|
get canResizeDataViewColumns() {
|
|
23835
23958
|
return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
23836
23959
|
}
|
|
23960
|
+
get carouselLayout() {
|
|
23961
|
+
return getCarouselLayout({
|
|
23962
|
+
...this.props.figureUI,
|
|
23963
|
+
x: 0,
|
|
23964
|
+
y: 0
|
|
23965
|
+
}, this.carousel, this.selectedCarouselItem);
|
|
23966
|
+
}
|
|
23837
23967
|
};
|
|
23838
23968
|
|
|
23839
23969
|
//#endregion
|
|
@@ -23874,7 +24004,7 @@ var ImageFigure = class extends Component {
|
|
|
23874
24004
|
figureUI: types.FigureUI(),
|
|
23875
24005
|
openContextMenu: types.function()
|
|
23876
24006
|
});
|
|
23877
|
-
menuButtonRef =
|
|
24007
|
+
menuButtonRef = _odoo_owl.signal.ref();
|
|
23878
24008
|
showMenu(ev) {
|
|
23879
24009
|
if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
|
|
23880
24010
|
figureId: this.props.figureUI.id,
|
|
@@ -23902,7 +24032,8 @@ figureRegistry.add("chart", {
|
|
|
23902
24032
|
menuBuilder: getChartMenuActions,
|
|
23903
24033
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23904
24034
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23905
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24035
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24036
|
+
isThemeDependant: true
|
|
23906
24037
|
});
|
|
23907
24038
|
figureRegistry.add("image", {
|
|
23908
24039
|
Component: ImageFigure,
|
|
@@ -23911,14 +24042,16 @@ figureRegistry.add("image", {
|
|
|
23911
24042
|
borderWidth: () => 0,
|
|
23912
24043
|
hasShadow: () => false,
|
|
23913
24044
|
isRounded: () => false,
|
|
23914
|
-
menuBuilder: getImageMenuActions
|
|
24045
|
+
menuBuilder: getImageMenuActions,
|
|
24046
|
+
isThemeDependant: false
|
|
23915
24047
|
});
|
|
23916
24048
|
figureRegistry.add("carousel", {
|
|
23917
24049
|
Component: CarouselFigure,
|
|
23918
24050
|
menuBuilder: getCarouselMenuActions,
|
|
23919
24051
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23920
24052
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23921
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24053
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24054
|
+
isThemeDependant: true
|
|
23922
24055
|
});
|
|
23923
24056
|
|
|
23924
24057
|
//#endregion
|
|
@@ -23940,10 +24073,13 @@ var FigureComponent = class extends Component {
|
|
|
23940
24073
|
anchorRect: null,
|
|
23941
24074
|
menuItems: []
|
|
23942
24075
|
});
|
|
23943
|
-
figureRef =
|
|
24076
|
+
figureRef = _odoo_owl.signal.ref();
|
|
23944
24077
|
get isSelected() {
|
|
23945
24078
|
return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
|
|
23946
24079
|
}
|
|
24080
|
+
get isThemeDependant() {
|
|
24081
|
+
return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
|
|
24082
|
+
}
|
|
23947
24083
|
get figureRegistry() {
|
|
23948
24084
|
return figureRegistry;
|
|
23949
24085
|
}
|
|
@@ -29542,6 +29678,35 @@ async function interactivePasteFromOS(env, target, parsedClipboardContent, paste
|
|
|
29542
29678
|
}));
|
|
29543
29679
|
}
|
|
29544
29680
|
|
|
29681
|
+
//#endregion
|
|
29682
|
+
//#region src/helpers/figures/charts/chart_data_source_helpers.ts
|
|
29683
|
+
function getUnboundRange(getters, zone) {
|
|
29684
|
+
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29685
|
+
}
|
|
29686
|
+
function isDatasetTitled(getters, zone) {
|
|
29687
|
+
const sheetId = getters.getActiveSheetId();
|
|
29688
|
+
const cell = getters.getEvaluatedCell({
|
|
29689
|
+
sheetId,
|
|
29690
|
+
col: zone.left,
|
|
29691
|
+
row: zone.top
|
|
29692
|
+
});
|
|
29693
|
+
return !["number", "empty"].includes(cell.type);
|
|
29694
|
+
}
|
|
29695
|
+
function dataset(zone, getters, id = "0") {
|
|
29696
|
+
return {
|
|
29697
|
+
dataRange: getUnboundRange(getters, zone),
|
|
29698
|
+
dataSetId: id
|
|
29699
|
+
};
|
|
29700
|
+
}
|
|
29701
|
+
function rangeSource(dataSets, dataSetsHaveTitle, labelRange) {
|
|
29702
|
+
return {
|
|
29703
|
+
type: "range",
|
|
29704
|
+
dataSets,
|
|
29705
|
+
dataSetsHaveTitle,
|
|
29706
|
+
labelRange
|
|
29707
|
+
};
|
|
29708
|
+
}
|
|
29709
|
+
|
|
29545
29710
|
//#endregion
|
|
29546
29711
|
//#region src/helpers/figures/charts/smart_chart_engine.ts
|
|
29547
29712
|
const DEFAULT_BAR_CHART_CONFIG = {
|
|
@@ -29572,9 +29737,6 @@ const DEFAULT_LINE_CHART_CONFIG = {
|
|
|
29572
29737
|
labelsAsText: false,
|
|
29573
29738
|
humanize: true
|
|
29574
29739
|
};
|
|
29575
|
-
function getUnboundRange(getters, zone) {
|
|
29576
|
-
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29577
|
-
}
|
|
29578
29740
|
function detectColumnType(cells) {
|
|
29579
29741
|
if (!cells.length) return "empty";
|
|
29580
29742
|
const counts = {
|
|
@@ -29621,14 +29783,6 @@ function getCellStats(getters, zone) {
|
|
|
29621
29783
|
totalCount: values.length
|
|
29622
29784
|
};
|
|
29623
29785
|
}
|
|
29624
|
-
function isDatasetTitled(getters, column) {
|
|
29625
|
-
const titleCell = getters.getEvaluatedCell({
|
|
29626
|
-
sheetId: getters.getActiveSheetId(),
|
|
29627
|
-
col: column.zone.left,
|
|
29628
|
-
row: column.zone.top
|
|
29629
|
-
});
|
|
29630
|
-
return !["number", "empty"].includes(titleCell.type);
|
|
29631
|
-
}
|
|
29632
29786
|
/**
|
|
29633
29787
|
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
29634
29788
|
* - If the column contains a single cell, create a scorecard.
|
|
@@ -29640,8 +29794,7 @@ function isDatasetTitled(getters, column) {
|
|
|
29640
29794
|
function buildSingleColumnChart(column, getters) {
|
|
29641
29795
|
const { type, zone } = column;
|
|
29642
29796
|
const sheetId = getters.getActiveSheetId();
|
|
29643
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
29644
|
-
const dataRange = getUnboundRange(getters, zone);
|
|
29797
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
|
|
29645
29798
|
const titleCell = getters.getEvaluatedCell({
|
|
29646
29799
|
sheetId,
|
|
29647
29800
|
col: zone.left,
|
|
@@ -29652,14 +29805,7 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29652
29805
|
case "percentage": return {
|
|
29653
29806
|
type: "pie",
|
|
29654
29807
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29655
|
-
dataSource:
|
|
29656
|
-
type: "range",
|
|
29657
|
-
dataSets: [{
|
|
29658
|
-
dataRange,
|
|
29659
|
-
dataSetId: "0"
|
|
29660
|
-
}],
|
|
29661
|
-
dataSetsHaveTitle
|
|
29662
|
-
},
|
|
29808
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29663
29809
|
dataSetStyles: {},
|
|
29664
29810
|
legendPosition: "none"
|
|
29665
29811
|
};
|
|
@@ -29670,45 +29816,28 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29670
29816
|
return {
|
|
29671
29817
|
type: "pie",
|
|
29672
29818
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
29673
|
-
dataSource:
|
|
29674
|
-
type: "range",
|
|
29675
|
-
dataSets: [{
|
|
29676
|
-
dataRange,
|
|
29677
|
-
dataSetId: "0"
|
|
29678
|
-
}],
|
|
29679
|
-
labelRange: dataRange,
|
|
29680
|
-
dataSetsHaveTitle: hasUniqueTitle
|
|
29681
|
-
},
|
|
29819
|
+
dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
|
|
29682
29820
|
dataSetStyles: {},
|
|
29683
29821
|
aggregated: true,
|
|
29684
29822
|
legendPosition: "top"
|
|
29685
29823
|
};
|
|
29686
|
-
case "date":
|
|
29687
|
-
|
|
29688
|
-
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
29697
|
-
|
|
29698
|
-
verticalGroupBy: "month_number"
|
|
29699
|
-
};
|
|
29824
|
+
case "date": {
|
|
29825
|
+
const dataRange = getUnboundRange(getters, zone);
|
|
29826
|
+
return {
|
|
29827
|
+
type: "calendar",
|
|
29828
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29829
|
+
dataSource: rangeSource([], dataSetsHaveTitle, dataRange),
|
|
29830
|
+
dataSetStyles: {},
|
|
29831
|
+
legendPosition: "left",
|
|
29832
|
+
horizontalGroupBy: "day_of_week",
|
|
29833
|
+
verticalGroupBy: "month_number"
|
|
29834
|
+
};
|
|
29835
|
+
}
|
|
29700
29836
|
}
|
|
29701
29837
|
return {
|
|
29702
29838
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29703
29839
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29704
|
-
dataSource:
|
|
29705
|
-
type: "range",
|
|
29706
|
-
dataSets: [{
|
|
29707
|
-
dataRange,
|
|
29708
|
-
dataSetId: "0"
|
|
29709
|
-
}],
|
|
29710
|
-
dataSetsHaveTitle
|
|
29711
|
-
},
|
|
29840
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29712
29841
|
dataSetStyles: {}
|
|
29713
29842
|
};
|
|
29714
29843
|
}
|
|
@@ -29726,15 +29855,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29726
29855
|
if (columns[1].type === "percentage") return {
|
|
29727
29856
|
type: "pie",
|
|
29728
29857
|
title: {},
|
|
29729
|
-
dataSource:
|
|
29730
|
-
type: "range",
|
|
29731
|
-
dataSets: [{
|
|
29732
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29733
|
-
dataSetId: "0"
|
|
29734
|
-
}],
|
|
29735
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29736
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29737
|
-
},
|
|
29858
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29738
29859
|
dataSetStyles: {},
|
|
29739
29860
|
aggregated: true,
|
|
29740
29861
|
legendPosition: "none"
|
|
@@ -29742,15 +29863,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29742
29863
|
if (columns[0].type === "number" && columns[1].type === "number") return {
|
|
29743
29864
|
type: "scatter",
|
|
29744
29865
|
title: {},
|
|
29745
|
-
dataSource:
|
|
29746
|
-
type: "range",
|
|
29747
|
-
dataSets: [{
|
|
29748
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29749
|
-
dataSetId: "0"
|
|
29750
|
-
}],
|
|
29751
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29752
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29753
|
-
},
|
|
29866
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29754
29867
|
dataSetStyles: {},
|
|
29755
29868
|
labelsAsText: false,
|
|
29756
29869
|
legendPosition: "none"
|
|
@@ -29758,49 +29871,25 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29758
29871
|
if (columns[0].type === "date" && columns[1].type === "number") return {
|
|
29759
29872
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29760
29873
|
type: "line",
|
|
29761
|
-
dataSource:
|
|
29762
|
-
type: "range",
|
|
29763
|
-
dataSets: [{
|
|
29764
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29765
|
-
dataSetId: "0"
|
|
29766
|
-
}],
|
|
29767
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29768
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
|
|
29769
|
-
},
|
|
29874
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29770
29875
|
dataSetStyles: {}
|
|
29771
29876
|
};
|
|
29772
29877
|
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
29773
29878
|
const textColumn = columns[0];
|
|
29774
29879
|
const numberColumn = columns[1];
|
|
29775
29880
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
29776
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
29881
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
|
|
29777
29882
|
if (uniqueCount !== totalCount) return {
|
|
29778
29883
|
type: "treemap",
|
|
29779
29884
|
title: {},
|
|
29780
|
-
dataSource:
|
|
29781
|
-
type: "range",
|
|
29782
|
-
dataSets: [{
|
|
29783
|
-
dataRange: getUnboundRange(getters, textColumn.zone),
|
|
29784
|
-
dataSetId: "0"
|
|
29785
|
-
}],
|
|
29786
|
-
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
29787
|
-
dataSetsHaveTitle
|
|
29788
|
-
},
|
|
29885
|
+
dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
|
|
29789
29886
|
dataSetStyles: {},
|
|
29790
29887
|
legendPosition: "none"
|
|
29791
29888
|
};
|
|
29792
29889
|
}
|
|
29793
29890
|
return {
|
|
29794
29891
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29795
|
-
dataSource:
|
|
29796
|
-
type: "range",
|
|
29797
|
-
dataSets: [{
|
|
29798
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29799
|
-
dataSetId: "0"
|
|
29800
|
-
}],
|
|
29801
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29802
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29803
|
-
},
|
|
29892
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29804
29893
|
dataSetStyles: {}
|
|
29805
29894
|
};
|
|
29806
29895
|
}
|
|
@@ -29814,42 +29903,26 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29814
29903
|
*/
|
|
29815
29904
|
function buildMultiColumnChart(columns, getters) {
|
|
29816
29905
|
if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
|
|
29817
|
-
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
29906
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
|
|
29818
29907
|
const lastColumn = columns[columns.length - 1];
|
|
29819
29908
|
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
29820
29909
|
if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
|
|
29821
|
-
const dataSets = columnsExceptLast.map(({ zone }, i) => (
|
|
29822
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29823
|
-
dataSetId: i.toString()
|
|
29824
|
-
}));
|
|
29910
|
+
const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29825
29911
|
return {
|
|
29826
29912
|
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
29827
29913
|
title: {},
|
|
29828
|
-
dataSource:
|
|
29829
|
-
type: "range",
|
|
29830
|
-
dataSets,
|
|
29831
|
-
dataSetsHaveTitle,
|
|
29832
|
-
labelRange: getUnboundRange(getters, lastColumn.zone)
|
|
29833
|
-
},
|
|
29914
|
+
dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
|
|
29834
29915
|
dataSetStyles: {},
|
|
29835
29916
|
legendPosition: "none"
|
|
29836
29917
|
};
|
|
29837
29918
|
}
|
|
29838
29919
|
const firstColumn = columns[0];
|
|
29839
29920
|
const columnsExceptFirst = columns.slice(1);
|
|
29840
|
-
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => (
|
|
29841
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29842
|
-
dataSetId: i.toString()
|
|
29843
|
-
}));
|
|
29921
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29844
29922
|
if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
|
|
29845
29923
|
type: "pie",
|
|
29846
29924
|
title: {},
|
|
29847
|
-
dataSource:
|
|
29848
|
-
type: "range",
|
|
29849
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29850
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29851
|
-
dataSetsHaveTitle
|
|
29852
|
-
},
|
|
29925
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29853
29926
|
dataSetStyles: {},
|
|
29854
29927
|
aggregated: false,
|
|
29855
29928
|
legendPosition: "top"
|
|
@@ -29857,23 +29930,13 @@ function buildMultiColumnChart(columns, getters) {
|
|
|
29857
29930
|
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
|
|
29858
29931
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29859
29932
|
type: "line",
|
|
29860
|
-
dataSource:
|
|
29861
|
-
type: "range",
|
|
29862
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29863
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29864
|
-
dataSetsHaveTitle
|
|
29865
|
-
},
|
|
29933
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29866
29934
|
dataSetStyles: {},
|
|
29867
29935
|
legendPosition: "top"
|
|
29868
29936
|
};
|
|
29869
29937
|
return {
|
|
29870
29938
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29871
|
-
dataSource:
|
|
29872
|
-
type: "range",
|
|
29873
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29874
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29875
|
-
dataSetsHaveTitle
|
|
29876
|
-
},
|
|
29939
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29877
29940
|
dataSetStyles: {},
|
|
29878
29941
|
legendPosition: "top"
|
|
29879
29942
|
};
|
|
@@ -29900,17 +29963,10 @@ function buildScorecard(zone, getters) {
|
|
|
29900
29963
|
function getSmartChartDefinition(zones, getters) {
|
|
29901
29964
|
const columns = categorizeColumns(zones, getters);
|
|
29902
29965
|
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
29903
|
-
const dataSets = columns.map(({ zone }, i) => (
|
|
29904
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29905
|
-
dataSetId: i.toString()
|
|
29906
|
-
}));
|
|
29966
|
+
const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29907
29967
|
return {
|
|
29908
29968
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29909
|
-
dataSource:
|
|
29910
|
-
type: "range",
|
|
29911
|
-
dataSets,
|
|
29912
|
-
dataSetsHaveTitle: false
|
|
29913
|
-
}
|
|
29969
|
+
dataSource: rangeSource(dataSets, false)
|
|
29914
29970
|
};
|
|
29915
29971
|
}
|
|
29916
29972
|
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
@@ -31598,7 +31654,7 @@ var SelectionInput = class extends Component {
|
|
|
31598
31654
|
dragAndDrop = useDragAndDropListItems();
|
|
31599
31655
|
focusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31600
31656
|
unfocusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31601
|
-
selectionRef =
|
|
31657
|
+
selectionRef = _odoo_owl.signal.ref();
|
|
31602
31658
|
DOMFocusableElementStore;
|
|
31603
31659
|
store;
|
|
31604
31660
|
get ranges() {
|
|
@@ -32105,7 +32161,7 @@ var ColorPickerWidget = class extends Component {
|
|
|
32105
32161
|
dropdownMaxHeight: types.Pixel().optional(),
|
|
32106
32162
|
class: types.string().optional()
|
|
32107
32163
|
});
|
|
32108
|
-
colorPickerButtonRef =
|
|
32164
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
32109
32165
|
get iconStyle() {
|
|
32110
32166
|
return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
|
|
32111
32167
|
}
|
|
@@ -32133,8 +32189,8 @@ var NumberEditor = class extends Component {
|
|
|
32133
32189
|
});
|
|
32134
32190
|
dropdown = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
32135
32191
|
inputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
32136
|
-
rootEditorRef =
|
|
32137
|
-
valueListRef =
|
|
32192
|
+
rootEditorRef = _odoo_owl.signal.ref();
|
|
32193
|
+
valueListRef = _odoo_owl.signal.ref();
|
|
32138
32194
|
DOMFocusableElementStore;
|
|
32139
32195
|
setup() {
|
|
32140
32196
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -32440,7 +32496,7 @@ var ChartTypePickerPopover = class extends Component {
|
|
|
32440
32496
|
});
|
|
32441
32497
|
categories = chartCategories;
|
|
32442
32498
|
chartTypeByCategories = {};
|
|
32443
|
-
popoverRef =
|
|
32499
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
32444
32500
|
setup() {
|
|
32445
32501
|
(0, _odoo_owl.useListener)(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
|
|
32446
32502
|
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
@@ -32465,7 +32521,7 @@ var CogWheelMenu = class extends Component {
|
|
|
32465
32521
|
static template = "o-spreadsheet-CogWheelMenu";
|
|
32466
32522
|
static components = { MenuPopover };
|
|
32467
32523
|
props = (0, _odoo_owl.useProps)({ items: types.array(types.ActionSpec()) });
|
|
32468
|
-
buttonRef =
|
|
32524
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
32469
32525
|
menuState = (0, _odoo_owl.proxy)({
|
|
32470
32526
|
isOpen: false,
|
|
32471
32527
|
anchorRect: null,
|
|
@@ -32509,8 +32565,8 @@ var CarouselPanel = class extends Component {
|
|
|
32509
32565
|
});
|
|
32510
32566
|
DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
|
|
32511
32567
|
dragAndDrop = useDragAndDropListItems();
|
|
32512
|
-
previewListRef =
|
|
32513
|
-
addChartButton =
|
|
32568
|
+
previewListRef = _odoo_owl.signal.ref();
|
|
32569
|
+
addChartButton = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
32514
32570
|
state = (0, _odoo_owl.proxy)({
|
|
32515
32571
|
popoverProps: void 0,
|
|
32516
32572
|
currentRange: void 0
|
|
@@ -32954,7 +33010,7 @@ var BarConfigPanel = class extends GenericChartConfigPanel {
|
|
|
32954
33010
|
var Collapse = class extends Component {
|
|
32955
33011
|
static template = "o-spreadsheet-Collapse";
|
|
32956
33012
|
props = (0, _odoo_owl.useProps)({ isCollapsed: types.boolean() });
|
|
32957
|
-
contentRef =
|
|
33013
|
+
contentRef = _odoo_owl.signal.ref();
|
|
32958
33014
|
setup() {
|
|
32959
33015
|
(0, _odoo_owl.onMounted)(() => {
|
|
32960
33016
|
if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
|
|
@@ -33007,7 +33063,7 @@ var ChartAnnotation = class extends Component {
|
|
|
33007
33063
|
TextInput
|
|
33008
33064
|
};
|
|
33009
33065
|
props = (0, _odoo_owl.useProps)(chartSidePanelPropsDefinition);
|
|
33010
|
-
editorRef =
|
|
33066
|
+
editorRef = _odoo_owl.signal.ref();
|
|
33011
33067
|
state = (0, _odoo_owl.proxy)({
|
|
33012
33068
|
annotationTextInput: this.props.definition.annotationText || "",
|
|
33013
33069
|
isOverflowing: false
|
|
@@ -33329,7 +33385,7 @@ var RoundColorPicker = class extends Component {
|
|
|
33329
33385
|
onColorPicked: types.function(),
|
|
33330
33386
|
disableNoColor: types.boolean().optional()
|
|
33331
33387
|
});
|
|
33332
|
-
colorPickerButtonRef =
|
|
33388
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
33333
33389
|
state;
|
|
33334
33390
|
setup() {
|
|
33335
33391
|
this.state = (0, _odoo_owl.proxy)({ pickerOpened: false });
|
|
@@ -36171,6 +36227,7 @@ var helpers_index_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
36171
36227
|
getDefinedAxis: () => getDefinedAxis,
|
|
36172
36228
|
getPieColors: () => getPieColors,
|
|
36173
36229
|
isTrendLineAxis: () => isTrendLineAxis,
|
|
36230
|
+
limitChartConfigDataPoints: () => limitChartConfigDataPoints,
|
|
36174
36231
|
shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
|
|
36175
36232
|
toExcelDataset: () => toExcelDataset,
|
|
36176
36233
|
toExcelLabelRange: () => toExcelLabelRange,
|
|
@@ -36941,7 +36998,7 @@ var ColorScalePicker = class extends Component {
|
|
|
36941
36998
|
popoverProps: void 0,
|
|
36942
36999
|
popoverStyle: ""
|
|
36943
37000
|
});
|
|
36944
|
-
popoverRef =
|
|
37001
|
+
popoverRef = _odoo_owl.signal.ref(HTMLTableElement);
|
|
36945
37002
|
setup() {
|
|
36946
37003
|
useExternalListener(window, "click", this.closePopover);
|
|
36947
37004
|
}
|
|
@@ -37190,7 +37247,7 @@ var TextValueProvider = class extends Component {
|
|
|
37190
37247
|
onValueSelected: types.function(),
|
|
37191
37248
|
onValueHovered: types.function()
|
|
37192
37249
|
});
|
|
37193
|
-
autoCompleteListRef =
|
|
37250
|
+
autoCompleteListRef = _odoo_owl.signal.ref();
|
|
37194
37251
|
setup() {
|
|
37195
37252
|
(0, _odoo_owl.useEffect)(() => {
|
|
37196
37253
|
const selectedIndex = this.props.selectedIndex;
|
|
@@ -37481,7 +37538,7 @@ var SpeechBubble = class extends Component {
|
|
|
37481
37538
|
anchorRect: types.Rect()
|
|
37482
37539
|
});
|
|
37483
37540
|
spreadsheetRect = useSpreadsheetRect();
|
|
37484
|
-
bubbleRef =
|
|
37541
|
+
bubbleRef = _odoo_owl.signal.ref();
|
|
37485
37542
|
setup() {
|
|
37486
37543
|
(0, _odoo_owl.useEffect)(() => {
|
|
37487
37544
|
const el = this.bubbleRef();
|
|
@@ -37526,8 +37583,8 @@ var Composer = class extends Component {
|
|
|
37526
37583
|
showAssistant: types.boolean().optional(true)
|
|
37527
37584
|
});
|
|
37528
37585
|
DOMFocusableElementStore;
|
|
37529
|
-
composerRef =
|
|
37530
|
-
containerRef =
|
|
37586
|
+
composerRef = _odoo_owl.signal.ref();
|
|
37587
|
+
containerRef = _odoo_owl.signal.ref();
|
|
37531
37588
|
contentHelper = new ContentEditableHelper(this.composerRef());
|
|
37532
37589
|
composerState = (0, _odoo_owl.proxy)({
|
|
37533
37590
|
positionStart: 0,
|
|
@@ -40029,7 +40086,7 @@ var ChartTypePicker = class extends Component {
|
|
|
40029
40086
|
chartId: types.UID(),
|
|
40030
40087
|
chartPanelStore: types.Store()
|
|
40031
40088
|
});
|
|
40032
|
-
selectRef =
|
|
40089
|
+
selectRef = _odoo_owl.signal.ref();
|
|
40033
40090
|
state = (0, _odoo_owl.proxy)({ popoverProps: void 0 });
|
|
40034
40091
|
getSupportedChartTypes() {
|
|
40035
40092
|
let supportedTypes;
|
|
@@ -40597,7 +40654,7 @@ var ColumnStatsPanel = class extends Component {
|
|
|
40597
40654
|
highlightPositions: []
|
|
40598
40655
|
});
|
|
40599
40656
|
store;
|
|
40600
|
-
chartCanvasRef =
|
|
40657
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
40601
40658
|
chart;
|
|
40602
40659
|
setup() {
|
|
40603
40660
|
this.store = useStore(ColumnStatisticsStore);
|
|
@@ -42025,7 +42082,7 @@ var ConditionalFormatPreview = class extends Component {
|
|
|
42025
42082
|
class: types.string()
|
|
42026
42083
|
});
|
|
42027
42084
|
icons = ICONS;
|
|
42028
|
-
cfPreviewRef =
|
|
42085
|
+
cfPreviewRef = _odoo_owl.signal.ref();
|
|
42029
42086
|
setup() {
|
|
42030
42087
|
useHighlightsOnHover(this.cfPreviewRef, this);
|
|
42031
42088
|
}
|
|
@@ -42082,7 +42139,7 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42082
42139
|
static components = { ConditionalFormatPreview };
|
|
42083
42140
|
props = (0, _odoo_owl.useProps)({ onCloseSidePanel: types.function() });
|
|
42084
42141
|
dragAndDrop = useDragAndDropListItems();
|
|
42085
|
-
cfListRef =
|
|
42142
|
+
cfListRef = _odoo_owl.signal.ref();
|
|
42086
42143
|
get conditionalFormats() {
|
|
42087
42144
|
return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
|
|
42088
42145
|
}
|
|
@@ -42142,6 +42199,1477 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42142
42199
|
}
|
|
42143
42200
|
};
|
|
42144
42201
|
|
|
42202
|
+
//#endregion
|
|
42203
|
+
//#region src/stores/chart_drag_store.ts
|
|
42204
|
+
/**
|
|
42205
|
+
* Holds the id of the figure that a dragged chart suggestion is currently
|
|
42206
|
+
* hovering over, so it can be highlighted reactively instead of through direct DOM manipulation.
|
|
42207
|
+
*/
|
|
42208
|
+
var ChartDragStore = class {
|
|
42209
|
+
mutators = ["setHighlightedFigure"];
|
|
42210
|
+
highlightedFigureId = void 0;
|
|
42211
|
+
setHighlightedFigure(figureId) {
|
|
42212
|
+
this.highlightedFigureId = figureId;
|
|
42213
|
+
}
|
|
42214
|
+
};
|
|
42215
|
+
|
|
42216
|
+
//#endregion
|
|
42217
|
+
//#region src/components/helpers/chart_drag_and_drop.ts
|
|
42218
|
+
function getDefaultChartFigureSize(type) {
|
|
42219
|
+
if (type === "scorecard") return {
|
|
42220
|
+
width: 213,
|
|
42221
|
+
height: 101
|
|
42222
|
+
};
|
|
42223
|
+
return {
|
|
42224
|
+
width: 536,
|
|
42225
|
+
height: 335
|
|
42226
|
+
};
|
|
42227
|
+
}
|
|
42228
|
+
function getCarouselOverlappingChart(figureUI, otherFigures, matchTags) {
|
|
42229
|
+
if (figureUI.tag !== "chart") return;
|
|
42230
|
+
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
42231
|
+
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
42232
|
+
let bestMatch;
|
|
42233
|
+
let smallestDistance = Infinity;
|
|
42234
|
+
for (const figure of otherFigures) {
|
|
42235
|
+
if (!matchTags.includes(figure.tag)) continue;
|
|
42236
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
42237
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
42238
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
42239
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
42240
|
+
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
42241
|
+
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
42242
|
+
smallestDistance = squaredDistance;
|
|
42243
|
+
bestMatch = figure;
|
|
42244
|
+
}
|
|
42245
|
+
}
|
|
42246
|
+
return bestMatch;
|
|
42247
|
+
}
|
|
42248
|
+
/**
|
|
42249
|
+
* Start dragging a floating preview of the given chart definition, following the mouse.
|
|
42250
|
+
* On drop, creates the chart on the grid (or inside a carousel if dropped on one).
|
|
42251
|
+
*/
|
|
42252
|
+
function startChartDragAndDrop(env, definition, ev) {
|
|
42253
|
+
const zoom = env.getStore(ZoomStore).zoomLevel;
|
|
42254
|
+
const gridPosition = gridOverlayPosition(zoom);
|
|
42255
|
+
const spreadsheet = document.querySelector(".o-spreadsheet");
|
|
42256
|
+
if (!spreadsheet) return;
|
|
42257
|
+
const startX = ev.clientX / zoom;
|
|
42258
|
+
const startY = ev.clientY / zoom;
|
|
42259
|
+
const { width, height } = getDefaultChartFigureSize(definition.type);
|
|
42260
|
+
const figureWidth = width * zoom;
|
|
42261
|
+
const figureHeight = height * zoom;
|
|
42262
|
+
const getters = env.model.getters;
|
|
42263
|
+
const sheetId = getters.getActiveSheetId();
|
|
42264
|
+
let container = null;
|
|
42265
|
+
let destroyChart = void 0;
|
|
42266
|
+
const chartDragStore = env.getStore(ChartDragStore);
|
|
42267
|
+
const previousCursor = document.body.style.cursor;
|
|
42268
|
+
document.body.style.cursor = "grabbing";
|
|
42269
|
+
/** Grid coordinates (in sheet pixels) of a mouse position, or undefined if outside the grid. */
|
|
42270
|
+
const getGridPosition = (clientX, clientY) => {
|
|
42271
|
+
if (clientX > gridPosition.x + gridPosition.width || clientY > gridPosition.y + gridPosition.height) return;
|
|
42272
|
+
const { scrollX, scrollY } = env.getStore(ViewportsStore).activeSheetScrollInfo;
|
|
42273
|
+
return {
|
|
42274
|
+
x: Math.max(0, (clientX - gridPosition.x) / zoom + scrollX),
|
|
42275
|
+
y: Math.max(0, (clientY - gridPosition.y) / zoom + scrollY)
|
|
42276
|
+
};
|
|
42277
|
+
};
|
|
42278
|
+
/** Carousel or standalone chart the dragged chart is dropped onto, if any. */
|
|
42279
|
+
const getOverlappingFigure = (clientX, clientY) => {
|
|
42280
|
+
const position = getGridPosition(clientX, clientY);
|
|
42281
|
+
if (!position) return;
|
|
42282
|
+
const figureUI = {
|
|
42283
|
+
tag: "chart",
|
|
42284
|
+
...position,
|
|
42285
|
+
width,
|
|
42286
|
+
height
|
|
42287
|
+
};
|
|
42288
|
+
const otherFigures = env.getStore(ViewportsStore).visibleFigures;
|
|
42289
|
+
return getCarouselOverlappingChart(figureUI, otherFigures, ["carousel", "chart"]);
|
|
42290
|
+
};
|
|
42291
|
+
const halfWidth = figureWidth / 2;
|
|
42292
|
+
const halfHeight = figureHeight / 2;
|
|
42293
|
+
const onMouseMove = (e) => {
|
|
42294
|
+
if (Math.abs(e.clientX - startX) <= 5 && Math.abs(e.clientY - startY) <= 5) return;
|
|
42295
|
+
if (container === null) {
|
|
42296
|
+
container = document.createElement("div");
|
|
42297
|
+
container.className = "o-chart-drag-preview os-theme-dependant position-fixed border pe-none";
|
|
42298
|
+
container.style.width = `${width}px`;
|
|
42299
|
+
container.style.height = `${height}px`;
|
|
42300
|
+
container.style.zoom = `${zoom}`;
|
|
42301
|
+
const canvas = document.createElement("canvas");
|
|
42302
|
+
canvas.className = "w-100 h-100";
|
|
42303
|
+
container.appendChild(canvas);
|
|
42304
|
+
spreadsheet.appendChild(container);
|
|
42305
|
+
destroyChart = drawChartOnCanvas(canvas, SpreadsheetChart.fromStrDefinition(getters, sheetId, definition).getRuntime(getters, "newChart"), {
|
|
42306
|
+
width,
|
|
42307
|
+
height
|
|
42308
|
+
}, definition.type, zoom);
|
|
42309
|
+
}
|
|
42310
|
+
container.style.left = `${Math.max(gridPosition.x, (e.clientX - halfWidth) / zoom)}px`;
|
|
42311
|
+
container.style.top = `${Math.max(gridPosition.y, (e.clientY - halfHeight) / zoom)}px`;
|
|
42312
|
+
const overlappingFigure = getOverlappingFigure(e.clientX - halfWidth, e.clientY - halfHeight);
|
|
42313
|
+
container.style.opacity = overlappingFigure?.id ? "0.6" : "0.9";
|
|
42314
|
+
chartDragStore.setHighlightedFigure(overlappingFigure?.id);
|
|
42315
|
+
};
|
|
42316
|
+
const onMouseUp = (mouseEvent) => {
|
|
42317
|
+
chartDragStore.setHighlightedFigure(void 0);
|
|
42318
|
+
if (container !== null) {
|
|
42319
|
+
spreadsheet.removeChild(container);
|
|
42320
|
+
container = null;
|
|
42321
|
+
}
|
|
42322
|
+
destroyChart?.();
|
|
42323
|
+
document.body.style.cursor = previousCursor;
|
|
42324
|
+
let position = getGridPosition(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42325
|
+
if (Math.abs(mouseEvent.clientX / zoom - startX) <= 5 && Math.abs(mouseEvent.clientY / zoom - startY) <= 5) position = {
|
|
42326
|
+
x: 0,
|
|
42327
|
+
y: 0
|
|
42328
|
+
};
|
|
42329
|
+
else if (!position || position.x + halfWidth > gridPosition.width) return;
|
|
42330
|
+
const { col, row, offset } = env.getStore(ViewportsStore).viewports.getPositionAnchorOffset(sheetId, position);
|
|
42331
|
+
const payload = {
|
|
42332
|
+
chartId: UuidGenerator.smallUuid(),
|
|
42333
|
+
figureId: UuidGenerator.smallUuid(),
|
|
42334
|
+
sheetId,
|
|
42335
|
+
size: {
|
|
42336
|
+
width,
|
|
42337
|
+
height
|
|
42338
|
+
},
|
|
42339
|
+
definition,
|
|
42340
|
+
col,
|
|
42341
|
+
row,
|
|
42342
|
+
offset
|
|
42343
|
+
};
|
|
42344
|
+
const overlappingFigure = getOverlappingFigure(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42345
|
+
if (overlappingFigure?.tag === "carousel") env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
|
|
42346
|
+
sheetId,
|
|
42347
|
+
figureId: overlappingFigure.id,
|
|
42348
|
+
newChartId: UuidGenerator.smallUuid(),
|
|
42349
|
+
chartDefinition: definition
|
|
42350
|
+
});
|
|
42351
|
+
else if (overlappingFigure?.tag === "chart") env.model.dispatch("CREATE_CHART_AND_MERGE_INTO_CAROUSEL", {
|
|
42352
|
+
chartId: payload.chartId,
|
|
42353
|
+
figureId: payload.figureId,
|
|
42354
|
+
sheetId: payload.sheetId,
|
|
42355
|
+
definition: payload.definition,
|
|
42356
|
+
baseFigureId: overlappingFigure.id
|
|
42357
|
+
});
|
|
42358
|
+
else env.model.dispatch("CREATE_CHART", payload);
|
|
42359
|
+
};
|
|
42360
|
+
startDnd(onMouseMove, onMouseUp);
|
|
42361
|
+
}
|
|
42362
|
+
|
|
42363
|
+
//#endregion
|
|
42364
|
+
//#region src/components/side_panel/data_analysis/chart_suggestion_preview.ts
|
|
42365
|
+
const PREVIEW_BUBBLE_RADIUS_RATIO = .3;
|
|
42366
|
+
const MIN_PREVIEW_BUBBLE_RADIUS = 1;
|
|
42367
|
+
var ChartSuggestionPreview = class extends Component {
|
|
42368
|
+
static template = "o-spreadsheet-ChartSuggestionPreview";
|
|
42369
|
+
props = (0, _odoo_owl.useProps)({
|
|
42370
|
+
definition: types.ChartDefinition(),
|
|
42371
|
+
description: types.string(),
|
|
42372
|
+
onPointerDown: types.function()
|
|
42373
|
+
});
|
|
42374
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
42375
|
+
chart;
|
|
42376
|
+
setup() {
|
|
42377
|
+
(0, _odoo_owl.onMounted)(() => registerChartJSExtensions());
|
|
42378
|
+
(0, _odoo_owl.onWillUnmount)(() => this.destroyChart());
|
|
42379
|
+
useLayoutEffect(() => {
|
|
42380
|
+
this.updateChart();
|
|
42381
|
+
}, () => [this.props.definition]);
|
|
42382
|
+
}
|
|
42383
|
+
getChartConfiguration() {
|
|
42384
|
+
const getters = this.env.model.getters;
|
|
42385
|
+
const activeSheetId = getters.getActiveSheetId();
|
|
42386
|
+
const runtime = SpreadsheetChart.fromStrDefinition(getters, activeSheetId, this.props.definition).getRuntime(getters, "myChart");
|
|
42387
|
+
if (!("chartJsConfig" in runtime)) return null;
|
|
42388
|
+
let config = runtime.chartJsConfig;
|
|
42389
|
+
const existingScales = config.options?.scales ?? {};
|
|
42390
|
+
config = {
|
|
42391
|
+
...config,
|
|
42392
|
+
data: this.props.definition.type === "bubble" ? {
|
|
42393
|
+
...config.data,
|
|
42394
|
+
datasets: (config.data?.datasets || []).map((dataset) => ({
|
|
42395
|
+
...dataset,
|
|
42396
|
+
pointRadius: this.scaleBubblePointRadius(dataset.pointRadius),
|
|
42397
|
+
pointHoverRadius: this.scaleBubblePointRadius(dataset.pointHoverRadius)
|
|
42398
|
+
}))
|
|
42399
|
+
} : config.data,
|
|
42400
|
+
options: {
|
|
42401
|
+
...config.options,
|
|
42402
|
+
plugins: {
|
|
42403
|
+
...config.options?.plugins,
|
|
42404
|
+
legend: { display: false },
|
|
42405
|
+
title: { display: false },
|
|
42406
|
+
tooltip: { enabled: false }
|
|
42407
|
+
},
|
|
42408
|
+
events: [],
|
|
42409
|
+
animation: false,
|
|
42410
|
+
scales: this.getScalesCongif(existingScales)
|
|
42411
|
+
}
|
|
42412
|
+
};
|
|
42413
|
+
return limitChartConfigDataPoints(config);
|
|
42414
|
+
}
|
|
42415
|
+
getScalesCongif(scales) {
|
|
42416
|
+
if (scales.x) scales.x = {
|
|
42417
|
+
...scales.x,
|
|
42418
|
+
ticks: { display: false },
|
|
42419
|
+
border: { display: false }
|
|
42420
|
+
};
|
|
42421
|
+
if (scales.y) scales.y = {
|
|
42422
|
+
...scales.y,
|
|
42423
|
+
ticks: { display: false },
|
|
42424
|
+
border: { display: false }
|
|
42425
|
+
};
|
|
42426
|
+
if (scales.r) scales.r = {
|
|
42427
|
+
...scales.r,
|
|
42428
|
+
ticks: { display: false },
|
|
42429
|
+
pointLabels: { display: false }
|
|
42430
|
+
};
|
|
42431
|
+
return scales;
|
|
42432
|
+
}
|
|
42433
|
+
scaleBubblePointRadius(pointRadius) {
|
|
42434
|
+
if (Array.isArray(pointRadius)) return pointRadius.map((radius) => radius <= 0 ? radius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, radius * PREVIEW_BUBBLE_RADIUS_RATIO));
|
|
42435
|
+
if (typeof pointRadius === "number") return pointRadius <= 0 ? pointRadius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, pointRadius * PREVIEW_BUBBLE_RADIUS_RATIO);
|
|
42436
|
+
return pointRadius ?? MIN_PREVIEW_BUBBLE_RADIUS;
|
|
42437
|
+
}
|
|
42438
|
+
updateChart() {
|
|
42439
|
+
this.destroyChart();
|
|
42440
|
+
const config = this.getChartConfiguration();
|
|
42441
|
+
if (!config) {
|
|
42442
|
+
this.drawNativeChart();
|
|
42443
|
+
return;
|
|
42444
|
+
}
|
|
42445
|
+
const ctx = this.chartCanvasRef()?.getContext("2d");
|
|
42446
|
+
if (!ctx || !globalThis.Chart) return;
|
|
42447
|
+
this.chart = new globalThis.Chart(ctx, config);
|
|
42448
|
+
}
|
|
42449
|
+
drawNativeChart() {
|
|
42450
|
+
const canvas = this.chartCanvasRef();
|
|
42451
|
+
if (!canvas) return;
|
|
42452
|
+
const getters = this.env.model.getters;
|
|
42453
|
+
let runtime = SpreadsheetChart.fromStrDefinition(getters, getters.getActiveSheetId(), this.props.definition).getRuntime(getters, "myChart");
|
|
42454
|
+
const { type } = this.props.definition;
|
|
42455
|
+
if (type === "scorecard") {
|
|
42456
|
+
const rect = canvas.getBoundingClientRect();
|
|
42457
|
+
runtime = {
|
|
42458
|
+
...runtime,
|
|
42459
|
+
keyValueStyle: {
|
|
42460
|
+
...runtime.keyValueStyle,
|
|
42461
|
+
fontSize: 16
|
|
42462
|
+
}
|
|
42463
|
+
};
|
|
42464
|
+
drawScoreChart(getScorecardConfiguration({
|
|
42465
|
+
width: rect.width || 130,
|
|
42466
|
+
height: rect.height || 120
|
|
42467
|
+
}, runtime), canvas);
|
|
42468
|
+
} else if (type === "gauge") {
|
|
42469
|
+
let gaugeRuntime = runtime;
|
|
42470
|
+
if (gaugeRuntime.title) gaugeRuntime = {
|
|
42471
|
+
...gaugeRuntime,
|
|
42472
|
+
title: { text: "" }
|
|
42473
|
+
};
|
|
42474
|
+
drawGaugeChart(canvas, gaugeRuntime, 1, void 0, { labelFontSize: 8 });
|
|
42475
|
+
}
|
|
42476
|
+
}
|
|
42477
|
+
destroyChart() {
|
|
42478
|
+
this.chart?.destroy();
|
|
42479
|
+
this.chart = void 0;
|
|
42480
|
+
}
|
|
42481
|
+
};
|
|
42482
|
+
|
|
42483
|
+
//#endregion
|
|
42484
|
+
//#region src/helpers/data_analysis.ts
|
|
42485
|
+
function analyzeColumns(zones, getters) {
|
|
42486
|
+
return getZonesByColumns(zones).map((zone) => analyzeColumn(zone, getters));
|
|
42487
|
+
}
|
|
42488
|
+
function analyzeColumn(zone, getters) {
|
|
42489
|
+
const sheetId = getters.getActiveSheetId();
|
|
42490
|
+
const cells = getters.getEvaluatedCellsInZone(sheetId, zone).filter((c) => c.type !== "empty");
|
|
42491
|
+
if (!cells.length) return {
|
|
42492
|
+
zone,
|
|
42493
|
+
type: "empty",
|
|
42494
|
+
hasHeader: false,
|
|
42495
|
+
rowCount: 0,
|
|
42496
|
+
uniqueCount: 0,
|
|
42497
|
+
uniqueRatio: 0,
|
|
42498
|
+
nonEmpty: []
|
|
42499
|
+
};
|
|
42500
|
+
const firstCell = cells[0];
|
|
42501
|
+
const rest = cells.slice(1);
|
|
42502
|
+
const hasHeader = firstCell.type === "text" && rest.some((c) => c.type !== "text");
|
|
42503
|
+
const dataCells = hasHeader ? rest : cells;
|
|
42504
|
+
const numericValues = dataCells.filter((c) => c.type === "number").map((c) => c.value);
|
|
42505
|
+
const allVals = dataCells.map((c) => String(c.value ?? ""));
|
|
42506
|
+
const uniqueCount = new Set(allVals).size;
|
|
42507
|
+
return {
|
|
42508
|
+
zone,
|
|
42509
|
+
type: computeColumnType(dataCells),
|
|
42510
|
+
header: hasHeader ? firstCell.value : void 0,
|
|
42511
|
+
hasHeader,
|
|
42512
|
+
rowCount: dataCells.length,
|
|
42513
|
+
uniqueCount,
|
|
42514
|
+
uniqueRatio: allVals.length > 0 ? uniqueCount / allVals.length : 0,
|
|
42515
|
+
maxValue: numericValues.length ? numericValues.reduce((max, v) => v > max ? v : max, numericValues[0]) : void 0,
|
|
42516
|
+
nonEmpty: dataCells
|
|
42517
|
+
};
|
|
42518
|
+
}
|
|
42519
|
+
function computeColumnType(cells) {
|
|
42520
|
+
if (cells.length === 0) return "empty";
|
|
42521
|
+
if (cells.every((c) => c.type === "error")) return "error";
|
|
42522
|
+
cells = cells.filter((c) => c.type !== "error");
|
|
42523
|
+
if (cells.every((c) => c.type === "boolean")) return "boolean";
|
|
42524
|
+
if (cells.every((c) => c.type === "number" && !!c.format && isDateTimeFormat(c.format))) return "date";
|
|
42525
|
+
else if (cells.every((c) => c.type === "number")) {
|
|
42526
|
+
if (cells.every((c) => c.format?.includes("%"))) return "percentage";
|
|
42527
|
+
return "number";
|
|
42528
|
+
} else {
|
|
42529
|
+
const textVals = cells.filter((c) => c.type === "text").map((c) => c.value);
|
|
42530
|
+
if (textVals.length > 0) {
|
|
42531
|
+
const unique = new Set(textVals).size;
|
|
42532
|
+
return unique / textVals.length < .75 && unique <= 20 ? "categorical" : "label";
|
|
42533
|
+
}
|
|
42534
|
+
}
|
|
42535
|
+
return "empty";
|
|
42536
|
+
}
|
|
42537
|
+
|
|
42538
|
+
//#endregion
|
|
42539
|
+
//#region src/helpers/figures/charts/charts_suggestions_constants.ts
|
|
42540
|
+
/** Above this many rows, shape-based charts (radar, scatter, pyramid) become unreadable. */
|
|
42541
|
+
const MAX_ROWS_FOR_SHAPE_CHART = 10;
|
|
42542
|
+
function barChart(titleText, source, opts = {}) {
|
|
42543
|
+
return {
|
|
42544
|
+
...opts,
|
|
42545
|
+
type: "bar",
|
|
42546
|
+
title: { text: titleText },
|
|
42547
|
+
dataSource: source,
|
|
42548
|
+
dataSetStyles: {},
|
|
42549
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42550
|
+
stacked: opts.stacked ?? false,
|
|
42551
|
+
humanize: true,
|
|
42552
|
+
aggregated: opts.aggregated ?? false
|
|
42553
|
+
};
|
|
42554
|
+
}
|
|
42555
|
+
function lineChart(titleText, source, opts = {}) {
|
|
42556
|
+
return {
|
|
42557
|
+
...opts,
|
|
42558
|
+
type: "line",
|
|
42559
|
+
title: { text: titleText },
|
|
42560
|
+
dataSource: source,
|
|
42561
|
+
dataSetStyles: {},
|
|
42562
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42563
|
+
stacked: opts.stacked ?? false,
|
|
42564
|
+
cumulative: opts.cumulative ?? false,
|
|
42565
|
+
labelsAsText: false,
|
|
42566
|
+
humanize: true
|
|
42567
|
+
};
|
|
42568
|
+
}
|
|
42569
|
+
function pieChart(titleText, source, opts = {}) {
|
|
42570
|
+
return {
|
|
42571
|
+
...opts,
|
|
42572
|
+
type: "pie",
|
|
42573
|
+
title: { text: titleText },
|
|
42574
|
+
dataSource: source,
|
|
42575
|
+
dataSetStyles: {},
|
|
42576
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42577
|
+
humanize: true
|
|
42578
|
+
};
|
|
42579
|
+
}
|
|
42580
|
+
function radarChart(titleText, source, opts = {}) {
|
|
42581
|
+
return {
|
|
42582
|
+
...opts,
|
|
42583
|
+
type: "radar",
|
|
42584
|
+
title: { text: titleText },
|
|
42585
|
+
dataSource: source,
|
|
42586
|
+
dataSetStyles: {},
|
|
42587
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42588
|
+
stacked: false,
|
|
42589
|
+
humanize: true
|
|
42590
|
+
};
|
|
42591
|
+
}
|
|
42592
|
+
function sunburstChart(titleText, source) {
|
|
42593
|
+
return {
|
|
42594
|
+
type: "sunburst",
|
|
42595
|
+
title: { text: titleText },
|
|
42596
|
+
dataSource: source,
|
|
42597
|
+
dataSetStyles: {},
|
|
42598
|
+
legendPosition: "none"
|
|
42599
|
+
};
|
|
42600
|
+
}
|
|
42601
|
+
function treemapChart(titleText, source) {
|
|
42602
|
+
return {
|
|
42603
|
+
type: "treemap",
|
|
42604
|
+
title: { text: titleText },
|
|
42605
|
+
dataSource: source,
|
|
42606
|
+
dataSetStyles: {},
|
|
42607
|
+
legendPosition: "none"
|
|
42608
|
+
};
|
|
42609
|
+
}
|
|
42610
|
+
function calendarChart(titleText, source) {
|
|
42611
|
+
return {
|
|
42612
|
+
type: "calendar",
|
|
42613
|
+
title: { text: titleText },
|
|
42614
|
+
dataSource: source,
|
|
42615
|
+
dataSetStyles: {},
|
|
42616
|
+
legendPosition: "none",
|
|
42617
|
+
horizontalGroupBy: "month_number",
|
|
42618
|
+
verticalGroupBy: "day_of_week",
|
|
42619
|
+
humanize: true
|
|
42620
|
+
};
|
|
42621
|
+
}
|
|
42622
|
+
function scatterChart(titleText, source) {
|
|
42623
|
+
return {
|
|
42624
|
+
type: "scatter",
|
|
42625
|
+
title: { text: titleText },
|
|
42626
|
+
dataSource: source,
|
|
42627
|
+
dataSetStyles: {},
|
|
42628
|
+
legendPosition: "none",
|
|
42629
|
+
labelsAsText: false,
|
|
42630
|
+
humanize: true
|
|
42631
|
+
};
|
|
42632
|
+
}
|
|
42633
|
+
function pyramidChart(titleText, source, opts = {}) {
|
|
42634
|
+
return {
|
|
42635
|
+
type: "pyramid",
|
|
42636
|
+
title: { text: titleText },
|
|
42637
|
+
dataSource: source,
|
|
42638
|
+
dataSetStyles: {},
|
|
42639
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42640
|
+
stacked: false,
|
|
42641
|
+
horizontal: true,
|
|
42642
|
+
humanize: true
|
|
42643
|
+
};
|
|
42644
|
+
}
|
|
42645
|
+
function comboChart(titleText, source, opts = {}) {
|
|
42646
|
+
return {
|
|
42647
|
+
...opts,
|
|
42648
|
+
type: "combo",
|
|
42649
|
+
title: { text: titleText },
|
|
42650
|
+
dataSource: source,
|
|
42651
|
+
dataSetStyles: {},
|
|
42652
|
+
legendPosition: "top",
|
|
42653
|
+
humanize: true
|
|
42654
|
+
};
|
|
42655
|
+
}
|
|
42656
|
+
function bubbleChart(titleText, xRange, yRanges, sizeRange, labelRange, dataSetsHaveTitle) {
|
|
42657
|
+
return {
|
|
42658
|
+
type: "bubble",
|
|
42659
|
+
title: { text: titleText },
|
|
42660
|
+
humanize: true,
|
|
42661
|
+
dataSetsHaveTitle,
|
|
42662
|
+
yRanges,
|
|
42663
|
+
xRange,
|
|
42664
|
+
sizeRange,
|
|
42665
|
+
labelRange,
|
|
42666
|
+
labelsAsText: false,
|
|
42667
|
+
legendPosition: "none",
|
|
42668
|
+
bubbleColor: { color: "multiple" },
|
|
42669
|
+
verticalAxisPosition: "left"
|
|
42670
|
+
};
|
|
42671
|
+
}
|
|
42672
|
+
function scorecardChart(titleText, keyValue, opts = {}) {
|
|
42673
|
+
return {
|
|
42674
|
+
...opts,
|
|
42675
|
+
type: "scorecard",
|
|
42676
|
+
title: { text: titleText },
|
|
42677
|
+
keyValue,
|
|
42678
|
+
baselineMode: opts.baselineMode ?? "difference",
|
|
42679
|
+
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
42680
|
+
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
42681
|
+
humanize: opts.humanize ?? true
|
|
42682
|
+
};
|
|
42683
|
+
}
|
|
42684
|
+
function gaugeChart(titleText, dataRange, rangeMin, rangeMax) {
|
|
42685
|
+
return {
|
|
42686
|
+
type: "gauge",
|
|
42687
|
+
title: { text: titleText },
|
|
42688
|
+
dataRange,
|
|
42689
|
+
sectionRule: {
|
|
42690
|
+
colors: {
|
|
42691
|
+
lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
|
|
42692
|
+
middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
|
|
42693
|
+
upperColor: DEFAULT_GAUGE_UPPER_COLOR
|
|
42694
|
+
},
|
|
42695
|
+
rangeMin,
|
|
42696
|
+
rangeMax,
|
|
42697
|
+
lowerInflectionPoint: {
|
|
42698
|
+
type: "percentage",
|
|
42699
|
+
value: "33",
|
|
42700
|
+
operator: "<="
|
|
42701
|
+
},
|
|
42702
|
+
upperInflectionPoint: {
|
|
42703
|
+
type: "percentage",
|
|
42704
|
+
value: "66",
|
|
42705
|
+
operator: "<="
|
|
42706
|
+
}
|
|
42707
|
+
},
|
|
42708
|
+
humanize: true
|
|
42709
|
+
};
|
|
42710
|
+
}
|
|
42711
|
+
/** Pattern A — Single numeric column */
|
|
42712
|
+
const SINGLE_NUMBER_COLUMN_SUGGESTIONS = [
|
|
42713
|
+
{
|
|
42714
|
+
description: _t("Highlights the most recent value compared to the previous one."),
|
|
42715
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42716
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42717
|
+
baseline: ctx.prevCellXC,
|
|
42718
|
+
baselineMode: "difference"
|
|
42719
|
+
})
|
|
42720
|
+
},
|
|
42721
|
+
{
|
|
42722
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
42723
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42724
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42725
|
+
},
|
|
42726
|
+
{
|
|
42727
|
+
description: _t("Compares individual values side-by-side."),
|
|
42728
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42729
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42730
|
+
},
|
|
42731
|
+
{
|
|
42732
|
+
description: _t("Shows the evolution of all values over the range."),
|
|
42733
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42734
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42735
|
+
},
|
|
42736
|
+
{
|
|
42737
|
+
description: _t("Emphasizes total accumulation over the range."),
|
|
42738
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42739
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42740
|
+
fillArea: true,
|
|
42741
|
+
cumulative: true
|
|
42742
|
+
})
|
|
42743
|
+
}
|
|
42744
|
+
];
|
|
42745
|
+
/** Pattern B — Single percentage column */
|
|
42746
|
+
const SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS = [
|
|
42747
|
+
{
|
|
42748
|
+
description: _t("Shows the last percentage value with its baseline."),
|
|
42749
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42750
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42751
|
+
baseline: ctx.prevCellXC,
|
|
42752
|
+
baselineMode: "percentage"
|
|
42753
|
+
})
|
|
42754
|
+
},
|
|
42755
|
+
{
|
|
42756
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42757
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42758
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, "0", ctx.isAboveOne ? "100" : "1")
|
|
42759
|
+
},
|
|
42760
|
+
{
|
|
42761
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42762
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42763
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42764
|
+
},
|
|
42765
|
+
{
|
|
42766
|
+
description: _t("Shows completion against total."),
|
|
42767
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42768
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { isDoughnut: true })
|
|
42769
|
+
},
|
|
42770
|
+
{
|
|
42771
|
+
description: _t("Compares all percentage values side-by-side."),
|
|
42772
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42773
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42774
|
+
}
|
|
42775
|
+
];
|
|
42776
|
+
/** Pattern C — Single date column */
|
|
42777
|
+
const SINGLE_DATE_COLUMN_SUGGESTIONS = [{
|
|
42778
|
+
description: _t("Shows the last date value."),
|
|
42779
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42780
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC)
|
|
42781
|
+
}];
|
|
42782
|
+
/** Pattern D — Single categorical column */
|
|
42783
|
+
const SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS = [
|
|
42784
|
+
{
|
|
42785
|
+
description: _t("Shows the share of each category."),
|
|
42786
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42787
|
+
aggregated: true,
|
|
42788
|
+
legendPosition: "top"
|
|
42789
|
+
})
|
|
42790
|
+
},
|
|
42791
|
+
{
|
|
42792
|
+
description: _t("Same as pie, cleaner proportional look."),
|
|
42793
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42794
|
+
aggregated: true,
|
|
42795
|
+
isDoughnut: true,
|
|
42796
|
+
legendPosition: "top"
|
|
42797
|
+
})
|
|
42798
|
+
},
|
|
42799
|
+
{
|
|
42800
|
+
description: _t("Absolute count per category."),
|
|
42801
|
+
build: (ctx) => barChart(ctx.title, {
|
|
42802
|
+
...ctx.source,
|
|
42803
|
+
labelRange: ctx.range
|
|
42804
|
+
}, {
|
|
42805
|
+
legendPosition: "none",
|
|
42806
|
+
aggregated: true
|
|
42807
|
+
})
|
|
42808
|
+
}
|
|
42809
|
+
];
|
|
42810
|
+
/** Pattern E — Single label column */
|
|
42811
|
+
const SINGLE_LABEL_COLUMN_SUGGESTIONS = [{
|
|
42812
|
+
description: _t("Displays a key performance indicator."),
|
|
42813
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42814
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42815
|
+
baselineMode: "text",
|
|
42816
|
+
humanize: false
|
|
42817
|
+
})
|
|
42818
|
+
}];
|
|
42819
|
+
/** Pattern F — Categorical + Number */
|
|
42820
|
+
const CATEGORICAL_VS_NUMBER_SUGGESTIONS = [
|
|
42821
|
+
{
|
|
42822
|
+
description: _t("Classic category-vs-value comparison."),
|
|
42823
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { aggregated: true })
|
|
42824
|
+
},
|
|
42825
|
+
{
|
|
42826
|
+
description: _t("Better when category labels are long."),
|
|
42827
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42828
|
+
horizontal: true,
|
|
42829
|
+
aggregated: true
|
|
42830
|
+
})
|
|
42831
|
+
},
|
|
42832
|
+
{
|
|
42833
|
+
description: _t("Share of total per category."),
|
|
42834
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42835
|
+
legendPosition: "top",
|
|
42836
|
+
aggregated: true
|
|
42837
|
+
})
|
|
42838
|
+
},
|
|
42839
|
+
{
|
|
42840
|
+
description: _t("Proportional area — good for many categories."),
|
|
42841
|
+
build: (ctx) => treemapChart(ctx.title, ctx.treemapSource)
|
|
42842
|
+
}
|
|
42843
|
+
];
|
|
42844
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
42845
|
+
const DATE_VS_SERIES_SUGGESTIONS = [
|
|
42846
|
+
{
|
|
42847
|
+
description: _t("Best for visualizing time-series trends."),
|
|
42848
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42849
|
+
},
|
|
42850
|
+
{
|
|
42851
|
+
description: _t("Emphasizes total volume over time."),
|
|
42852
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42853
|
+
fillArea: true,
|
|
42854
|
+
cumulative: true
|
|
42855
|
+
})
|
|
42856
|
+
},
|
|
42857
|
+
{
|
|
42858
|
+
description: _t("Period-by-period comparison."),
|
|
42859
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42860
|
+
},
|
|
42861
|
+
{
|
|
42862
|
+
description: _t("Shows intensity variation across days of the year."),
|
|
42863
|
+
isApplicable: ({ isPercentage }) => !isPercentage,
|
|
42864
|
+
build: (ctx) => calendarChart(ctx.title, ctx.source)
|
|
42865
|
+
}
|
|
42866
|
+
];
|
|
42867
|
+
/** Pattern H — Number + Number */
|
|
42868
|
+
const NUMBER_VS_NUMBER_SUGGESTIONS = [
|
|
42869
|
+
{
|
|
42870
|
+
description: _t("Highlights the second metric compared to the first one."),
|
|
42871
|
+
isApplicable: ({ rowCount1, rowCount2 }) => rowCount1 === 1 && rowCount2 === 1,
|
|
42872
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42873
|
+
baseline: ctx.prevCellXC,
|
|
42874
|
+
baselineMode: "difference"
|
|
42875
|
+
})
|
|
42876
|
+
},
|
|
42877
|
+
{
|
|
42878
|
+
description: _t("Side-by-side comparison of two numeric series."),
|
|
42879
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42880
|
+
},
|
|
42881
|
+
{
|
|
42882
|
+
description: _t("Reveals correlation between two numeric variables."),
|
|
42883
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42884
|
+
build: (ctx) => scatterChart(ctx.title, ctx.source2)
|
|
42885
|
+
},
|
|
42886
|
+
{
|
|
42887
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
42888
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42889
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth)
|
|
42890
|
+
},
|
|
42891
|
+
{
|
|
42892
|
+
description: _t("When both metrics contribute to a total."),
|
|
42893
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42894
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceBoth, {
|
|
42895
|
+
stacked: true,
|
|
42896
|
+
fillArea: true,
|
|
42897
|
+
legendPosition: "top"
|
|
42898
|
+
})
|
|
42899
|
+
},
|
|
42900
|
+
{
|
|
42901
|
+
description: _t("Shape-based comparison when rows represent named entities."),
|
|
42902
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2 && rowCount1 <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42903
|
+
build: (ctx) => radarChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42904
|
+
}
|
|
42905
|
+
];
|
|
42906
|
+
/** Pattern I — Categorical + Percentage */
|
|
42907
|
+
const CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS = [
|
|
42908
|
+
{
|
|
42909
|
+
description: _t("Vertical comparison of rates per category."),
|
|
42910
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42911
|
+
},
|
|
42912
|
+
{
|
|
42913
|
+
description: _t("Progress-bar style per category."),
|
|
42914
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42915
|
+
},
|
|
42916
|
+
{
|
|
42917
|
+
description: _t("Share of total percentage across categories."),
|
|
42918
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42919
|
+
},
|
|
42920
|
+
{
|
|
42921
|
+
description: _t("Comparison of completion rates across categories."),
|
|
42922
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42923
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42924
|
+
}
|
|
42925
|
+
];
|
|
42926
|
+
/** Pattern K — Label + Number */
|
|
42927
|
+
const LABEL_VS_NUMBER_SUGGESTIONS = [
|
|
42928
|
+
{
|
|
42929
|
+
description: _t("Highlights the most recent value for the named entity."),
|
|
42930
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42931
|
+
build: (ctx) => scorecardChart("", ctx.lastCellXC, {
|
|
42932
|
+
humanize: false,
|
|
42933
|
+
baselineMode: "text",
|
|
42934
|
+
baseline: ctx.prevCellXC
|
|
42935
|
+
})
|
|
42936
|
+
},
|
|
42937
|
+
{
|
|
42938
|
+
description: _t("Vertical comparison across named items."),
|
|
42939
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42940
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42941
|
+
},
|
|
42942
|
+
{
|
|
42943
|
+
description: _t("Works well for named entities with long labels."),
|
|
42944
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42945
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42946
|
+
},
|
|
42947
|
+
{
|
|
42948
|
+
description: _t("Share of total per category."),
|
|
42949
|
+
isApplicable: ({ rowCount }) => rowCount > 1 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42950
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42951
|
+
legendPosition: "top",
|
|
42952
|
+
aggregated: true
|
|
42953
|
+
})
|
|
42954
|
+
},
|
|
42955
|
+
{
|
|
42956
|
+
description: _t("Shape-based comparison across labeled items."),
|
|
42957
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42958
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42959
|
+
}
|
|
42960
|
+
];
|
|
42961
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
42962
|
+
const CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42963
|
+
{
|
|
42964
|
+
description: _t("Side-by-side comparison across categories for each series."),
|
|
42965
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42966
|
+
},
|
|
42967
|
+
{
|
|
42968
|
+
description: _t("Shows composition and total per category."),
|
|
42969
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42970
|
+
stacked: true,
|
|
42971
|
+
legendPosition: "top"
|
|
42972
|
+
})
|
|
42973
|
+
},
|
|
42974
|
+
{
|
|
42975
|
+
description: _t("Trend per series across categories."),
|
|
42976
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42977
|
+
},
|
|
42978
|
+
{
|
|
42979
|
+
description: _t("Volume and composition across categories."),
|
|
42980
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42981
|
+
stacked: true,
|
|
42982
|
+
fillArea: true,
|
|
42983
|
+
legendPosition: "top"
|
|
42984
|
+
})
|
|
42985
|
+
},
|
|
42986
|
+
{
|
|
42987
|
+
description: _t("Shape comparison across metrics (best for ≤ 10 rows)."),
|
|
42988
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42989
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42990
|
+
}
|
|
42991
|
+
];
|
|
42992
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
42993
|
+
const DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42994
|
+
{
|
|
42995
|
+
description: _t("Trend comparison across multiple metrics over time."),
|
|
42996
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42997
|
+
},
|
|
42998
|
+
{
|
|
42999
|
+
description: _t("Volume composition over time."),
|
|
43000
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
43001
|
+
stacked: true,
|
|
43002
|
+
fillArea: true,
|
|
43003
|
+
legendPosition: "top"
|
|
43004
|
+
})
|
|
43005
|
+
},
|
|
43006
|
+
{
|
|
43007
|
+
description: _t("Bar for the primary metric, line for the others."),
|
|
43008
|
+
build: (ctx) => comboChart(ctx.title, ctx.source)
|
|
43009
|
+
},
|
|
43010
|
+
{
|
|
43011
|
+
description: _t("Period-by-period grouped comparison."),
|
|
43012
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43013
|
+
}
|
|
43014
|
+
];
|
|
43015
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43016
|
+
const MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS = [
|
|
43017
|
+
{
|
|
43018
|
+
description: _t("Two-level hierarchy weighted by value."),
|
|
43019
|
+
build: (ctx) => sunburstChart(ctx.title, ctx.hierarchySource)
|
|
43020
|
+
},
|
|
43021
|
+
{
|
|
43022
|
+
description: _t("Proportional nested area weighted by value."),
|
|
43023
|
+
build: (ctx) => treemapChart(ctx.title, ctx.hierarchySource)
|
|
43024
|
+
},
|
|
43025
|
+
{
|
|
43026
|
+
description: _t("One series per inner category, grouped by outer category."),
|
|
43027
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, { legendPosition: "top" })
|
|
43028
|
+
},
|
|
43029
|
+
{
|
|
43030
|
+
description: _t("Contribution of inner categories per outer category."),
|
|
43031
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, {
|
|
43032
|
+
stacked: true,
|
|
43033
|
+
legendPosition: "top"
|
|
43034
|
+
})
|
|
43035
|
+
}
|
|
43036
|
+
];
|
|
43037
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43038
|
+
const CATEGORICAL_DATE_NUMBER_SUGGESTIONS = [
|
|
43039
|
+
{
|
|
43040
|
+
description: _t("Trend of values over time."),
|
|
43041
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, { legendPosition: "top" })
|
|
43042
|
+
},
|
|
43043
|
+
{
|
|
43044
|
+
description: _t("Volume contribution over time."),
|
|
43045
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, {
|
|
43046
|
+
stacked: true,
|
|
43047
|
+
fillArea: true,
|
|
43048
|
+
legendPosition: "top"
|
|
43049
|
+
})
|
|
43050
|
+
},
|
|
43051
|
+
{
|
|
43052
|
+
description: _t("Period × category side-by-side comparison."),
|
|
43053
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByCat, { legendPosition: "top" })
|
|
43054
|
+
},
|
|
43055
|
+
{
|
|
43056
|
+
description: _t("Composition per period over time."),
|
|
43057
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByDate, {
|
|
43058
|
+
stacked: true,
|
|
43059
|
+
legendPosition: "top"
|
|
43060
|
+
})
|
|
43061
|
+
}
|
|
43062
|
+
];
|
|
43063
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43064
|
+
const LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
43065
|
+
{
|
|
43066
|
+
description: _t("Side-by-side per entity — works well for long labels."),
|
|
43067
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43068
|
+
horizontal: true,
|
|
43069
|
+
legendPosition: "top"
|
|
43070
|
+
})
|
|
43071
|
+
},
|
|
43072
|
+
{
|
|
43073
|
+
description: _t("Grouped bars per entity for direct metric comparison."),
|
|
43074
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43075
|
+
},
|
|
43076
|
+
{
|
|
43077
|
+
description: _t("Shape/profile comparison across metrics for each entity."),
|
|
43078
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43079
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43080
|
+
},
|
|
43081
|
+
{
|
|
43082
|
+
description: _t("Correlation between the two numeric metrics across entities."),
|
|
43083
|
+
isApplicable: ({ rowCount, numColsCount }) => rowCount > 2 && numColsCount === 2,
|
|
43084
|
+
build: (ctx) => scatterChart(ctx.title, ctx.scatterSource)
|
|
43085
|
+
},
|
|
43086
|
+
{
|
|
43087
|
+
description: _t("Three metrics in one view: X position, Y position and bubble size."),
|
|
43088
|
+
isApplicable: ({ numColsCount }) => numColsCount === 3,
|
|
43089
|
+
build: (ctx) => {
|
|
43090
|
+
const bubble = ctx.bubble;
|
|
43091
|
+
return bubbleChart(ctx.title, bubble.xRange, bubble.yRanges, bubble.sizeRange, bubble.labelRange, bubble.hasTitle);
|
|
43092
|
+
}
|
|
43093
|
+
}
|
|
43094
|
+
];
|
|
43095
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43096
|
+
const CATEGORICAL_TWO_NUMBERS_SUGGESTIONS = [
|
|
43097
|
+
{
|
|
43098
|
+
description: _t("Natural fit for symmetric or opposing values per category."),
|
|
43099
|
+
isApplicable: ({ isPyramid }) => isPyramid,
|
|
43100
|
+
build: (ctx) => pyramidChart(ctx.title, ctx.sourceBoth)
|
|
43101
|
+
},
|
|
43102
|
+
{
|
|
43103
|
+
description: _t("Side-by-side comparison of both metrics per category."),
|
|
43104
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43105
|
+
legendPosition: "top",
|
|
43106
|
+
aggregated: true
|
|
43107
|
+
})
|
|
43108
|
+
},
|
|
43109
|
+
{
|
|
43110
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
43111
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth, { aggregated: true })
|
|
43112
|
+
},
|
|
43113
|
+
{
|
|
43114
|
+
description: _t("Shows total and composition per category."),
|
|
43115
|
+
isApplicable: ({ isPyramid }) => !isPyramid,
|
|
43116
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43117
|
+
stacked: true,
|
|
43118
|
+
legendPosition: "top",
|
|
43119
|
+
aggregated: true
|
|
43120
|
+
})
|
|
43121
|
+
}
|
|
43122
|
+
];
|
|
43123
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43124
|
+
const MANY_NUMBERS_SUGGESTIONS = [
|
|
43125
|
+
{
|
|
43126
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
43127
|
+
isApplicable: ({ colsLength, rowCount }) => colsLength === 3 && rowCount === 1,
|
|
43128
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.secondCellXC}`)
|
|
43129
|
+
},
|
|
43130
|
+
{
|
|
43131
|
+
description: _t("Trend comparison across all metrics."),
|
|
43132
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
43133
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43134
|
+
},
|
|
43135
|
+
{
|
|
43136
|
+
description: _t("Overall shape/profile across all metrics."),
|
|
43137
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43138
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43139
|
+
},
|
|
43140
|
+
{
|
|
43141
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43142
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43143
|
+
},
|
|
43144
|
+
{
|
|
43145
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43146
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43147
|
+
legendPosition: "top",
|
|
43148
|
+
stacked: true
|
|
43149
|
+
})
|
|
43150
|
+
}
|
|
43151
|
+
];
|
|
43152
|
+
|
|
43153
|
+
//#endregion
|
|
43154
|
+
//#region src/helpers/figures/charts/chart_suggestion_engine.ts
|
|
43155
|
+
const PYRAMID_HEADER_KEYWORD_PATTERNS = [
|
|
43156
|
+
"male",
|
|
43157
|
+
"female",
|
|
43158
|
+
"man",
|
|
43159
|
+
"woman",
|
|
43160
|
+
"boy",
|
|
43161
|
+
"girl",
|
|
43162
|
+
"before",
|
|
43163
|
+
"after",
|
|
43164
|
+
"previous",
|
|
43165
|
+
"current",
|
|
43166
|
+
"positive",
|
|
43167
|
+
"negative",
|
|
43168
|
+
"gain",
|
|
43169
|
+
"loss",
|
|
43170
|
+
"income",
|
|
43171
|
+
"expense",
|
|
43172
|
+
"revenue",
|
|
43173
|
+
"cost"
|
|
43174
|
+
].map((keyword) => new RegExp(`\\b${keyword}\\b`, "i"));
|
|
43175
|
+
function matchesColumnType(spec, type) {
|
|
43176
|
+
return Array.isArray(spec) ? spec.includes(type) : spec === type;
|
|
43177
|
+
}
|
|
43178
|
+
function matchesShape(pattern, shape) {
|
|
43179
|
+
const { leadingTypes, extension } = pattern;
|
|
43180
|
+
const expectedLength = leadingTypes.length + (extension?.minSize ?? 0);
|
|
43181
|
+
if (extension ? shape.length < expectedLength : shape.length !== expectedLength) return false;
|
|
43182
|
+
return shape.every((type, i) => {
|
|
43183
|
+
const spec = i < leadingTypes.length ? leadingTypes[i] : extension?.type;
|
|
43184
|
+
return spec !== void 0 && matchesColumnType(spec, type);
|
|
43185
|
+
});
|
|
43186
|
+
}
|
|
43187
|
+
function isPyramidLike(numCol1, numCol2) {
|
|
43188
|
+
const h1 = numCol1.header ?? "";
|
|
43189
|
+
const h2 = numCol2.header ?? "";
|
|
43190
|
+
return PYRAMID_HEADER_KEYWORD_PATTERNS.some((re) => re.test(h1) || re.test(h2));
|
|
43191
|
+
}
|
|
43192
|
+
/** Returns the first and last cell XC coordinates, and the second-to-last cell XC coordinate if it exists. */
|
|
43193
|
+
function interestingCellsXc(col) {
|
|
43194
|
+
if (!col) return {
|
|
43195
|
+
firstCellXC: "",
|
|
43196
|
+
lastCellXC: "",
|
|
43197
|
+
prevCellXC: void 0
|
|
43198
|
+
};
|
|
43199
|
+
const firstCellPosition = col.nonEmpty.at(0)?.position;
|
|
43200
|
+
const firstCellXC = firstCellPosition ? toXC(firstCellPosition?.col, firstCellPosition?.row) : "";
|
|
43201
|
+
const lastCellPosition = col.nonEmpty.at(-1)?.position;
|
|
43202
|
+
const lastCellXC = lastCellPosition ? toXC(lastCellPosition?.col, lastCellPosition?.row) : "";
|
|
43203
|
+
const prevCellPosition = col.nonEmpty.at(-2)?.position;
|
|
43204
|
+
return {
|
|
43205
|
+
firstCellXC,
|
|
43206
|
+
lastCellXC,
|
|
43207
|
+
prevCellXC: prevCellPosition ? toXC(prevCellPosition?.col, prevCellPosition?.row) : void 0
|
|
43208
|
+
};
|
|
43209
|
+
}
|
|
43210
|
+
/** Pattern A — Single numeric column */
|
|
43211
|
+
function buildSingleNumberContext([col], getters) {
|
|
43212
|
+
const title = col.header ?? _t("Value");
|
|
43213
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43214
|
+
return {
|
|
43215
|
+
title,
|
|
43216
|
+
source: rangeSource([dataset(col.zone, getters)], col.hasHeader),
|
|
43217
|
+
rowCount: col.rowCount,
|
|
43218
|
+
firstCellXC,
|
|
43219
|
+
lastCellXC,
|
|
43220
|
+
prevCellXC
|
|
43221
|
+
};
|
|
43222
|
+
}
|
|
43223
|
+
/** Pattern B — Single percentage column */
|
|
43224
|
+
function buildSinglePercentageContext([col], getters) {
|
|
43225
|
+
const hasTitle = col.hasHeader;
|
|
43226
|
+
const title = col.header ?? _t("Rate");
|
|
43227
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43228
|
+
const source = rangeSource([dataset(col.zone, getters)], hasTitle);
|
|
43229
|
+
const isAboveOne = (col.maxValue ?? 0) > 1;
|
|
43230
|
+
return {
|
|
43231
|
+
title,
|
|
43232
|
+
source,
|
|
43233
|
+
rowCount: col.rowCount,
|
|
43234
|
+
firstCellXC,
|
|
43235
|
+
lastCellXC,
|
|
43236
|
+
prevCellXC,
|
|
43237
|
+
isAboveOne
|
|
43238
|
+
};
|
|
43239
|
+
}
|
|
43240
|
+
/** Pattern C — Single date column */
|
|
43241
|
+
function buildSingleDateContext([col]) {
|
|
43242
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43243
|
+
return {
|
|
43244
|
+
title: col.header ?? _t("Date"),
|
|
43245
|
+
lastCellXC,
|
|
43246
|
+
rowCount: col.rowCount
|
|
43247
|
+
};
|
|
43248
|
+
}
|
|
43249
|
+
/** Pattern D — Single categorical column */
|
|
43250
|
+
function buildSingleCategoricalContext([col], getters) {
|
|
43251
|
+
const hasTitle = col.hasHeader;
|
|
43252
|
+
const title = col.header ?? _t("Category");
|
|
43253
|
+
const range = getUnboundRange(getters, col.zone);
|
|
43254
|
+
return {
|
|
43255
|
+
title,
|
|
43256
|
+
source: rangeSource([dataset(col.zone, getters)], hasTitle, range),
|
|
43257
|
+
range
|
|
43258
|
+
};
|
|
43259
|
+
}
|
|
43260
|
+
/** Pattern E — Single label column */
|
|
43261
|
+
function buildSingleLabelContext([col]) {
|
|
43262
|
+
const title = col.header ?? _t("Label");
|
|
43263
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43264
|
+
return {
|
|
43265
|
+
title,
|
|
43266
|
+
lastCellXC,
|
|
43267
|
+
rowCount: col.rowCount
|
|
43268
|
+
};
|
|
43269
|
+
}
|
|
43270
|
+
/** Pattern F — Categorical + Number */
|
|
43271
|
+
function buildCategoricalVsNumberContext([catCol, numCol], getters) {
|
|
43272
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43273
|
+
const hasTitle = numCol.hasHeader;
|
|
43274
|
+
return {
|
|
43275
|
+
title: numCol.header ? _t("%(numberHeader)s by %(categoryHeader)s", {
|
|
43276
|
+
numberHeader: numCol.header,
|
|
43277
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43278
|
+
}) : _t("By Category"),
|
|
43279
|
+
source: rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange),
|
|
43280
|
+
treemapSource: rangeSource([dataset(catCol.zone, getters)], hasTitle, getUnboundRange(getters, numCol.zone))
|
|
43281
|
+
};
|
|
43282
|
+
}
|
|
43283
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
43284
|
+
function buildDateVsSeriesContext([dateCol, seriesCol], getters) {
|
|
43285
|
+
const isPercentage = seriesCol.type === "percentage";
|
|
43286
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43287
|
+
const hasTitle = isDatasetTitled(getters, dateCol.zone);
|
|
43288
|
+
return {
|
|
43289
|
+
title: seriesCol.header ? _t("%(seriesHeader)s over time", { seriesHeader: seriesCol.header }) : isPercentage ? _t("Rate over Time") : _t("Over Time"),
|
|
43290
|
+
source: rangeSource([dataset(seriesCol.zone, getters)], hasTitle, labelRange),
|
|
43291
|
+
isPercentage
|
|
43292
|
+
};
|
|
43293
|
+
}
|
|
43294
|
+
/** Pattern H — Number + Number */
|
|
43295
|
+
function buildNumberVsNumberContext([col1, col2], getters) {
|
|
43296
|
+
const title = col1.header && col2.header ? _t("%(col2Header)s vs %(col1Header)s", {
|
|
43297
|
+
col2Header: col2.header,
|
|
43298
|
+
col1Header: col1.header
|
|
43299
|
+
}) : _t("Correlation");
|
|
43300
|
+
const hasTitle = col1.hasHeader || col2.hasHeader;
|
|
43301
|
+
const source2 = rangeSource([dataset(col2.zone, getters)], col2.hasHeader, getUnboundRange(getters, col1.zone));
|
|
43302
|
+
const sourceBoth = rangeSource([dataset(col1.zone, getters, "0"), dataset(col2.zone, getters, "1")], hasTitle);
|
|
43303
|
+
const { lastCellXC } = interestingCellsXc(col2);
|
|
43304
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(col1);
|
|
43305
|
+
return {
|
|
43306
|
+
title,
|
|
43307
|
+
source2,
|
|
43308
|
+
sourceBoth,
|
|
43309
|
+
rowCount1: col1.rowCount,
|
|
43310
|
+
rowCount2: col2.rowCount,
|
|
43311
|
+
lastCellXC,
|
|
43312
|
+
prevCellXC
|
|
43313
|
+
};
|
|
43314
|
+
}
|
|
43315
|
+
/** Pattern I — Categorical + Percentage */
|
|
43316
|
+
function buildCategoricalVsPercentageContext([catCol, pctCol], getters) {
|
|
43317
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43318
|
+
const hasTitle = pctCol.hasHeader;
|
|
43319
|
+
return {
|
|
43320
|
+
title: pctCol.header ? _t("%(percentageHeader)s by %(categoryHeader)s", {
|
|
43321
|
+
percentageHeader: pctCol.header,
|
|
43322
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43323
|
+
}) : _t("Rates by Category"),
|
|
43324
|
+
source: rangeSource([dataset(pctCol.zone, getters)], hasTitle, labelRange),
|
|
43325
|
+
rowCount: catCol.rowCount
|
|
43326
|
+
};
|
|
43327
|
+
}
|
|
43328
|
+
/** Pattern K — Label + Number */
|
|
43329
|
+
function buildLabelVsNumberContext([labelCol, numCol], getters) {
|
|
43330
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43331
|
+
const hasTitle = numCol.hasHeader;
|
|
43332
|
+
const title = numCol.header ? _t("%(numberHeader)s by %(labelHeader)s", {
|
|
43333
|
+
numberHeader: numCol.header,
|
|
43334
|
+
labelHeader: labelCol.header ?? _t("Name")
|
|
43335
|
+
}) : _t("By Name");
|
|
43336
|
+
const source = rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange);
|
|
43337
|
+
const { lastCellXC } = interestingCellsXc(numCol);
|
|
43338
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(labelCol);
|
|
43339
|
+
return {
|
|
43340
|
+
title,
|
|
43341
|
+
source,
|
|
43342
|
+
rowCount: labelCol.rowCount,
|
|
43343
|
+
lastCellXC,
|
|
43344
|
+
prevCellXC
|
|
43345
|
+
};
|
|
43346
|
+
}
|
|
43347
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
43348
|
+
function buildCategoricalVsMultipleNumbersContext([catCol, ...numCols], getters) {
|
|
43349
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43350
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43351
|
+
return {
|
|
43352
|
+
title: catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Multi-series"),
|
|
43353
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange),
|
|
43354
|
+
rowCount: catCol.rowCount
|
|
43355
|
+
};
|
|
43356
|
+
}
|
|
43357
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
43358
|
+
function buildDateVsMultipleNumbersContext([dateCol, ...numCols], getters) {
|
|
43359
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43360
|
+
const hasTitle = numCols.some((c) => c.hasHeader) || isDatasetTitled(getters, dateCol.zone);
|
|
43361
|
+
return {
|
|
43362
|
+
title: numCols.length === 1 && numCols[0].header ? _t("%(header)s over time", { header: numCols[0].header }) : _t("Multi-series over Time"),
|
|
43363
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange)
|
|
43364
|
+
};
|
|
43365
|
+
}
|
|
43366
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43367
|
+
function buildMultipleCategoricalsVsNumberContext([cat1, cat2, numCol], getters) {
|
|
43368
|
+
const title = numCol.header ? _t("%(numHeader)s by %(cat1Header)s and %(cat2Header)s", {
|
|
43369
|
+
numHeader: numCol.header,
|
|
43370
|
+
cat1Header: cat1.header ?? _t("Level 1"),
|
|
43371
|
+
cat2Header: cat2.header ?? _t("Level 2")
|
|
43372
|
+
}) : _t("Two-level hierarchy");
|
|
43373
|
+
const hasTitle = numCol.hasHeader;
|
|
43374
|
+
return {
|
|
43375
|
+
title,
|
|
43376
|
+
hierarchySource: rangeSource([dataset(cat1.zone, getters, "0"), dataset(cat2.zone, getters, "1")], hasTitle, getUnboundRange(getters, numCol.zone)),
|
|
43377
|
+
barSource: rangeSource([dataset(numCol.zone, getters)], hasTitle, getUnboundRange(getters, cat1.zone))
|
|
43378
|
+
};
|
|
43379
|
+
}
|
|
43380
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43381
|
+
function buildCategoricalDateNumberContext([catCol, dateCol, numCol], getters) {
|
|
43382
|
+
const dateRange = getUnboundRange(getters, dateCol.zone);
|
|
43383
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43384
|
+
const hasTitle = numCol.hasHeader;
|
|
43385
|
+
return {
|
|
43386
|
+
title: numCol.header ? _t("%(numHeader)s by %(catHeader)s over %(dateHeader)s", {
|
|
43387
|
+
numHeader: numCol.header,
|
|
43388
|
+
catHeader: catCol.header ?? _t("Category"),
|
|
43389
|
+
dateHeader: dateCol.header ?? _t("Time")
|
|
43390
|
+
}) : _t("Multi-series over Time"),
|
|
43391
|
+
sourceByDate: rangeSource([dataset(numCol.zone, getters)], hasTitle, dateRange),
|
|
43392
|
+
sourceByCat: rangeSource([dataset(numCol.zone, getters)], hasTitle, catRange)
|
|
43393
|
+
};
|
|
43394
|
+
}
|
|
43395
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43396
|
+
function buildLabelVsMultipleNumbersContext([labelCol, ...numCols], getters) {
|
|
43397
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43398
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43399
|
+
const title = labelCol.header ? _t("By %(header)s", { header: labelCol.header }) : _t("Profile Comparison");
|
|
43400
|
+
const source = rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange);
|
|
43401
|
+
const scatterSource = rangeSource([dataset(numCols[1].zone, getters)], numCols[1].hasHeader, getUnboundRange(getters, numCols[0].zone));
|
|
43402
|
+
const bubble = numCols.length === 3 ? {
|
|
43403
|
+
xRange: getUnboundRange(getters, numCols[0].zone),
|
|
43404
|
+
yRanges: [getUnboundRange(getters, numCols[1].zone)],
|
|
43405
|
+
sizeRange: getUnboundRange(getters, numCols[2].zone),
|
|
43406
|
+
labelRange,
|
|
43407
|
+
hasTitle
|
|
43408
|
+
} : void 0;
|
|
43409
|
+
return {
|
|
43410
|
+
title,
|
|
43411
|
+
source,
|
|
43412
|
+
rowCount: labelCol.rowCount,
|
|
43413
|
+
numColsCount: numCols.length,
|
|
43414
|
+
scatterSource,
|
|
43415
|
+
bubble
|
|
43416
|
+
};
|
|
43417
|
+
}
|
|
43418
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43419
|
+
function buildCategoricalTwoNumbersContext([catCol, numCol1, numCol2], getters) {
|
|
43420
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43421
|
+
const hasTitle = numCol1.hasHeader || numCol2.hasHeader;
|
|
43422
|
+
const title = numCol1.header && numCol2.header ? _t("%(num1Header)s vs %(num2Header)s by %(catHeader)s", {
|
|
43423
|
+
num1Header: numCol1.header,
|
|
43424
|
+
num2Header: numCol2.header,
|
|
43425
|
+
catHeader: catCol.header ?? _t("Category")
|
|
43426
|
+
}) : catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Category Comparison");
|
|
43427
|
+
const isPyramid = isPyramidLike(numCol1, numCol2);
|
|
43428
|
+
return {
|
|
43429
|
+
title,
|
|
43430
|
+
sourceBoth: rangeSource([dataset(numCol1.zone, getters, "0"), dataset(numCol2.zone, getters, "1")], hasTitle, catRange),
|
|
43431
|
+
isPyramid
|
|
43432
|
+
};
|
|
43433
|
+
}
|
|
43434
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43435
|
+
function buildManyNumbersContext(cols, getters) {
|
|
43436
|
+
const title = cols.every((c) => c.hasHeader) ? cols.map((c) => c.header).join(" / ") : _t("KPI Overview");
|
|
43437
|
+
const hasTitle = cols.some((c) => c.hasHeader);
|
|
43438
|
+
const source = rangeSource(cols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle);
|
|
43439
|
+
const { lastCellXC } = interestingCellsXc(cols[2]);
|
|
43440
|
+
const { lastCellXC: firstCellXC } = interestingCellsXc(cols[0]);
|
|
43441
|
+
const { lastCellXC: secondCellXC } = interestingCellsXc(cols[1]);
|
|
43442
|
+
return {
|
|
43443
|
+
title,
|
|
43444
|
+
source,
|
|
43445
|
+
colsLength: cols.length,
|
|
43446
|
+
rowCount: cols[0].rowCount,
|
|
43447
|
+
lastCellXC,
|
|
43448
|
+
firstCellXC,
|
|
43449
|
+
secondCellXC
|
|
43450
|
+
};
|
|
43451
|
+
}
|
|
43452
|
+
const NUMBER_OR_PERCENTAGE = ["number", "percentage"];
|
|
43453
|
+
const EXACT_PATTERNS = [
|
|
43454
|
+
{
|
|
43455
|
+
pattern: { leadingTypes: ["number"] },
|
|
43456
|
+
buildContext: buildSingleNumberContext,
|
|
43457
|
+
suggestions: SINGLE_NUMBER_COLUMN_SUGGESTIONS
|
|
43458
|
+
},
|
|
43459
|
+
{
|
|
43460
|
+
pattern: { leadingTypes: ["percentage"] },
|
|
43461
|
+
buildContext: buildSinglePercentageContext,
|
|
43462
|
+
suggestions: SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS
|
|
43463
|
+
},
|
|
43464
|
+
{
|
|
43465
|
+
pattern: { leadingTypes: ["date"] },
|
|
43466
|
+
buildContext: buildSingleDateContext,
|
|
43467
|
+
suggestions: SINGLE_DATE_COLUMN_SUGGESTIONS
|
|
43468
|
+
},
|
|
43469
|
+
{
|
|
43470
|
+
pattern: { leadingTypes: ["categorical"] },
|
|
43471
|
+
buildContext: buildSingleCategoricalContext,
|
|
43472
|
+
suggestions: SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS
|
|
43473
|
+
},
|
|
43474
|
+
{
|
|
43475
|
+
pattern: { leadingTypes: ["label"] },
|
|
43476
|
+
buildContext: buildSingleLabelContext,
|
|
43477
|
+
suggestions: SINGLE_LABEL_COLUMN_SUGGESTIONS
|
|
43478
|
+
},
|
|
43479
|
+
{
|
|
43480
|
+
pattern: { leadingTypes: ["categorical", "number"] },
|
|
43481
|
+
buildContext: buildCategoricalVsNumberContext,
|
|
43482
|
+
suggestions: CATEGORICAL_VS_NUMBER_SUGGESTIONS
|
|
43483
|
+
},
|
|
43484
|
+
{
|
|
43485
|
+
pattern: { leadingTypes: ["date", NUMBER_OR_PERCENTAGE] },
|
|
43486
|
+
buildContext: buildDateVsSeriesContext,
|
|
43487
|
+
suggestions: DATE_VS_SERIES_SUGGESTIONS
|
|
43488
|
+
},
|
|
43489
|
+
{
|
|
43490
|
+
pattern: { leadingTypes: ["number", "number"] },
|
|
43491
|
+
buildContext: buildNumberVsNumberContext,
|
|
43492
|
+
suggestions: NUMBER_VS_NUMBER_SUGGESTIONS
|
|
43493
|
+
},
|
|
43494
|
+
{
|
|
43495
|
+
pattern: { leadingTypes: ["categorical", "percentage"] },
|
|
43496
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43497
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43498
|
+
},
|
|
43499
|
+
{
|
|
43500
|
+
pattern: { leadingTypes: ["label", "number"] },
|
|
43501
|
+
buildContext: buildLabelVsNumberContext,
|
|
43502
|
+
suggestions: LABEL_VS_NUMBER_SUGGESTIONS
|
|
43503
|
+
},
|
|
43504
|
+
{
|
|
43505
|
+
pattern: { leadingTypes: ["label", "percentage"] },
|
|
43506
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43507
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43508
|
+
},
|
|
43509
|
+
{
|
|
43510
|
+
pattern: { leadingTypes: [
|
|
43511
|
+
"categorical",
|
|
43512
|
+
"categorical",
|
|
43513
|
+
"number"
|
|
43514
|
+
] },
|
|
43515
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43516
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43517
|
+
},
|
|
43518
|
+
{
|
|
43519
|
+
pattern: { leadingTypes: [
|
|
43520
|
+
"categorical",
|
|
43521
|
+
"label",
|
|
43522
|
+
"number"
|
|
43523
|
+
] },
|
|
43524
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43525
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43526
|
+
},
|
|
43527
|
+
{
|
|
43528
|
+
pattern: { leadingTypes: [
|
|
43529
|
+
"categorical",
|
|
43530
|
+
"date",
|
|
43531
|
+
NUMBER_OR_PERCENTAGE
|
|
43532
|
+
] },
|
|
43533
|
+
buildContext: buildCategoricalDateNumberContext,
|
|
43534
|
+
suggestions: CATEGORICAL_DATE_NUMBER_SUGGESTIONS
|
|
43535
|
+
},
|
|
43536
|
+
{
|
|
43537
|
+
pattern: { leadingTypes: [
|
|
43538
|
+
"categorical",
|
|
43539
|
+
NUMBER_OR_PERCENTAGE,
|
|
43540
|
+
NUMBER_OR_PERCENTAGE
|
|
43541
|
+
] },
|
|
43542
|
+
buildContext: buildCategoricalTwoNumbersContext,
|
|
43543
|
+
suggestions: CATEGORICAL_TWO_NUMBERS_SUGGESTIONS
|
|
43544
|
+
}
|
|
43545
|
+
];
|
|
43546
|
+
const EXTENDABLE_PATTERNS = [
|
|
43547
|
+
{
|
|
43548
|
+
pattern: {
|
|
43549
|
+
leadingTypes: [],
|
|
43550
|
+
extension: {
|
|
43551
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43552
|
+
minSize: 3
|
|
43553
|
+
}
|
|
43554
|
+
},
|
|
43555
|
+
buildContext: buildManyNumbersContext,
|
|
43556
|
+
suggestions: MANY_NUMBERS_SUGGESTIONS
|
|
43557
|
+
},
|
|
43558
|
+
{
|
|
43559
|
+
pattern: {
|
|
43560
|
+
leadingTypes: ["label"],
|
|
43561
|
+
extension: {
|
|
43562
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43563
|
+
minSize: 2
|
|
43564
|
+
}
|
|
43565
|
+
},
|
|
43566
|
+
buildContext: buildLabelVsMultipleNumbersContext,
|
|
43567
|
+
suggestions: LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43568
|
+
},
|
|
43569
|
+
{
|
|
43570
|
+
pattern: {
|
|
43571
|
+
leadingTypes: ["categorical"],
|
|
43572
|
+
extension: {
|
|
43573
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43574
|
+
minSize: 2
|
|
43575
|
+
}
|
|
43576
|
+
},
|
|
43577
|
+
buildContext: buildCategoricalVsMultipleNumbersContext,
|
|
43578
|
+
suggestions: CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43579
|
+
},
|
|
43580
|
+
{
|
|
43581
|
+
pattern: {
|
|
43582
|
+
leadingTypes: ["date"],
|
|
43583
|
+
extension: {
|
|
43584
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43585
|
+
minSize: 2
|
|
43586
|
+
}
|
|
43587
|
+
},
|
|
43588
|
+
buildContext: buildDateVsMultipleNumbersContext,
|
|
43589
|
+
suggestions: DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43590
|
+
}
|
|
43591
|
+
];
|
|
43592
|
+
function getChartSuggestions(zones, getters) {
|
|
43593
|
+
const cols = analyzeColumns(zones, getters);
|
|
43594
|
+
if (cols.some((c) => c.type === "error")) return [];
|
|
43595
|
+
const nonEmpty = cols.filter((c) => c.type !== "empty");
|
|
43596
|
+
if (!nonEmpty.length) return [];
|
|
43597
|
+
const shape = nonEmpty.map((c) => c.type);
|
|
43598
|
+
const rule = EXACT_PATTERNS.find((rule) => matchesShape(rule.pattern, shape)) ?? EXTENDABLE_PATTERNS.find((rule) => matchesShape(rule.pattern, shape));
|
|
43599
|
+
if (!rule) return [];
|
|
43600
|
+
const ctx = rule.buildContext(nonEmpty, getters);
|
|
43601
|
+
return rule.suggestions.filter((suggestion) => suggestion.isApplicable?.(ctx) ?? true).map((suggestion) => ({
|
|
43602
|
+
description: suggestion.description.toString(),
|
|
43603
|
+
definition: suggestion.build(ctx)
|
|
43604
|
+
}));
|
|
43605
|
+
}
|
|
43606
|
+
|
|
43607
|
+
//#endregion
|
|
43608
|
+
//#region src/components/side_panel/data_analysis/data_analysis_store.ts
|
|
43609
|
+
var DataAnalysisStore = class extends SpreadsheetStore {
|
|
43610
|
+
mutators = [];
|
|
43611
|
+
hasData = false;
|
|
43612
|
+
chartSuggestions = [];
|
|
43613
|
+
isDirty = false;
|
|
43614
|
+
ranges;
|
|
43615
|
+
constructor(get) {
|
|
43616
|
+
super(get);
|
|
43617
|
+
this.model.selection.observe(this, { handleEvent: () => this.refresh() });
|
|
43618
|
+
this.onDispose(() => {
|
|
43619
|
+
this.model.selection.unobserve(this);
|
|
43620
|
+
});
|
|
43621
|
+
this.refresh();
|
|
43622
|
+
}
|
|
43623
|
+
handle(cmd) {
|
|
43624
|
+
if (invalidateEvaluationCommands.has(cmd.type) || cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd)) this.isDirty = true;
|
|
43625
|
+
switch (cmd.type) {
|
|
43626
|
+
case "HIDE_COLUMNS_ROWS":
|
|
43627
|
+
case "UNHIDE_COLUMNS_ROWS":
|
|
43628
|
+
case "GROUP_HEADERS":
|
|
43629
|
+
case "UNGROUP_HEADERS":
|
|
43630
|
+
case "ACTIVATE_SHEET":
|
|
43631
|
+
case "ACTIVATE_NEXT_SHEET":
|
|
43632
|
+
case "ACTIVATE_PREVIOUS_SHEET":
|
|
43633
|
+
case "EVALUATE_CELLS":
|
|
43634
|
+
case "SET_FORMATTING":
|
|
43635
|
+
case "CLEAR_FORMATTING":
|
|
43636
|
+
this.isDirty = true;
|
|
43637
|
+
break;
|
|
43638
|
+
}
|
|
43639
|
+
}
|
|
43640
|
+
finalize() {
|
|
43641
|
+
if (this.isDirty) {
|
|
43642
|
+
this.refresh();
|
|
43643
|
+
this.isDirty = false;
|
|
43644
|
+
}
|
|
43645
|
+
}
|
|
43646
|
+
refresh() {
|
|
43647
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
43648
|
+
const zones = this.getters.getSelectedZones();
|
|
43649
|
+
this.ranges = zones.map(zoneToXc);
|
|
43650
|
+
this.hasData = zones.some((zone) => this.getters.getEvaluatedCellsInZone(sheetId, zone).some((cell) => cell.type !== "empty"));
|
|
43651
|
+
const suggestions = this.hasData ? getChartSuggestions(zones, this.getters) : [];
|
|
43652
|
+
this.chartSuggestions = suggestions;
|
|
43653
|
+
}
|
|
43654
|
+
};
|
|
43655
|
+
|
|
43656
|
+
//#endregion
|
|
43657
|
+
//#region src/components/side_panel/data_analysis/data_analysis_panel.ts
|
|
43658
|
+
var DataAnalysisPanel = class extends Component {
|
|
43659
|
+
static template = "o-spreadsheet-DataAnalysisPanel";
|
|
43660
|
+
static components = {
|
|
43661
|
+
Section,
|
|
43662
|
+
ChartSuggestionPreview
|
|
43663
|
+
};
|
|
43664
|
+
store;
|
|
43665
|
+
setup() {
|
|
43666
|
+
this.store = useLocalStore(DataAnalysisStore);
|
|
43667
|
+
}
|
|
43668
|
+
onStartChartSuggestionDrag(definition, ev) {
|
|
43669
|
+
startChartDragAndDrop(this.env, definition, ev);
|
|
43670
|
+
}
|
|
43671
|
+
};
|
|
43672
|
+
|
|
42145
43673
|
//#endregion
|
|
42146
43674
|
//#region src/registries/data_source_registry.ts
|
|
42147
43675
|
const unusedDataSourceRegistry = new Registry();
|
|
@@ -42218,7 +43746,7 @@ var DataSourcesCleanup = class extends Component {
|
|
|
42218
43746
|
var DataValidationPreview = class extends Component {
|
|
42219
43747
|
static template = "o-spreadsheet-DataValidationPreview";
|
|
42220
43748
|
props = (0, _odoo_owl.useProps)({ rule: types.DataValidationRule() });
|
|
42221
|
-
dvPreviewRef =
|
|
43749
|
+
dvPreviewRef = _odoo_owl.signal.ref();
|
|
42222
43750
|
setup() {
|
|
42223
43751
|
useHighlightsOnHover(this.dvPreviewRef, this);
|
|
42224
43752
|
}
|
|
@@ -43717,7 +45245,7 @@ var NamedRangePreview = class extends Component {
|
|
|
43717
45245
|
};
|
|
43718
45246
|
props = (0, _odoo_owl.useProps)({ namedRange: types.NamedRange() });
|
|
43719
45247
|
state = (0, _odoo_owl.proxy)({});
|
|
43720
|
-
namedRangePreviewRef =
|
|
45248
|
+
namedRangePreviewRef = _odoo_owl.signal.ref();
|
|
43721
45249
|
setup() {
|
|
43722
45250
|
useHighlightsOnHover(this.namedRangePreviewRef, this);
|
|
43723
45251
|
}
|
|
@@ -44208,7 +45736,7 @@ var FilterMenuValueItem = class extends Component {
|
|
|
44208
45736
|
onClick: types.function(),
|
|
44209
45737
|
scrolledTo: types.or([types.literal("top"), types.literal("bottom")]).optional()
|
|
44210
45738
|
});
|
|
44211
|
-
itemRef =
|
|
45739
|
+
itemRef = _odoo_owl.signal.ref();
|
|
44212
45740
|
setup() {
|
|
44213
45741
|
(0, _odoo_owl.onWillPatch)(() => {
|
|
44214
45742
|
if (this.props.scrolledTo) this.scrollListToSelectedValue();
|
|
@@ -44448,7 +45976,7 @@ var PivotFilterEditor = class extends Component {
|
|
|
44448
45976
|
onFiltersUpdated: types.function()
|
|
44449
45977
|
});
|
|
44450
45978
|
state;
|
|
44451
|
-
buttonFilter =
|
|
45979
|
+
buttonFilter = _odoo_owl.signal.ref();
|
|
44452
45980
|
popover;
|
|
44453
45981
|
setup() {
|
|
44454
45982
|
useExternalListener(window, "click", (ev) => {
|
|
@@ -44577,11 +46105,11 @@ var AddDimensionButton = class extends Component {
|
|
|
44577
46105
|
onFieldPicked: types.function(),
|
|
44578
46106
|
fields: types.array()
|
|
44579
46107
|
});
|
|
44580
|
-
buttonRef =
|
|
46108
|
+
buttonRef = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
44581
46109
|
popover = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
44582
46110
|
search = (0, _odoo_owl.proxy)({ input: "" });
|
|
44583
46111
|
autoComplete;
|
|
44584
|
-
autofocusRef =
|
|
46112
|
+
autofocusRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
44585
46113
|
setup() {
|
|
44586
46114
|
this.autoComplete = useLocalStore(AutoCompleteStore);
|
|
44587
46115
|
this.autoComplete.useProvider(this.getProvider());
|
|
@@ -44990,7 +46518,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
44990
46518
|
getScrollableContainerEl: types.function().optional(),
|
|
44991
46519
|
pivotId: types.UID()
|
|
44992
46520
|
});
|
|
44993
|
-
dimensionsRef =
|
|
46521
|
+
dimensionsRef = _odoo_owl.signal.ref();
|
|
44994
46522
|
dragAndDrop = useDragAndDropListItems();
|
|
44995
46523
|
AGGREGATORS = AGGREGATORS;
|
|
44996
46524
|
isDateOrDatetimeField = isDateOrDatetimeField;
|
|
@@ -45061,16 +46589,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
45061
46589
|
});
|
|
45062
46590
|
}
|
|
45063
46591
|
getDimensionElementsRects() {
|
|
45064
|
-
return Array.from(this.dimensionsRef().children).map((el) =>
|
|
45065
|
-
const style = getComputedStyle(el);
|
|
45066
|
-
const rect = el.getBoundingClientRect();
|
|
45067
|
-
return {
|
|
45068
|
-
x: rect.x,
|
|
45069
|
-
y: rect.y,
|
|
45070
|
-
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
45071
|
-
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
45072
|
-
};
|
|
45073
|
-
});
|
|
46592
|
+
return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
|
|
45074
46593
|
}
|
|
45075
46594
|
removeDimension(dimension) {
|
|
45076
46595
|
const { columns, rows } = this.props.definition;
|
|
@@ -45635,7 +47154,7 @@ var PivotSpreadsheetSidePanel = class extends Component {
|
|
|
45635
47154
|
});
|
|
45636
47155
|
store;
|
|
45637
47156
|
state;
|
|
45638
|
-
pivotSidePanelRef =
|
|
47157
|
+
pivotSidePanelRef = _odoo_owl.signal.ref();
|
|
45639
47158
|
setup() {
|
|
45640
47159
|
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
|
|
45641
47160
|
this.state = (0, _odoo_owl.proxy)({
|
|
@@ -46875,7 +48394,7 @@ var TableStylePreview = class extends Component {
|
|
|
46875
48394
|
selected: types.boolean().optional(),
|
|
46876
48395
|
onClick: types.function().optional()
|
|
46877
48396
|
});
|
|
46878
|
-
canvasRef =
|
|
48397
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
46879
48398
|
menu = (0, _odoo_owl.proxy)({
|
|
46880
48399
|
isOpen: false,
|
|
46881
48400
|
anchorRect: null,
|
|
@@ -46969,7 +48488,7 @@ var TableStylesPopover = class extends Component {
|
|
|
46969
48488
|
tableStyles: types.RecordOf(),
|
|
46970
48489
|
type: types.or([types.literal("table"), types.literal("pivot")])
|
|
46971
48490
|
});
|
|
46972
|
-
tableStyleListRef =
|
|
48491
|
+
tableStyleListRef = _odoo_owl.signal.ref();
|
|
46973
48492
|
state = (0, _odoo_owl.proxy)({ selectedCategory: this.initialSelectedCategory });
|
|
46974
48493
|
setup() {
|
|
46975
48494
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
@@ -48340,6 +49859,10 @@ sidePanelRegistry.add("ColumnStats", {
|
|
|
48340
49859
|
title: _t("Column statistics"),
|
|
48341
49860
|
Body: ColumnStatsPanel
|
|
48342
49861
|
});
|
|
49862
|
+
sidePanelRegistry.add("DataAnalysisPanel", {
|
|
49863
|
+
title: _t("Data analysis"),
|
|
49864
|
+
Body: DataAnalysisPanel
|
|
49865
|
+
});
|
|
48343
49866
|
sidePanelRegistry.add("TableSidePanel", {
|
|
48344
49867
|
title: _t("Edit table"),
|
|
48345
49868
|
Body: TablePanel,
|
|
@@ -48447,6 +49970,7 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48447
49970
|
"changePanelSize",
|
|
48448
49971
|
"resetPanelSize",
|
|
48449
49972
|
"togglePinPanel",
|
|
49973
|
+
"togglePinnedSidePanel",
|
|
48450
49974
|
"closeMainPanel",
|
|
48451
49975
|
"changeSpreadsheetWidth",
|
|
48452
49976
|
"toggleCollapsePanel"
|
|
@@ -48598,6 +50122,19 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48598
50122
|
this.secondaryPanel = void 0;
|
|
48599
50123
|
}
|
|
48600
50124
|
}
|
|
50125
|
+
togglePinnedSidePanel(componentTag, panelProps = {}) {
|
|
50126
|
+
if (this.mainPanel?.componentTag === componentTag && this.isMainPanelOpen) {
|
|
50127
|
+
this.closeMainPanel();
|
|
50128
|
+
return;
|
|
50129
|
+
}
|
|
50130
|
+
if (this.secondaryPanel?.componentTag === componentTag && this.isSecondaryPanelOpen) {
|
|
50131
|
+
this.secondaryPanel?.currentPanelProps.onCloseSidePanel?.();
|
|
50132
|
+
this.secondaryPanel = void 0;
|
|
50133
|
+
return;
|
|
50134
|
+
}
|
|
50135
|
+
this.open(componentTag, panelProps);
|
|
50136
|
+
if (!this.mainPanel?.isPinned) this.togglePinPanel();
|
|
50137
|
+
}
|
|
48601
50138
|
toggleCollapsePanel(panel) {
|
|
48602
50139
|
const panelInfo = this[panel];
|
|
48603
50140
|
if (!panelInfo) return;
|
|
@@ -48976,6 +50513,219 @@ var MenuItemRegistry = class extends Registry {
|
|
|
48976
50513
|
}
|
|
48977
50514
|
};
|
|
48978
50515
|
|
|
50516
|
+
//#endregion
|
|
50517
|
+
//#region src/helpers/sort_interactive.ts
|
|
50518
|
+
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
50519
|
+
let multiColumns = zone.right > zone.left;
|
|
50520
|
+
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
50521
|
+
multiColumns = false;
|
|
50522
|
+
let table;
|
|
50523
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
50524
|
+
table = [];
|
|
50525
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
50526
|
+
const merge = env.model.getters.getMerge({
|
|
50527
|
+
sheetId,
|
|
50528
|
+
col,
|
|
50529
|
+
row
|
|
50530
|
+
});
|
|
50531
|
+
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
50532
|
+
}
|
|
50533
|
+
if (table.length >= 2) {
|
|
50534
|
+
multiColumns = true;
|
|
50535
|
+
break;
|
|
50536
|
+
}
|
|
50537
|
+
}
|
|
50538
|
+
}
|
|
50539
|
+
if (multiColumns) {
|
|
50540
|
+
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50541
|
+
return;
|
|
50542
|
+
}
|
|
50543
|
+
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
50544
|
+
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50545
|
+
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));
|
|
50546
|
+
}
|
|
50547
|
+
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
50548
|
+
const result = env.model.dispatch("SORT_CELLS", {
|
|
50549
|
+
sheetId,
|
|
50550
|
+
col: anchor.col,
|
|
50551
|
+
row: anchor.row,
|
|
50552
|
+
zone,
|
|
50553
|
+
sortDirection,
|
|
50554
|
+
sortOptions
|
|
50555
|
+
});
|
|
50556
|
+
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
50557
|
+
const { col, row } = anchor;
|
|
50558
|
+
env.model.selection.selectZone({
|
|
50559
|
+
cell: {
|
|
50560
|
+
col,
|
|
50561
|
+
row
|
|
50562
|
+
},
|
|
50563
|
+
zone
|
|
50564
|
+
});
|
|
50565
|
+
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
50566
|
+
}
|
|
50567
|
+
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
50568
|
+
const { col, row } = anchor;
|
|
50569
|
+
env.model.selection.selectZone({
|
|
50570
|
+
cell: {
|
|
50571
|
+
col,
|
|
50572
|
+
row
|
|
50573
|
+
},
|
|
50574
|
+
zone
|
|
50575
|
+
});
|
|
50576
|
+
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
50577
|
+
}
|
|
50578
|
+
}
|
|
50579
|
+
|
|
50580
|
+
//#endregion
|
|
50581
|
+
//#region src/stores/trim_whitespace_store.ts
|
|
50582
|
+
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
50583
|
+
mutators = ["trimWhitespace"];
|
|
50584
|
+
notificationStore = this.get(NotificationStore);
|
|
50585
|
+
trimWhitespace() {
|
|
50586
|
+
this.model.dispatch("TRIM_WHITESPACE");
|
|
50587
|
+
}
|
|
50588
|
+
handle(cmd) {
|
|
50589
|
+
switch (cmd.type) {
|
|
50590
|
+
case "TRIM_WHITESPACE":
|
|
50591
|
+
this._trimWhitespace();
|
|
50592
|
+
break;
|
|
50593
|
+
}
|
|
50594
|
+
}
|
|
50595
|
+
_trimWhitespace() {
|
|
50596
|
+
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
50597
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
50598
|
+
let count = 0;
|
|
50599
|
+
for (const { col, row } of zones.map(positions).flat()) {
|
|
50600
|
+
const cell = this.getters.getCell({
|
|
50601
|
+
col,
|
|
50602
|
+
row,
|
|
50603
|
+
sheetId
|
|
50604
|
+
});
|
|
50605
|
+
if (!cell) continue;
|
|
50606
|
+
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
50607
|
+
const trimmedContent = trimContent(currentContent);
|
|
50608
|
+
if (trimmedContent !== currentContent) {
|
|
50609
|
+
count += 1;
|
|
50610
|
+
this.model.dispatch("UPDATE_CELL", {
|
|
50611
|
+
sheetId,
|
|
50612
|
+
col,
|
|
50613
|
+
row,
|
|
50614
|
+
content: trimmedContent
|
|
50615
|
+
});
|
|
50616
|
+
}
|
|
50617
|
+
}
|
|
50618
|
+
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
50619
|
+
this.notificationStore.notifyUser({
|
|
50620
|
+
type: "info",
|
|
50621
|
+
text,
|
|
50622
|
+
sticky: false
|
|
50623
|
+
});
|
|
50624
|
+
}
|
|
50625
|
+
};
|
|
50626
|
+
|
|
50627
|
+
//#endregion
|
|
50628
|
+
//#region src/actions/data_actions.ts
|
|
50629
|
+
const sortRange = {
|
|
50630
|
+
name: _t("Sort range"),
|
|
50631
|
+
isVisible: IS_ONLY_ONE_RANGE,
|
|
50632
|
+
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
50633
|
+
};
|
|
50634
|
+
const sortAscending = {
|
|
50635
|
+
name: _t("Ascending (A ⟶ Z)"),
|
|
50636
|
+
execute: (env) => {
|
|
50637
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50638
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
50639
|
+
},
|
|
50640
|
+
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
50641
|
+
};
|
|
50642
|
+
const dataCleanup = {
|
|
50643
|
+
name: _t("Data cleanup"),
|
|
50644
|
+
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
50645
|
+
};
|
|
50646
|
+
const removeDuplicates = {
|
|
50647
|
+
name: _t("Remove duplicates"),
|
|
50648
|
+
execute: (env) => {
|
|
50649
|
+
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
50650
|
+
env.openSidePanel("RemoveDuplicates", {});
|
|
50651
|
+
},
|
|
50652
|
+
isEnabled: (env) => !env.isSmall
|
|
50653
|
+
};
|
|
50654
|
+
const trimWhitespace = {
|
|
50655
|
+
name: _t("Trim whitespace"),
|
|
50656
|
+
execute: (env) => {
|
|
50657
|
+
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
50658
|
+
}
|
|
50659
|
+
};
|
|
50660
|
+
const cleanupDataSources = {
|
|
50661
|
+
name: _t("Remove unused data sources"),
|
|
50662
|
+
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
50663
|
+
isEnabled: (env) => !env.isSmall
|
|
50664
|
+
};
|
|
50665
|
+
const sortDescending = {
|
|
50666
|
+
name: _t("Descending (Z ⟶ A)"),
|
|
50667
|
+
execute: (env) => {
|
|
50668
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50669
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
50670
|
+
},
|
|
50671
|
+
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
50672
|
+
};
|
|
50673
|
+
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
50674
|
+
const createRemoveFilterTool = {
|
|
50675
|
+
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
50676
|
+
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
50677
|
+
};
|
|
50678
|
+
const splitToColumns = {
|
|
50679
|
+
name: _t("Split text to columns"),
|
|
50680
|
+
sequence: 1,
|
|
50681
|
+
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
50682
|
+
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
50683
|
+
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
50684
|
+
};
|
|
50685
|
+
const dataAnalysis = {
|
|
50686
|
+
name: _t("Data analysis"),
|
|
50687
|
+
execute: (env) => {
|
|
50688
|
+
env.getStore(SidePanelStore).togglePinnedSidePanel("DataAnalysisPanel");
|
|
50689
|
+
},
|
|
50690
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS",
|
|
50691
|
+
isEnabled: (env) => !env.isSmall
|
|
50692
|
+
};
|
|
50693
|
+
const columnStatistics = {
|
|
50694
|
+
name: _t("Column statistics"),
|
|
50695
|
+
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
50696
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
50697
|
+
};
|
|
50698
|
+
const reinsertDynamicPivotMenu = {
|
|
50699
|
+
id: "reinsert_dynamic_pivot",
|
|
50700
|
+
name: _t("Re-insert dynamic pivot"),
|
|
50701
|
+
sequence: 60,
|
|
50702
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50703
|
+
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
50704
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50705
|
+
};
|
|
50706
|
+
const reinsertStaticPivotMenu = {
|
|
50707
|
+
id: "reinsert_static_pivot",
|
|
50708
|
+
name: _t("Re-insert static pivot"),
|
|
50709
|
+
sequence: 70,
|
|
50710
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50711
|
+
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
50712
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50713
|
+
};
|
|
50714
|
+
const calculationMode = {
|
|
50715
|
+
name: _t("Calculation"),
|
|
50716
|
+
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
50717
|
+
};
|
|
50718
|
+
const automaticCalculation = {
|
|
50719
|
+
name: _t("Automatic"),
|
|
50720
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
50721
|
+
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
50722
|
+
};
|
|
50723
|
+
const manualCalculation = {
|
|
50724
|
+
name: _t("Manual"),
|
|
50725
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
50726
|
+
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
50727
|
+
};
|
|
50728
|
+
|
|
48979
50729
|
//#endregion
|
|
48980
50730
|
//#region src/helpers/ui/merge_interactive.ts
|
|
48981
50731
|
const AddMergeInteractiveContent = {
|
|
@@ -49190,6 +50940,10 @@ cellMenuRegistry.add("cut", {
|
|
|
49190
50940
|
}).addChild("paste_format_only", ["paste_special"], {
|
|
49191
50941
|
...pasteSpecialFormat,
|
|
49192
50942
|
sequence: 20
|
|
50943
|
+
}).add("data_analysis", {
|
|
50944
|
+
...dataAnalysis,
|
|
50945
|
+
sequence: 50,
|
|
50946
|
+
separator: true
|
|
49193
50947
|
}).add("add_row_before", {
|
|
49194
50948
|
...cellInsertRowsBefore,
|
|
49195
50949
|
sequence: 70
|
|
@@ -49297,211 +51051,6 @@ cellMenuRegistry.add("cut", {
|
|
|
49297
51051
|
sequence: 30
|
|
49298
51052
|
});
|
|
49299
51053
|
|
|
49300
|
-
//#endregion
|
|
49301
|
-
//#region src/helpers/sort_interactive.ts
|
|
49302
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
49303
|
-
let multiColumns = zone.right > zone.left;
|
|
49304
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
49305
|
-
multiColumns = false;
|
|
49306
|
-
let table;
|
|
49307
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
49308
|
-
table = [];
|
|
49309
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
49310
|
-
const merge = env.model.getters.getMerge({
|
|
49311
|
-
sheetId,
|
|
49312
|
-
col,
|
|
49313
|
-
row
|
|
49314
|
-
});
|
|
49315
|
-
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
49316
|
-
}
|
|
49317
|
-
if (table.length >= 2) {
|
|
49318
|
-
multiColumns = true;
|
|
49319
|
-
break;
|
|
49320
|
-
}
|
|
49321
|
-
}
|
|
49322
|
-
}
|
|
49323
|
-
if (multiColumns) {
|
|
49324
|
-
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49325
|
-
return;
|
|
49326
|
-
}
|
|
49327
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
49328
|
-
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49329
|
-
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));
|
|
49330
|
-
}
|
|
49331
|
-
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
49332
|
-
const result = env.model.dispatch("SORT_CELLS", {
|
|
49333
|
-
sheetId,
|
|
49334
|
-
col: anchor.col,
|
|
49335
|
-
row: anchor.row,
|
|
49336
|
-
zone,
|
|
49337
|
-
sortDirection,
|
|
49338
|
-
sortOptions
|
|
49339
|
-
});
|
|
49340
|
-
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
49341
|
-
const { col, row } = anchor;
|
|
49342
|
-
env.model.selection.selectZone({
|
|
49343
|
-
cell: {
|
|
49344
|
-
col,
|
|
49345
|
-
row
|
|
49346
|
-
},
|
|
49347
|
-
zone
|
|
49348
|
-
});
|
|
49349
|
-
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
49350
|
-
}
|
|
49351
|
-
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
49352
|
-
const { col, row } = anchor;
|
|
49353
|
-
env.model.selection.selectZone({
|
|
49354
|
-
cell: {
|
|
49355
|
-
col,
|
|
49356
|
-
row
|
|
49357
|
-
},
|
|
49358
|
-
zone
|
|
49359
|
-
});
|
|
49360
|
-
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
49361
|
-
}
|
|
49362
|
-
}
|
|
49363
|
-
|
|
49364
|
-
//#endregion
|
|
49365
|
-
//#region src/stores/trim_whitespace_store.ts
|
|
49366
|
-
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
49367
|
-
mutators = ["trimWhitespace"];
|
|
49368
|
-
notificationStore = this.get(NotificationStore);
|
|
49369
|
-
trimWhitespace() {
|
|
49370
|
-
this.model.dispatch("TRIM_WHITESPACE");
|
|
49371
|
-
}
|
|
49372
|
-
handle(cmd) {
|
|
49373
|
-
switch (cmd.type) {
|
|
49374
|
-
case "TRIM_WHITESPACE":
|
|
49375
|
-
this._trimWhitespace();
|
|
49376
|
-
break;
|
|
49377
|
-
}
|
|
49378
|
-
}
|
|
49379
|
-
_trimWhitespace() {
|
|
49380
|
-
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
49381
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
49382
|
-
let count = 0;
|
|
49383
|
-
for (const { col, row } of zones.map(positions).flat()) {
|
|
49384
|
-
const cell = this.getters.getCell({
|
|
49385
|
-
col,
|
|
49386
|
-
row,
|
|
49387
|
-
sheetId
|
|
49388
|
-
});
|
|
49389
|
-
if (!cell) continue;
|
|
49390
|
-
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
49391
|
-
const trimmedContent = trimContent(currentContent);
|
|
49392
|
-
if (trimmedContent !== currentContent) {
|
|
49393
|
-
count += 1;
|
|
49394
|
-
this.model.dispatch("UPDATE_CELL", {
|
|
49395
|
-
sheetId,
|
|
49396
|
-
col,
|
|
49397
|
-
row,
|
|
49398
|
-
content: trimmedContent
|
|
49399
|
-
});
|
|
49400
|
-
}
|
|
49401
|
-
}
|
|
49402
|
-
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
49403
|
-
this.notificationStore.notifyUser({
|
|
49404
|
-
type: "info",
|
|
49405
|
-
text,
|
|
49406
|
-
sticky: false
|
|
49407
|
-
});
|
|
49408
|
-
}
|
|
49409
|
-
};
|
|
49410
|
-
|
|
49411
|
-
//#endregion
|
|
49412
|
-
//#region src/actions/data_actions.ts
|
|
49413
|
-
const sortRange = {
|
|
49414
|
-
name: _t("Sort range"),
|
|
49415
|
-
isVisible: IS_ONLY_ONE_RANGE,
|
|
49416
|
-
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
49417
|
-
};
|
|
49418
|
-
const sortAscending = {
|
|
49419
|
-
name: _t("Ascending (A ⟶ Z)"),
|
|
49420
|
-
execute: (env) => {
|
|
49421
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49422
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
49423
|
-
},
|
|
49424
|
-
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
49425
|
-
};
|
|
49426
|
-
const dataCleanup = {
|
|
49427
|
-
name: _t("Data cleanup"),
|
|
49428
|
-
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
49429
|
-
};
|
|
49430
|
-
const removeDuplicates = {
|
|
49431
|
-
name: _t("Remove duplicates"),
|
|
49432
|
-
execute: (env) => {
|
|
49433
|
-
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
49434
|
-
env.openSidePanel("RemoveDuplicates", {});
|
|
49435
|
-
},
|
|
49436
|
-
isEnabled: (env) => !env.isSmall
|
|
49437
|
-
};
|
|
49438
|
-
const trimWhitespace = {
|
|
49439
|
-
name: _t("Trim whitespace"),
|
|
49440
|
-
execute: (env) => {
|
|
49441
|
-
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
49442
|
-
}
|
|
49443
|
-
};
|
|
49444
|
-
const cleanupDataSources = {
|
|
49445
|
-
name: _t("Remove unused data sources"),
|
|
49446
|
-
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
49447
|
-
isEnabled: (env) => !env.isSmall
|
|
49448
|
-
};
|
|
49449
|
-
const sortDescending = {
|
|
49450
|
-
name: _t("Descending (Z ⟶ A)"),
|
|
49451
|
-
execute: (env) => {
|
|
49452
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49453
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
49454
|
-
},
|
|
49455
|
-
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
49456
|
-
};
|
|
49457
|
-
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
49458
|
-
const createRemoveFilterTool = {
|
|
49459
|
-
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
49460
|
-
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
49461
|
-
};
|
|
49462
|
-
const splitToColumns = {
|
|
49463
|
-
name: _t("Split text to columns"),
|
|
49464
|
-
sequence: 1,
|
|
49465
|
-
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
49466
|
-
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
49467
|
-
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
49468
|
-
};
|
|
49469
|
-
const columnStatistics = {
|
|
49470
|
-
name: _t("Column statistics"),
|
|
49471
|
-
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
49472
|
-
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
49473
|
-
};
|
|
49474
|
-
const reinsertDynamicPivotMenu = {
|
|
49475
|
-
id: "reinsert_dynamic_pivot",
|
|
49476
|
-
name: _t("Re-insert dynamic pivot"),
|
|
49477
|
-
sequence: 60,
|
|
49478
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49479
|
-
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
49480
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49481
|
-
};
|
|
49482
|
-
const reinsertStaticPivotMenu = {
|
|
49483
|
-
id: "reinsert_static_pivot",
|
|
49484
|
-
name: _t("Re-insert static pivot"),
|
|
49485
|
-
sequence: 70,
|
|
49486
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49487
|
-
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
49488
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49489
|
-
};
|
|
49490
|
-
const calculationMode = {
|
|
49491
|
-
name: _t("Calculation"),
|
|
49492
|
-
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
49493
|
-
};
|
|
49494
|
-
const automaticCalculation = {
|
|
49495
|
-
name: _t("Automatic"),
|
|
49496
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
49497
|
-
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
49498
|
-
};
|
|
49499
|
-
const manualCalculation = {
|
|
49500
|
-
name: _t("Manual"),
|
|
49501
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
49502
|
-
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
49503
|
-
};
|
|
49504
|
-
|
|
49505
51054
|
//#endregion
|
|
49506
51055
|
//#region src/registries/menus/col_menu_registry.ts
|
|
49507
51056
|
const colMenuRegistry = new MenuItemRegistry();
|
|
@@ -49535,6 +51084,9 @@ colMenuRegistry.add("cut", {
|
|
|
49535
51084
|
}).addChild("sort_descending", ["sort_columns"], {
|
|
49536
51085
|
...sortDescending,
|
|
49537
51086
|
sequence: 20
|
|
51087
|
+
}).add("data_analysis", {
|
|
51088
|
+
...dataAnalysis,
|
|
51089
|
+
sequence: 55
|
|
49538
51090
|
}).add("column_statistics", {
|
|
49539
51091
|
...columnStatistics,
|
|
49540
51092
|
sequence: 60,
|
|
@@ -52350,14 +53902,15 @@ var FiguresContainer = class extends Component {
|
|
|
52350
53902
|
selectedRect: void 0,
|
|
52351
53903
|
horizontalSnap: void 0,
|
|
52352
53904
|
verticalSnap: void 0,
|
|
52353
|
-
cancelDnd: void 0
|
|
52354
|
-
overlappingChartOrCarousel: void 0
|
|
53905
|
+
cancelDnd: void 0
|
|
52355
53906
|
});
|
|
52356
53907
|
viewStore;
|
|
52357
53908
|
zoomStore;
|
|
53909
|
+
chartDragStore;
|
|
52358
53910
|
setup() {
|
|
52359
53911
|
this.viewStore = useStore(ViewportsStore);
|
|
52360
53912
|
this.zoomStore = useStore(ZoomStore);
|
|
53913
|
+
this.chartDragStore = useStore(ChartDragStore);
|
|
52361
53914
|
(0, _odoo_owl.onMounted)(() => {
|
|
52362
53915
|
this.render();
|
|
52363
53916
|
});
|
|
@@ -52371,7 +53924,7 @@ var FiguresContainer = class extends Component {
|
|
|
52371
53924
|
this.dnd.selectedRect = void 0;
|
|
52372
53925
|
this.dnd.horizontalSnap = void 0;
|
|
52373
53926
|
this.dnd.verticalSnap = void 0;
|
|
52374
|
-
this.
|
|
53927
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52375
53928
|
this.dnd.cancelDnd = void 0;
|
|
52376
53929
|
}
|
|
52377
53930
|
});
|
|
@@ -52497,6 +54050,7 @@ var FiguresContainer = class extends Component {
|
|
|
52497
54050
|
maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
|
|
52498
54051
|
};
|
|
52499
54052
|
let hasStartedDnd = false;
|
|
54053
|
+
let overlappingChartOrCarousel = void 0;
|
|
52500
54054
|
const onMouseMove = (ev) => {
|
|
52501
54055
|
const currentMousePosition = {
|
|
52502
54056
|
x: ev.clientX / zoom,
|
|
@@ -52508,10 +54062,10 @@ var FiguresContainer = class extends Component {
|
|
|
52508
54062
|
hasStartedDnd = true;
|
|
52509
54063
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
|
|
52510
54064
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
52511
|
-
|
|
54065
|
+
overlappingChartOrCarousel = void 0;
|
|
52512
54066
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
52513
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel =
|
|
52514
|
-
this.
|
|
54067
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
|
|
54068
|
+
this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
|
|
52515
54069
|
if (!overlappingChartOrCarousel) {
|
|
52516
54070
|
const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
|
|
52517
54071
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
@@ -52533,7 +54087,7 @@ var FiguresContainer = class extends Component {
|
|
|
52533
54087
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
52534
54088
|
return;
|
|
52535
54089
|
}
|
|
52536
|
-
if (!
|
|
54090
|
+
if (!overlappingChartOrCarousel) {
|
|
52537
54091
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
52538
54092
|
return {
|
|
52539
54093
|
sheetId,
|
|
@@ -52543,14 +54097,14 @@ var FiguresContainer = class extends Component {
|
|
|
52543
54097
|
}) || [];
|
|
52544
54098
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
52545
54099
|
} else {
|
|
52546
|
-
const overlappingFigureId =
|
|
54100
|
+
const overlappingFigureId = overlappingChartOrCarousel.id;
|
|
52547
54101
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
52548
|
-
if (
|
|
54102
|
+
if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
52549
54103
|
sheetId,
|
|
52550
54104
|
carouselFigureId: overlappingFigureId,
|
|
52551
54105
|
chartFigureIds
|
|
52552
54106
|
});
|
|
52553
|
-
else if (
|
|
54107
|
+
else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
52554
54108
|
sheetId,
|
|
52555
54109
|
baseFigureId: overlappingFigureId,
|
|
52556
54110
|
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
@@ -52561,7 +54115,7 @@ var FiguresContainer = class extends Component {
|
|
|
52561
54115
|
this.dnd.selectedRect = void 0;
|
|
52562
54116
|
this.dnd.horizontalSnap = void 0;
|
|
52563
54117
|
this.dnd.verticalSnap = void 0;
|
|
52564
|
-
this.
|
|
54118
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52565
54119
|
};
|
|
52566
54120
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52567
54121
|
}
|
|
@@ -52617,7 +54171,6 @@ var FiguresContainer = class extends Component {
|
|
|
52617
54171
|
this.dnd.selectedRect = void 0;
|
|
52618
54172
|
this.dnd.horizontalSnap = void 0;
|
|
52619
54173
|
this.dnd.verticalSnap = void 0;
|
|
52620
|
-
this.dnd.overlappingChartOrCarousel = void 0;
|
|
52621
54174
|
};
|
|
52622
54175
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52623
54176
|
}
|
|
@@ -52627,12 +54180,12 @@ var FiguresContainer = class extends Component {
|
|
|
52627
54180
|
getFigureStyle(figureUI) {
|
|
52628
54181
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
52629
54182
|
return cssPropertiesToCss({
|
|
52630
|
-
opacity: this.
|
|
54183
|
+
opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
|
|
52631
54184
|
cursor: "grabbing"
|
|
52632
54185
|
});
|
|
52633
54186
|
}
|
|
52634
54187
|
getFigureClass(figureUI) {
|
|
52635
|
-
if (figureUI.id !== this.
|
|
54188
|
+
if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
|
|
52636
54189
|
return "o-add-to-carousel";
|
|
52637
54190
|
}
|
|
52638
54191
|
getSnap(snapLine) {
|
|
@@ -52675,26 +54228,6 @@ var FiguresContainer = class extends Component {
|
|
|
52675
54228
|
height: `100%`
|
|
52676
54229
|
});
|
|
52677
54230
|
}
|
|
52678
|
-
getOverlappingFigure(figureUI, otherFigures) {
|
|
52679
|
-
if (figureUI.tag !== "chart") return;
|
|
52680
|
-
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
52681
|
-
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
52682
|
-
let bestMatch;
|
|
52683
|
-
let smallestDistance = Infinity;
|
|
52684
|
-
for (const figure of otherFigures) {
|
|
52685
|
-
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
52686
|
-
const targetCenterX = figure.x + figure.width / 2;
|
|
52687
|
-
const targetCenterY = figure.y + figure.height / 2;
|
|
52688
|
-
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
52689
|
-
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
52690
|
-
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
52691
|
-
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
52692
|
-
smallestDistance = squaredDistance;
|
|
52693
|
-
bestMatch = figure;
|
|
52694
|
-
}
|
|
52695
|
-
}
|
|
52696
|
-
return bestMatch;
|
|
52697
|
-
}
|
|
52698
54231
|
};
|
|
52699
54232
|
|
|
52700
54233
|
//#endregion
|
|
@@ -52994,7 +54527,7 @@ var AbstractResizer = class extends Component {
|
|
|
52994
54527
|
var ColResizer = class extends AbstractResizer {
|
|
52995
54528
|
static template = "o-spreadsheet-ColResizer";
|
|
52996
54529
|
static components = { UnhideColumnHeaders };
|
|
52997
|
-
colResizerRef =
|
|
54530
|
+
colResizerRef = _odoo_owl.signal.ref();
|
|
52998
54531
|
setup() {
|
|
52999
54532
|
super.setup();
|
|
53000
54533
|
this.PADDING = 15;
|
|
@@ -53128,7 +54661,7 @@ var ColResizer = class extends AbstractResizer {
|
|
|
53128
54661
|
var RowResizer = class extends AbstractResizer {
|
|
53129
54662
|
static template = "o-spreadsheet-RowResizer";
|
|
53130
54663
|
static components = { UnhideRowHeaders };
|
|
53131
|
-
rowResizerRef =
|
|
54664
|
+
rowResizerRef = _odoo_owl.signal.ref();
|
|
53132
54665
|
setup() {
|
|
53133
54666
|
super.setup();
|
|
53134
54667
|
this.PADDING = 5;
|
|
@@ -53737,9 +55270,8 @@ var TableResizer = class extends Component {
|
|
|
53737
55270
|
useStore(TableResizeStore);
|
|
53738
55271
|
}
|
|
53739
55272
|
get containerStyle() {
|
|
53740
|
-
const tableZone = this.props.table.range.zone;
|
|
53741
55273
|
const sheetId = this.props.table.range.sheetId;
|
|
53742
|
-
|
|
55274
|
+
const tableZone = this.props.table.range.zone;
|
|
53743
55275
|
const bottomRight = {
|
|
53744
55276
|
...tableZone,
|
|
53745
55277
|
left: tableZone.right,
|
|
@@ -53826,8 +55358,8 @@ var Grid = class extends Component {
|
|
|
53826
55358
|
HEADER_HEIGHT = 26;
|
|
53827
55359
|
HEADER_WIDTH = 48;
|
|
53828
55360
|
menuState;
|
|
53829
|
-
gridRef =
|
|
53830
|
-
canvasRef =
|
|
55361
|
+
gridRef = _odoo_owl.signal.ref();
|
|
55362
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
53831
55363
|
highlightStore;
|
|
53832
55364
|
viewStore;
|
|
53833
55365
|
zoomStore;
|
|
@@ -54458,6 +55990,9 @@ var Grid = class extends Component {
|
|
|
54458
55990
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
54459
55991
|
return this.env.model.getters.getClientsToDisplay(sheetId);
|
|
54460
55992
|
}
|
|
55993
|
+
get displayTableResizer() {
|
|
55994
|
+
return this.env.model.getters.isGridSelectionActive() && !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
55995
|
+
}
|
|
54461
55996
|
};
|
|
54462
55997
|
|
|
54463
55998
|
//#endregion
|
|
@@ -54476,7 +56011,7 @@ var FullScreenFigure = class extends Component {
|
|
|
54476
56011
|
static template = "o-spreadsheet-FullScreenFigure";
|
|
54477
56012
|
static components = { ChartFigure };
|
|
54478
56013
|
fullScreenFigureStore;
|
|
54479
|
-
fullScreenFigureRef =
|
|
56014
|
+
fullScreenFigureRef = _odoo_owl.signal.ref();
|
|
54480
56015
|
spreadsheetRect = useSpreadsheetRect();
|
|
54481
56016
|
figureRegistry = figureRegistry;
|
|
54482
56017
|
setup() {
|
|
@@ -54724,6 +56259,41 @@ var PivotHTMLRenderer = class extends Component {
|
|
|
54724
56259
|
}
|
|
54725
56260
|
};
|
|
54726
56261
|
|
|
56262
|
+
//#endregion
|
|
56263
|
+
//#region src/components/tables/hovered_table_store.ts
|
|
56264
|
+
var HoveredTableStore = class extends SpreadsheetStore {
|
|
56265
|
+
mutators = ["hover"];
|
|
56266
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
56267
|
+
position;
|
|
56268
|
+
constructor(get) {
|
|
56269
|
+
super(get);
|
|
56270
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
56271
|
+
cellHoverOverlayStore.register(this);
|
|
56272
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
56273
|
+
}
|
|
56274
|
+
getHighlightedPositions(hoveredPosition) {
|
|
56275
|
+
const highlightedPositions = [];
|
|
56276
|
+
const { sheetId, row } = hoveredPosition;
|
|
56277
|
+
const table = this.getters.getTable(hoveredPosition);
|
|
56278
|
+
if (!table) return highlightedPositions;
|
|
56279
|
+
const { left, right, top } = table.range.zone;
|
|
56280
|
+
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
56281
|
+
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
56282
|
+
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
56283
|
+
sheetId,
|
|
56284
|
+
col,
|
|
56285
|
+
row
|
|
56286
|
+
}).formattedValue;
|
|
56287
|
+
});
|
|
56288
|
+
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) highlightedPositions.push({
|
|
56289
|
+
sheetId,
|
|
56290
|
+
col,
|
|
56291
|
+
row
|
|
56292
|
+
});
|
|
56293
|
+
return highlightedPositions;
|
|
56294
|
+
}
|
|
56295
|
+
};
|
|
56296
|
+
|
|
54727
56297
|
//#endregion
|
|
54728
56298
|
//#region src/helpers/figures/charts/runtime/helpers_index.ts
|
|
54729
56299
|
var helpers_index_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -55514,7 +57084,8 @@ var Squisher = class {
|
|
|
55514
57084
|
* The result of this method is:
|
|
55515
57085
|
* - if the cell is not a formula, returns the content as is and resets the base formula
|
|
55516
57086
|
* - if the cell is a formula:
|
|
55517
|
-
* - if there is no previous formula, or the normalized formula is different from the previous one
|
|
57087
|
+
* - if there is no previous formula, or the normalized formula is different from the previous one or it contains blacklisted functions (see NonSquishableFunctionRegistry),
|
|
57088
|
+
* resets the base formula to this one and returns the full formula string
|
|
55518
57089
|
* - else, compares the literal values and range dependencies to the previous formula, and for each parameter:
|
|
55519
57090
|
* - for numbers: returns a relative change (+N or -N) if possible, else the full number or "=" if unchanged
|
|
55520
57091
|
* - for strings: returns the full string if changed, else "="
|
|
@@ -55522,6 +57093,10 @@ var Squisher = class {
|
|
|
55522
57093
|
* */
|
|
55523
57094
|
squish(cell, forSheetId) {
|
|
55524
57095
|
if (cell.isFormula) {
|
|
57096
|
+
if (NonSquishableFunctionRegistry.getAll().some((functionName) => cell.compiledFormula.usesSymbol(functionName))) {
|
|
57097
|
+
this.resetBaseFormula();
|
|
57098
|
+
return cell.compiledFormula.toFormulaString(this.getters, this.rangeToStringOptions);
|
|
57099
|
+
}
|
|
55525
57100
|
let numbers = [];
|
|
55526
57101
|
let strings = [];
|
|
55527
57102
|
let references = [];
|
|
@@ -65508,7 +67083,7 @@ var Session = class extends EventBus {
|
|
|
65508
67083
|
}
|
|
65509
67084
|
message = {
|
|
65510
67085
|
...message,
|
|
65511
|
-
commands:
|
|
67086
|
+
commands: revision.commands
|
|
65512
67087
|
};
|
|
65513
67088
|
}
|
|
65514
67089
|
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.
|
|
@@ -68005,6 +69580,12 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
68005
69580
|
const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
|
|
68006
69581
|
const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
68007
69582
|
if (figures.some((f) => f === void 0 || f.tag !== "chart") || !baseFigureId || baseFigureId.tag !== "chart") return "FigureDoesNotExist";
|
|
69583
|
+
break;
|
|
69584
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69585
|
+
const baseFigure = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69586
|
+
if (this.getters.getFigure(cmd.sheetId, cmd.figureId) || !baseFigure) return "InvalidFigureId";
|
|
69587
|
+
if (baseFigure.tag !== "chart") return "FigureDoesNotExist";
|
|
69588
|
+
break;
|
|
68008
69589
|
}
|
|
68009
69590
|
return "Success";
|
|
68010
69591
|
}
|
|
@@ -68040,6 +69621,29 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
68040
69621
|
carouselFigureId,
|
|
68041
69622
|
chartFigureIds: cmd.chartFigureIds
|
|
68042
69623
|
});
|
|
69624
|
+
break;
|
|
69625
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69626
|
+
const baseFigureToMerge = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69627
|
+
if (!baseFigureToMerge) throw new Error(`Figure ${cmd.baseFigureId} does not exists.`);
|
|
69628
|
+
this.dispatch("CREATE_CHART", {
|
|
69629
|
+
chartId: cmd.chartId,
|
|
69630
|
+
figureId: cmd.figureId,
|
|
69631
|
+
sheetId: cmd.sheetId,
|
|
69632
|
+
definition: cmd.definition,
|
|
69633
|
+
col: baseFigureToMerge.col,
|
|
69634
|
+
row: baseFigureToMerge.row,
|
|
69635
|
+
offset: baseFigureToMerge.offset,
|
|
69636
|
+
size: {
|
|
69637
|
+
width: baseFigureToMerge.width,
|
|
69638
|
+
height: baseFigureToMerge.height
|
|
69639
|
+
}
|
|
69640
|
+
});
|
|
69641
|
+
this.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
69642
|
+
sheetId: cmd.sheetId,
|
|
69643
|
+
baseFigureId: cmd.baseFigureId,
|
|
69644
|
+
chartFigureIds: [cmd.baseFigureId, cmd.figureId]
|
|
69645
|
+
});
|
|
69646
|
+
break;
|
|
68043
69647
|
}
|
|
68044
69648
|
}
|
|
68045
69649
|
getFigureUI(sheetId, figure) {
|
|
@@ -69729,6 +71333,9 @@ topbarMenuRegistry.add("file", {
|
|
|
69729
71333
|
}).addChild("split_to_columns", ["data"], {
|
|
69730
71334
|
...splitToColumns,
|
|
69731
71335
|
sequence: 20
|
|
71336
|
+
}).addChild("data_analysis", ["data"], {
|
|
71337
|
+
...dataAnalysis,
|
|
71338
|
+
sequence: 24
|
|
69732
71339
|
}).addChild("column_statistics", ["data"], {
|
|
69733
71340
|
...columnStatistics,
|
|
69734
71341
|
sequence: 25
|
|
@@ -70130,7 +71737,7 @@ var RippleEffect = class extends Component {
|
|
|
70130
71737
|
onAnimationEnd: types.function(),
|
|
70131
71738
|
style: types.string()
|
|
70132
71739
|
});
|
|
70133
|
-
rippleRef =
|
|
71740
|
+
rippleRef = _odoo_owl.signal.ref();
|
|
70134
71741
|
setup() {
|
|
70135
71742
|
let animation = void 0;
|
|
70136
71743
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -70184,7 +71791,7 @@ var Ripple = class extends Component {
|
|
|
70184
71791
|
onAnimationEnd: types.function().optional(() => () => {}),
|
|
70185
71792
|
class: types.string().optional("")
|
|
70186
71793
|
});
|
|
70187
|
-
childContainerRef =
|
|
71794
|
+
childContainerRef = _odoo_owl.signal.ref();
|
|
70188
71795
|
state = (0, _odoo_owl.proxy)({ ripples: [] });
|
|
70189
71796
|
currentId = 1;
|
|
70190
71797
|
onClick(ev) {
|
|
@@ -70455,9 +72062,9 @@ var BottomBarSheet = class extends Component {
|
|
|
70455
72062
|
isEditing: false,
|
|
70456
72063
|
openedPicker: void 0
|
|
70457
72064
|
});
|
|
70458
|
-
sheetDivRef =
|
|
70459
|
-
iconRef =
|
|
70460
|
-
sheetNameRef =
|
|
72065
|
+
sheetDivRef = _odoo_owl.signal.ref();
|
|
72066
|
+
iconRef = _odoo_owl.signal.ref();
|
|
72067
|
+
sheetNameRef = _odoo_owl.signal.ref();
|
|
70461
72068
|
editionState = "initializing";
|
|
70462
72069
|
DOMFocusableElementStore;
|
|
70463
72070
|
setup() {
|
|
@@ -70741,8 +72348,10 @@ var BottomBarStatistic = class extends Component {
|
|
|
70741
72348
|
});
|
|
70742
72349
|
state = (0, _odoo_owl.proxy)({ selectedStatisticFn: "" });
|
|
70743
72350
|
store;
|
|
72351
|
+
sidePanelStore;
|
|
70744
72352
|
setup() {
|
|
70745
72353
|
this.store = useStore(AggregateStatisticsStore);
|
|
72354
|
+
this.sidePanelStore = useStore(SidePanelStore);
|
|
70746
72355
|
(0, _odoo_owl.onWillUpdateProps)(() => {
|
|
70747
72356
|
if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
|
|
70748
72357
|
});
|
|
@@ -70782,6 +72391,12 @@ var BottomBarStatistic = class extends Component {
|
|
|
70782
72391
|
format: fnValue.format()
|
|
70783
72392
|
});
|
|
70784
72393
|
}
|
|
72394
|
+
get isDataAnalysisOpen() {
|
|
72395
|
+
return this.sidePanelStore.mainPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isMainPanelOpen || this.sidePanelStore.secondaryPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isSecondaryPanelOpen;
|
|
72396
|
+
}
|
|
72397
|
+
toggleDataAnalysisPanel() {
|
|
72398
|
+
this.sidePanelStore.togglePinnedSidePanel("DataAnalysisPanel");
|
|
72399
|
+
}
|
|
70785
72400
|
};
|
|
70786
72401
|
|
|
70787
72402
|
//#endregion
|
|
@@ -70796,8 +72411,8 @@ var BottomBar = class extends Component {
|
|
|
70796
72411
|
BottomBarSheet,
|
|
70797
72412
|
BottomBarStatistic
|
|
70798
72413
|
};
|
|
70799
|
-
bottomBarRef =
|
|
70800
|
-
sheetListRef =
|
|
72414
|
+
bottomBarRef = _odoo_owl.signal.ref();
|
|
72415
|
+
sheetListRef = _odoo_owl.signal.ref();
|
|
70801
72416
|
dragAndDrop = useDragAndDropListItems();
|
|
70802
72417
|
targetScroll = void 0;
|
|
70803
72418
|
state = (0, _odoo_owl.proxy)({
|
|
@@ -71010,12 +72625,13 @@ var SpreadsheetDashboard = class extends Component {
|
|
|
71010
72625
|
hoveredCell;
|
|
71011
72626
|
viewStore;
|
|
71012
72627
|
zoomStore;
|
|
71013
|
-
gridRef =
|
|
71014
|
-
canvasRef =
|
|
72628
|
+
gridRef = _odoo_owl.signal.ref();
|
|
72629
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
71015
72630
|
setup() {
|
|
71016
72631
|
this.hoveredCell = useStore(DelayedHoveredCellStore);
|
|
71017
72632
|
this.viewStore = useStore(ViewportsStore);
|
|
71018
72633
|
this.zoomStore = useStore(ZoomStore);
|
|
72634
|
+
useLocalStore(HoveredTableStore);
|
|
71019
72635
|
const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
|
|
71020
72636
|
useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
|
|
71021
72637
|
useGridDrawing({
|
|
@@ -71433,8 +73049,8 @@ var RibbonMenu = class extends Component {
|
|
|
71433
73049
|
static components = { Menu };
|
|
71434
73050
|
props = (0, _odoo_owl.useProps)({ onClose: types.function() });
|
|
71435
73051
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
71436
|
-
menuRef =
|
|
71437
|
-
containerRef =
|
|
73052
|
+
menuRef = _odoo_owl.signal.ref();
|
|
73053
|
+
containerRef = _odoo_owl.signal.ref();
|
|
71438
73054
|
state = (0, _odoo_owl.proxy)({
|
|
71439
73055
|
menuItems: this.rootItems,
|
|
71440
73056
|
title: _t("Menu Bar"),
|
|
@@ -71507,7 +73123,7 @@ var SmallBottomBar = class extends Component {
|
|
|
71507
73123
|
composerStore;
|
|
71508
73124
|
viewStore;
|
|
71509
73125
|
composerInterface;
|
|
71510
|
-
composerRef =
|
|
73126
|
+
composerRef = _odoo_owl.signal.ref();
|
|
71511
73127
|
menuState = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
71512
73128
|
setup() {
|
|
71513
73129
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -71586,6 +73202,49 @@ var SmallBottomBar = class extends Component {
|
|
|
71586
73202
|
}
|
|
71587
73203
|
};
|
|
71588
73204
|
|
|
73205
|
+
//#endregion
|
|
73206
|
+
//#region src/components/standalone_grid_canvas/carousel_data_view_print.ts
|
|
73207
|
+
var CarouselDataViewPrint = class extends Component {
|
|
73208
|
+
static template = _odoo_owl.xml``;
|
|
73209
|
+
props = (0, _odoo_owl.useProps)({
|
|
73210
|
+
range: types.Range(),
|
|
73211
|
+
columnWeights: types.array().optional(),
|
|
73212
|
+
rect: types.object(),
|
|
73213
|
+
rendererStore: types.object()
|
|
73214
|
+
});
|
|
73215
|
+
viewportsStore;
|
|
73216
|
+
gridRender;
|
|
73217
|
+
setup() {
|
|
73218
|
+
useChildStoreProvider([ViewportsStore]);
|
|
73219
|
+
const store = useLocalStore(StandaloneViewportStore, this.props.range, this.props.columnWeights);
|
|
73220
|
+
this.gridRender = useLocalStore(GridRenderer, null);
|
|
73221
|
+
this.viewportsStore = useStore(ViewportsStore);
|
|
73222
|
+
(0, _odoo_owl.useEffect)(() => {
|
|
73223
|
+
store.setContainerSize(this.props.rect.width, this.props.rect.height);
|
|
73224
|
+
});
|
|
73225
|
+
(0, _odoo_owl.onMounted)(() => this.props.rendererStore.register(this));
|
|
73226
|
+
(0, _odoo_owl.onWillUnmount)(() => this.props.rendererStore.unRegister(this));
|
|
73227
|
+
}
|
|
73228
|
+
get renderingLayers() {
|
|
73229
|
+
return ["Chart"];
|
|
73230
|
+
}
|
|
73231
|
+
drawLayer(renderingContext) {
|
|
73232
|
+
const { ctx } = renderingContext;
|
|
73233
|
+
ctx.save();
|
|
73234
|
+
ctx.translate(this.props.rect.x, this.props.rect.y);
|
|
73235
|
+
ctx.beginPath();
|
|
73236
|
+
ctx.rect(0, 0, this.props.rect.width, this.props.rect.height);
|
|
73237
|
+
ctx.clip();
|
|
73238
|
+
const subRenderingCtx = {
|
|
73239
|
+
...renderingContext,
|
|
73240
|
+
sheetId: this.viewportsStore.displayedSheetId,
|
|
73241
|
+
viewports: this.viewportsStore.viewports
|
|
73242
|
+
};
|
|
73243
|
+
this.gridRender.drawLayer(subRenderingCtx, "Background", void 0);
|
|
73244
|
+
ctx.restore();
|
|
73245
|
+
}
|
|
73246
|
+
};
|
|
73247
|
+
|
|
71589
73248
|
//#endregion
|
|
71590
73249
|
//#region src/components/standalone_grid_canvas/figure_renderer_store.ts
|
|
71591
73250
|
/** Store that draws the figures directly onto the canvas */
|
|
@@ -71611,29 +73270,25 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71611
73270
|
drawLayer(renderingCtx) {
|
|
71612
73271
|
const { viewports, ctx } = renderingCtx;
|
|
71613
73272
|
const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
|
|
71614
|
-
const
|
|
73273
|
+
const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
|
|
71615
73274
|
for (const figure of visibleFigures) {
|
|
71616
|
-
const
|
|
71617
|
-
|
|
73275
|
+
const figureRect = {
|
|
73276
|
+
...figure,
|
|
73277
|
+
x: figure.x - scrollOffset.x,
|
|
73278
|
+
y: figure.y - scrollOffset.y
|
|
73279
|
+
};
|
|
73280
|
+
const { x, y, width, height } = figureRect;
|
|
71618
73281
|
if (figure.tag === "chart") {
|
|
71619
73282
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71620
|
-
if (chartId)
|
|
71621
|
-
const chartRect = {
|
|
71622
|
-
x,
|
|
71623
|
-
y,
|
|
71624
|
-
width: figure.width,
|
|
71625
|
-
height: figure.height
|
|
71626
|
-
};
|
|
71627
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71628
|
-
}
|
|
73283
|
+
if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
|
|
71629
73284
|
} else if (figure.tag === "image") {
|
|
71630
73285
|
const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
|
|
71631
|
-
if (loadedImage) ctx.drawImage(loadedImage, x, y,
|
|
71632
|
-
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure);
|
|
73286
|
+
if (loadedImage) ctx.drawImage(loadedImage, x, y, width, height);
|
|
73287
|
+
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure, figureRect);
|
|
71633
73288
|
if (!this.getters.isDashboard()) {
|
|
71634
73289
|
ctx.strokeStyle = GRAY_400;
|
|
71635
73290
|
ctx.lineWidth = 1;
|
|
71636
|
-
ctx.strokeRect(x, y,
|
|
73291
|
+
ctx.strokeRect(x, y, width, height);
|
|
71637
73292
|
}
|
|
71638
73293
|
}
|
|
71639
73294
|
}
|
|
@@ -71651,53 +73306,37 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71651
73306
|
renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
|
|
71652
73307
|
cleanUp();
|
|
71653
73308
|
}
|
|
71654
|
-
drawCarousel(renderingCtx, figure) {
|
|
71655
|
-
const {
|
|
71656
|
-
const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
|
|
71657
|
-
const x = figure.x - offsetX;
|
|
71658
|
-
const y = figure.y - offsetY;
|
|
73309
|
+
drawCarousel(renderingCtx, figure, figureRect) {
|
|
73310
|
+
const { ctx } = renderingCtx;
|
|
71659
73311
|
const carousel = this.getters.getCarousel(figure.id);
|
|
71660
73312
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71661
73313
|
if (!carousel) return;
|
|
73314
|
+
const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
|
|
71662
73315
|
const chartDefinition = chartId ? this.getters.getChartDefinition(chartId) : void 0;
|
|
73316
|
+
ctx.save();
|
|
73317
|
+
ctx.beginPath();
|
|
73318
|
+
ctx.rect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
73319
|
+
ctx.clip();
|
|
73320
|
+
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
73321
|
+
ctx.fillRect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
71663
73322
|
const title = {
|
|
71664
73323
|
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
71665
73324
|
...carousel.title
|
|
71666
73325
|
};
|
|
71667
|
-
|
|
71668
|
-
|
|
71669
|
-
if (!title.text && chartId) {
|
|
71670
|
-
const chartRect = {
|
|
71671
|
-
x,
|
|
71672
|
-
y,
|
|
71673
|
-
width: figure.width,
|
|
71674
|
-
height: figure.height
|
|
71675
|
-
};
|
|
71676
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71677
|
-
} else if (title.text) {
|
|
71678
|
-
ctx.save();
|
|
71679
|
-
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
71680
|
-
ctx.fillRect(x, y, figure.width, headerSize);
|
|
71681
|
-
ctx.font = computeTextFont(chartStyleToCellStyle(title));
|
|
73326
|
+
if (title.text) {
|
|
73327
|
+
ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
|
|
71682
73328
|
ctx.fillStyle = title.color;
|
|
71683
|
-
ctx.
|
|
71684
|
-
|
|
71685
|
-
|
|
71686
|
-
|
|
71687
|
-
|
|
71688
|
-
|
|
71689
|
-
|
|
71690
|
-
|
|
71691
|
-
if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
|
|
71692
|
-
else if (!this.getters.isDashboard()) {
|
|
71693
|
-
ctx.strokeStyle = GRAY_400;
|
|
71694
|
-
ctx.lineWidth = 1;
|
|
71695
|
-
ctx.beginPath();
|
|
71696
|
-
ctx.moveTo(x, y + headerSize);
|
|
71697
|
-
ctx.lineTo(x + figure.width, y + headerSize);
|
|
71698
|
-
ctx.stroke();
|
|
71699
|
-
}
|
|
73329
|
+
ctx.textBaseline = "middle";
|
|
73330
|
+
const textY = Math.ceil(layout.headerRect.y + layout.headerRect.height / 2);
|
|
73331
|
+
ctx.fillText(title.text, layout.headerRect.x, textY);
|
|
73332
|
+
}
|
|
73333
|
+
const separator = layout.separatorRect;
|
|
73334
|
+
if (separator) {
|
|
73335
|
+
ctx.fillStyle = GRAY_400;
|
|
73336
|
+
ctx.fillRect(separator.x, separator.y, separator.width, separator.height);
|
|
71700
73337
|
}
|
|
73338
|
+
ctx.restore();
|
|
73339
|
+
if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
|
|
71701
73340
|
}
|
|
71702
73341
|
};
|
|
71703
73342
|
|
|
@@ -71705,12 +73344,13 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71705
73344
|
//#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
|
|
71706
73345
|
var StandaloneGridCanvas = class extends Component {
|
|
71707
73346
|
static template = "o-spreadsheet-StandaloneGridCanvas";
|
|
73347
|
+
static components = { CarouselDataViewPrint };
|
|
71708
73348
|
props = (0, _odoo_owl.useProps)({
|
|
71709
73349
|
sheetId: types.UID(),
|
|
71710
73350
|
zone: types.Zone(),
|
|
71711
73351
|
renderingCtx: types.object()
|
|
71712
73352
|
});
|
|
71713
|
-
canvasRef =
|
|
73353
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
71714
73354
|
rendererStore;
|
|
71715
73355
|
figureRendererStore;
|
|
71716
73356
|
setup() {
|
|
@@ -71732,6 +73372,28 @@ var StandaloneGridCanvas = class extends Component {
|
|
|
71732
73372
|
this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
|
|
71733
73373
|
}));
|
|
71734
73374
|
}
|
|
73375
|
+
get carouselDataViews() {
|
|
73376
|
+
const { sheetId, viewports } = this.props.renderingCtx;
|
|
73377
|
+
return viewports.getVisibleFigures(sheetId).filter((figure) => figure.tag === "carousel").map((figure) => {
|
|
73378
|
+
const carouselItem = this.env.model.getters.getSelectedCarouselItem(figure.id);
|
|
73379
|
+
if (carouselItem?.type !== "carouselDataView" || !carouselItem.range) return;
|
|
73380
|
+
const carousel = this.env.model.getters.getCarousel(figure.id);
|
|
73381
|
+
const scroll = viewports.getViewportOffset(sheetId);
|
|
73382
|
+
const { x, y, width, height } = figure;
|
|
73383
|
+
const layout = getCarouselLayout({
|
|
73384
|
+
x: x - scroll.x,
|
|
73385
|
+
y: y - scroll.y,
|
|
73386
|
+
width,
|
|
73387
|
+
height
|
|
73388
|
+
}, carousel, carouselItem);
|
|
73389
|
+
return {
|
|
73390
|
+
range: carouselItem.range,
|
|
73391
|
+
columnWeights: carouselItem.columnWeights,
|
|
73392
|
+
rect: layout.contentRect,
|
|
73393
|
+
rendererStore: this.rendererStore
|
|
73394
|
+
};
|
|
73395
|
+
}).filter(isDefined);
|
|
73396
|
+
}
|
|
71735
73397
|
};
|
|
71736
73398
|
|
|
71737
73399
|
//#endregion
|
|
@@ -72193,7 +73855,7 @@ var NamedRangeSelector = class extends Component {
|
|
|
72193
73855
|
anchorRect: null,
|
|
72194
73856
|
menuItems: []
|
|
72195
73857
|
});
|
|
72196
|
-
namedRangeSelectorRef =
|
|
73858
|
+
namedRangeSelectorRef = _odoo_owl.signal.ref();
|
|
72197
73859
|
setup() {
|
|
72198
73860
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72199
73861
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -72391,7 +74053,7 @@ var BorderEditor = class extends Component {
|
|
|
72391
74053
|
anchorRect: types.Rect()
|
|
72392
74054
|
});
|
|
72393
74055
|
BORDER_POSITIONS = BORDER_POSITIONS;
|
|
72394
|
-
lineStyleButtonRef =
|
|
74056
|
+
lineStyleButtonRef = _odoo_owl.signal.ref();
|
|
72395
74057
|
borderStyles = borderStyles;
|
|
72396
74058
|
state = (0, _odoo_owl.proxy)({ activeTool: void 0 });
|
|
72397
74059
|
toggleDropdownTool(tool) {
|
|
@@ -72458,7 +74120,7 @@ var BorderEditorWidget = class extends Component {
|
|
|
72458
74120
|
});
|
|
72459
74121
|
topBarToolStore;
|
|
72460
74122
|
viewStore;
|
|
72461
|
-
borderEditorButtonRef =
|
|
74123
|
+
borderEditorButtonRef = _odoo_owl.signal.ref();
|
|
72462
74124
|
state = (0, _odoo_owl.proxy)({
|
|
72463
74125
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
72464
74126
|
currentStyle: DEFAULT_BORDER_DESC.style,
|
|
@@ -72688,7 +74350,7 @@ var DropdownAction = class extends Component {
|
|
|
72688
74350
|
childClass: types.string()
|
|
72689
74351
|
});
|
|
72690
74352
|
topBarToolStore;
|
|
72691
|
-
actionRef =
|
|
74353
|
+
actionRef = _odoo_owl.signal.ref();
|
|
72692
74354
|
setup() {
|
|
72693
74355
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72694
74356
|
}
|
|
@@ -72751,7 +74413,7 @@ var NumberFormatsTool = class extends Component {
|
|
|
72751
74413
|
props = (0, _odoo_owl.useProps)({ class: types.string() });
|
|
72752
74414
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
72753
74415
|
topBarToolStore;
|
|
72754
|
-
buttonRef =
|
|
74416
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
72755
74417
|
state = (0, _odoo_owl.proxy)({
|
|
72756
74418
|
anchorRect: {
|
|
72757
74419
|
x: 0,
|
|
@@ -73040,12 +74702,12 @@ var TopBar = class extends Component {
|
|
|
73040
74702
|
composerFocusStore;
|
|
73041
74703
|
fingerprints;
|
|
73042
74704
|
topBarToolStore;
|
|
73043
|
-
toolBarContainerRef =
|
|
73044
|
-
toolbarRef =
|
|
73045
|
-
namedRangesRef =
|
|
73046
|
-
topBarTopRef =
|
|
73047
|
-
moreToolsContainerRef =
|
|
73048
|
-
moreToolsButtonRef =
|
|
74705
|
+
toolBarContainerRef = _odoo_owl.signal.ref();
|
|
74706
|
+
toolbarRef = _odoo_owl.signal.ref();
|
|
74707
|
+
namedRangesRef = _odoo_owl.signal.ref();
|
|
74708
|
+
topBarTopRef = _odoo_owl.signal.ref();
|
|
74709
|
+
moreToolsContainerRef = _odoo_owl.signal.ref();
|
|
74710
|
+
moreToolsButtonRef = _odoo_owl.signal.ref();
|
|
73049
74711
|
spreadsheetRect = useSpreadsheetRect();
|
|
73050
74712
|
setup() {
|
|
73051
74713
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -73307,7 +74969,7 @@ var Spreadsheet = class extends Component {
|
|
|
73307
74969
|
SpreadsheetPrint
|
|
73308
74970
|
};
|
|
73309
74971
|
sidePanel;
|
|
73310
|
-
spreadsheetRef =
|
|
74972
|
+
spreadsheetRef = _odoo_owl.signal.ref();
|
|
73311
74973
|
spreadsheetRect = useSpreadsheetRect();
|
|
73312
74974
|
state = (0, _odoo_owl.proxy)({ printModeEnabled: false });
|
|
73313
74975
|
_focusGrid;
|
|
@@ -79315,12 +80977,13 @@ var FilterMenu = class extends Component {
|
|
|
79315
80977
|
}
|
|
79316
80978
|
sortFilterZone(sortDirection) {
|
|
79317
80979
|
const filterPosition = this.props.filterPosition;
|
|
79318
|
-
const
|
|
80980
|
+
const table = this.table;
|
|
80981
|
+
const tableZone = table?.range.zone;
|
|
79319
80982
|
if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
|
|
79320
80983
|
const sheetId = this.viewStore.displayedSheetId;
|
|
79321
80984
|
const contentZone = {
|
|
79322
80985
|
...tableZone,
|
|
79323
|
-
top: tableZone.top +
|
|
80986
|
+
top: tableZone.top + table.config.numberOfHeaders
|
|
79324
80987
|
};
|
|
79325
80988
|
const sortAnchor = {
|
|
79326
80989
|
col: filterPosition.col,
|
|
@@ -79478,9 +81141,9 @@ var LinkEditor = class extends Component {
|
|
|
79478
81141
|
onClosed: types.function().optional()
|
|
79479
81142
|
});
|
|
79480
81143
|
static size = { maxHeight: 500 };
|
|
79481
|
-
urlInput =
|
|
79482
|
-
suggestionListRef =
|
|
79483
|
-
urlInputContainer =
|
|
81144
|
+
urlInput = _odoo_owl.signal.ref(HTMLInputElement);
|
|
81145
|
+
suggestionListRef = _odoo_owl.signal.ref();
|
|
81146
|
+
urlInputContainer = _odoo_owl.signal.ref();
|
|
79484
81147
|
state;
|
|
79485
81148
|
viewStore;
|
|
79486
81149
|
setup() {
|
|
@@ -81598,7 +83261,7 @@ const ARRAYTOTEXT = {
|
|
|
81598
83261
|
else if (_format === 0) {
|
|
81599
83262
|
const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
|
|
81600
83263
|
return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
|
|
81601
|
-
return isEvaluationError(value.value) ? value.value :
|
|
83264
|
+
return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
|
|
81602
83265
|
})).join(rowSeparator) };
|
|
81603
83266
|
} else return new EvaluationError(_t("Format must be 0 or 1"));
|
|
81604
83267
|
},
|
|
@@ -87646,7 +89309,7 @@ const CONCAT = {
|
|
|
87646
89309
|
description: _t("Concatenation of two values."),
|
|
87647
89310
|
args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
|
|
87648
89311
|
compute: function(value1, value2) {
|
|
87649
|
-
return { value:
|
|
89312
|
+
return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
|
|
87650
89313
|
},
|
|
87651
89314
|
isExported: true
|
|
87652
89315
|
};
|
|
@@ -88529,7 +90192,7 @@ const CLEAN = {
|
|
|
88529
90192
|
description: _t("Remove non-printable characters from a piece of text."),
|
|
88530
90193
|
args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
|
|
88531
90194
|
compute: function(text) {
|
|
88532
|
-
const _text =
|
|
90195
|
+
const _text = toLocaleString(text, this.locale);
|
|
88533
90196
|
let cleanedStr = "";
|
|
88534
90197
|
for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
|
|
88535
90198
|
return { value: cleanedStr };
|
|
@@ -88540,7 +90203,7 @@ const CONCATENATE = {
|
|
|
88540
90203
|
description: _t("Appends strings to one another."),
|
|
88541
90204
|
args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
|
|
88542
90205
|
compute: function(...datas) {
|
|
88543
|
-
return { value: reduceAny(datas, (acc, a) => acc +
|
|
90206
|
+
return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
|
|
88544
90207
|
},
|
|
88545
90208
|
isExported: true
|
|
88546
90209
|
};
|
|
@@ -88548,7 +90211,7 @@ const EXACT = {
|
|
|
88548
90211
|
description: _t("Tests whether two strings are identical."),
|
|
88549
90212
|
args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
|
|
88550
90213
|
compute: function(string1, string2) {
|
|
88551
|
-
return { value:
|
|
90214
|
+
return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
|
|
88552
90215
|
},
|
|
88553
90216
|
isExported: true
|
|
88554
90217
|
};
|
|
@@ -88560,8 +90223,8 @@ const FIND = {
|
|
|
88560
90223
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88561
90224
|
],
|
|
88562
90225
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88563
|
-
const _searchFor =
|
|
88564
|
-
const _textToSearch =
|
|
90226
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
90227
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
88565
90228
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88566
90229
|
if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
88567
90230
|
if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
@@ -88598,8 +90261,8 @@ const JOIN = {
|
|
|
88598
90261
|
description: _t("Concatenates elements of arrays with delimiter."),
|
|
88599
90262
|
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."))],
|
|
88600
90263
|
compute: function(delimiter, ...valuesOrArrays) {
|
|
88601
|
-
const _delimiter =
|
|
88602
|
-
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") +
|
|
90264
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
90265
|
+
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "") };
|
|
88603
90266
|
}
|
|
88604
90267
|
};
|
|
88605
90268
|
const LEFT = {
|
|
@@ -88608,7 +90271,7 @@ const LEFT = {
|
|
|
88608
90271
|
compute: function(text, ...args) {
|
|
88609
90272
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88610
90273
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
88611
|
-
return { value:
|
|
90274
|
+
return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
|
|
88612
90275
|
},
|
|
88613
90276
|
isExported: true
|
|
88614
90277
|
};
|
|
@@ -88616,7 +90279,7 @@ const LEN = {
|
|
|
88616
90279
|
description: _t("Length of a string."),
|
|
88617
90280
|
args: [arg("text (string)", _t("The string whose length will be returned."))],
|
|
88618
90281
|
compute: function(text) {
|
|
88619
|
-
return { value:
|
|
90282
|
+
return { value: toLocaleString(text, this.locale).length };
|
|
88620
90283
|
},
|
|
88621
90284
|
isExported: true
|
|
88622
90285
|
};
|
|
@@ -88624,7 +90287,7 @@ const LOWER = {
|
|
|
88624
90287
|
description: _t("Converts a specified string to lowercase."),
|
|
88625
90288
|
args: [arg("text (string)", _t("The string to convert to lowercase."))],
|
|
88626
90289
|
compute: function(text) {
|
|
88627
|
-
return { value:
|
|
90290
|
+
return { value: toLocaleString(text, this.locale).toLowerCase() };
|
|
88628
90291
|
},
|
|
88629
90292
|
isExported: true
|
|
88630
90293
|
};
|
|
@@ -88636,7 +90299,7 @@ const MID = {
|
|
|
88636
90299
|
arg("extract_length (number)", _t("The length of the segment to extract."))
|
|
88637
90300
|
],
|
|
88638
90301
|
compute: function(text, starting_at, extract_length) {
|
|
88639
|
-
const _text =
|
|
90302
|
+
const _text = toLocaleString(text, this.locale);
|
|
88640
90303
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
88641
90304
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
88642
90305
|
if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
@@ -88649,7 +90312,7 @@ const PROPER = {
|
|
|
88649
90312
|
description: _t("Capitalizes each word in a specified string."),
|
|
88650
90313
|
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."))],
|
|
88651
90314
|
compute: function(text) {
|
|
88652
|
-
return { value:
|
|
90315
|
+
return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
|
|
88653
90316
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
88654
90317
|
}) };
|
|
88655
90318
|
},
|
|
@@ -88799,8 +90462,8 @@ const REPLACE = {
|
|
|
88799
90462
|
if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
88800
90463
|
const _length = toNumber(length, this.locale);
|
|
88801
90464
|
if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
|
|
88802
|
-
const _text =
|
|
88803
|
-
const _newText =
|
|
90465
|
+
const _text = toLocaleString(text, this.locale);
|
|
90466
|
+
const _newText = toLocaleString(newText, this.locale);
|
|
88804
90467
|
return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
|
|
88805
90468
|
},
|
|
88806
90469
|
isExported: true
|
|
@@ -88811,7 +90474,7 @@ const RIGHT = {
|
|
|
88811
90474
|
compute: function(text, ...args) {
|
|
88812
90475
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88813
90476
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
|
|
88814
|
-
const _text =
|
|
90477
|
+
const _text = toLocaleString(text, this.locale);
|
|
88815
90478
|
const stringLength = _text.length;
|
|
88816
90479
|
return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
|
|
88817
90480
|
},
|
|
@@ -88825,8 +90488,8 @@ const SEARCH = {
|
|
|
88825
90488
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88826
90489
|
],
|
|
88827
90490
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88828
|
-
const _searchFor =
|
|
88829
|
-
const _textToSearch =
|
|
90491
|
+
const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
|
|
90492
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
|
|
88830
90493
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88831
90494
|
if (_textToSearch === "") return {
|
|
88832
90495
|
value: CellErrorType.GenericError,
|
|
@@ -88856,8 +90519,8 @@ const SPLIT = {
|
|
|
88856
90519
|
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."))
|
|
88857
90520
|
],
|
|
88858
90521
|
computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
|
|
88859
|
-
const _text =
|
|
88860
|
-
const _delimiter = escapeRegExp(
|
|
90522
|
+
const _text = toLocaleString(text, this.locale);
|
|
90523
|
+
const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
|
|
88861
90524
|
const _splitByEach = toBoolean(splitByEach);
|
|
88862
90525
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
88863
90526
|
if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
@@ -88879,10 +90542,10 @@ const SUBSTITUTE = {
|
|
|
88879
90542
|
compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
88880
90543
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
88881
90544
|
if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
88882
|
-
const _textToSearch =
|
|
88883
|
-
const _searchFor =
|
|
90545
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
90546
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
88884
90547
|
if (_searchFor === "") return { value: _textToSearch };
|
|
88885
|
-
const _replaceWith =
|
|
90548
|
+
const _replaceWith = toLocaleString(replaceWith, this.locale);
|
|
88886
90549
|
const reg = new RegExp(escapeRegExp(_searchFor), "g");
|
|
88887
90550
|
if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
|
|
88888
90551
|
let n = 0;
|
|
@@ -88905,10 +90568,10 @@ const TEXTJOIN = {
|
|
|
88905
90568
|
arg("texts (string, range<string>, repeating)", _t("Text item to join."))
|
|
88906
90569
|
],
|
|
88907
90570
|
compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
|
|
88908
|
-
const _delimiter =
|
|
90571
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
88909
90572
|
const _ignoreEmpty = toBoolean(ignoreEmpty);
|
|
88910
90573
|
let n = 0;
|
|
88911
|
-
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty &&
|
|
90574
|
+
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "") };
|
|
88912
90575
|
},
|
|
88913
90576
|
isExported: true
|
|
88914
90577
|
};
|
|
@@ -88961,7 +90624,7 @@ const TRIM = {
|
|
|
88961
90624
|
description: _t("Removes space characters."),
|
|
88962
90625
|
args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
|
|
88963
90626
|
compute: function(text) {
|
|
88964
|
-
return { value: trimContent(
|
|
90627
|
+
return { value: trimContent(toLocaleString(text, this.locale)) };
|
|
88965
90628
|
},
|
|
88966
90629
|
isExported: true
|
|
88967
90630
|
};
|
|
@@ -88969,7 +90632,7 @@ const UPPER = {
|
|
|
88969
90632
|
description: _t("Converts a specified string to uppercase."),
|
|
88970
90633
|
args: [arg("text (string)", _t("The string to convert to uppercase."))],
|
|
88971
90634
|
compute: function(text) {
|
|
88972
|
-
return { value:
|
|
90635
|
+
return { value: toLocaleString(text, this.locale).toUpperCase() };
|
|
88973
90636
|
},
|
|
88974
90637
|
isExported: true
|
|
88975
90638
|
};
|
|
@@ -89066,7 +90729,7 @@ const HYPERLINK = {
|
|
|
89066
90729
|
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."))],
|
|
89067
90730
|
compute: function(url, linkLabel) {
|
|
89068
90731
|
const processedUrl = toString(url).trim();
|
|
89069
|
-
const processedLabel =
|
|
90732
|
+
const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
|
|
89070
90733
|
if (processedUrl === "") return { value: processedLabel };
|
|
89071
90734
|
return { value: markdownLink(processedLabel, processedUrl) };
|
|
89072
90735
|
},
|
|
@@ -89213,7 +90876,8 @@ const registries = {
|
|
|
89213
90876
|
chartJsExtensionRegistry,
|
|
89214
90877
|
onIterationEndEvaluationRegistry,
|
|
89215
90878
|
specificRangeTransformRegistry,
|
|
89216
|
-
unusedDataSourceRegistry
|
|
90879
|
+
unusedDataSourceRegistry,
|
|
90880
|
+
NonSquishableFunctionRegistry
|
|
89217
90881
|
};
|
|
89218
90882
|
const helpers = {
|
|
89219
90883
|
arg,
|
|
@@ -89295,7 +90959,9 @@ const helpers = {
|
|
|
89295
90959
|
computeCachedTextDimension,
|
|
89296
90960
|
createComputeFunction,
|
|
89297
90961
|
isMobileOS,
|
|
89298
|
-
drawHighlight
|
|
90962
|
+
drawHighlight,
|
|
90963
|
+
getBoundingRectWithMargins,
|
|
90964
|
+
hasInteractiveElementInEventTree
|
|
89299
90965
|
};
|
|
89300
90966
|
const links = {
|
|
89301
90967
|
isMarkdownLink,
|
|
@@ -89527,6 +91193,6 @@ exports.stores = stores;
|
|
|
89527
91193
|
exports.tokenColors = tokenColors;
|
|
89528
91194
|
exports.tokenize = tokenize;
|
|
89529
91195
|
|
|
89530
|
-
__info__.version = "19.5.0-alpha.
|
|
89531
|
-
__info__.date = "2026-08-
|
|
89532
|
-
__info__.hash = "
|
|
91196
|
+
__info__.version = "19.5.0-alpha.12";
|
|
91197
|
+
__info__.date = "2026-08-19T09:47:03.774Z";
|
|
91198
|
+
__info__.hash = "68ca68e";
|