@odoo/o-spreadsheet 19.5.0-alpha.11 → 19.5.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/material_symbols_outlined.css +9 -0
- package/dist/o_spreadsheet.cjs +2313 -656
- package/dist/o_spreadsheet.css +169 -33
- package/dist/o_spreadsheet.esm.js +2305 -648
- package/dist/o_spreadsheet.iife.js +2313 -656
- package/dist/o_spreadsheet.min.iife.js +344 -344
- package/dist/o_spreadsheet.xml +209 -128
- package/dist/types/actions/data_actions.d.ts +1 -0
- package/dist/types/components/bottom_bar/bottom_bar_statistic/bottom_bar_statistic.d.ts +3 -0
- package/dist/types/components/dashboard/clickable_cell_sort_icon/clickable_cell_sort_icon.d.ts +1 -1
- package/dist/types/components/figures/figure/figure.d.ts +1 -0
- package/dist/types/components/figures/figure_carousel/figure_carousel.d.ts +10 -0
- package/dist/types/components/figures/figure_container/figure_container.d.ts +1 -2
- package/dist/types/components/grid/grid.d.ts +1 -0
- package/dist/types/components/helpers/chart_drag_and_drop.d.ts +15 -0
- package/dist/types/components/helpers/dom_helpers.d.ts +3 -2
- package/dist/types/components/link/link_editor/link_editor.d.ts +1 -1
- package/dist/types/components/side_panel/carousel_panel/carousel_panel.d.ts +1 -1
- package/dist/types/components/side_panel/chart/building_blocks/color_scale/color_scale_picker.d.ts +1 -1
- package/dist/types/components/side_panel/data_analysis/chart_suggestion_preview.d.ts +19 -0
- package/dist/types/components/side_panel/data_analysis/data_analysis_panel.d.ts +17 -0
- package/dist/types/components/side_panel/data_analysis/data_analysis_store.d.ts +15 -0
- package/dist/types/components/side_panel/pivot/pivot_layout_configurator/pivot_layout_configurator.d.ts +1 -6
- package/dist/types/components/side_panel/side_panel/side_panel_store.d.ts +2 -1
- package/dist/types/components/standalone_grid_canvas/carousel_data_view_print.d.ts +32 -0
- package/dist/types/components/standalone_grid_canvas/standalone_grid_canvas.d.ts +6 -0
- package/dist/types/components/standalone_viewport/standalone_viewport.d.ts +6 -3
- package/dist/types/components/standalone_viewport/standalone_viewport_store.d.ts +2 -1
- package/dist/types/components/tables/hovered_table_store.d.ts +6 -7
- package/dist/types/constants.d.ts +13 -1
- package/dist/types/functions/helpers.d.ts +4 -0
- package/dist/types/functions/module_operators.d.ts +1 -1
- package/dist/types/functions/module_text.d.ts +11 -11
- package/dist/types/functions/module_web.d.ts +1 -1
- package/dist/types/helpers/carousel_helpers.d.ts +8 -1
- package/dist/types/helpers/data_analysis.d.ts +16 -0
- package/dist/types/helpers/figures/charts/chart_data_source_helpers.d.ts +10 -0
- package/dist/types/helpers/figures/charts/chart_suggestion_engine.d.ts +8 -0
- package/dist/types/helpers/figures/charts/chart_ui_common.d.ts +8 -1
- package/dist/types/helpers/figures/charts/charts_suggestions_constants.d.ts +37 -0
- package/dist/types/helpers/figures/charts/gauge_chart_rendering.d.ts +6 -2
- package/dist/types/helpers/text_helper.d.ts +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/registries/figures_registry.d.ts +1 -0
- package/dist/types/stores/cell_hover_overlay_store.d.ts +15 -0
- package/dist/types/stores/chart_drag_store.d.ts +10 -0
- package/dist/types/stores/grid_renderer_store.d.ts +3 -2
- package/dist/types/types/chart/chart_suggestion.d.ts +136 -0
- package/dist/types/types/commands.d.ts +16 -9
- package/package.json +4 -4
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, "");
|
|
@@ -9248,8 +9278,8 @@ function computeCachedTextDimension(context, text, font) {
|
|
|
9248
9278
|
function fontSizeInPixels(fontSize) {
|
|
9249
9279
|
return Math.round(fontSize * 96 / 72);
|
|
9250
9280
|
}
|
|
9251
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
9252
|
-
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}`;
|
|
9253
9283
|
}
|
|
9254
9284
|
function computeTextFontSizeInPixels(style) {
|
|
9255
9285
|
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
@@ -11883,7 +11913,7 @@ var ChartJsComponent = class extends Component {
|
|
|
11883
11913
|
chartId: types.string(),
|
|
11884
11914
|
isFullScreen: types.boolean().optional()
|
|
11885
11915
|
});
|
|
11886
|
-
canvas =
|
|
11916
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
11887
11917
|
chart;
|
|
11888
11918
|
currentRuntime;
|
|
11889
11919
|
animationStore;
|
|
@@ -12606,7 +12636,7 @@ var ScorecardChart = class extends Component {
|
|
|
12606
12636
|
chartId: types.string(),
|
|
12607
12637
|
isFullScreen: types.boolean().optional()
|
|
12608
12638
|
});
|
|
12609
|
-
canvas =
|
|
12639
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
12610
12640
|
zoomStore;
|
|
12611
12641
|
get runtime() {
|
|
12612
12642
|
return this.env.model.getters.getChartRuntime(this.props.chartId);
|
|
@@ -13991,6 +14021,44 @@ function getCarouselItemTitle(getters, item) {
|
|
|
13991
14021
|
const definition = getters.getChartDefinition(item.chartId);
|
|
13992
14022
|
return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
|
|
13993
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
|
+
}
|
|
13994
14062
|
|
|
13995
14063
|
//#endregion
|
|
13996
14064
|
//#region src/helpers/figures/charts/gauge_chart_rendering.ts
|
|
@@ -14002,7 +14070,7 @@ const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
|
14002
14070
|
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
14003
14071
|
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
14004
14072
|
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
14005
|
-
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
14073
|
+
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
|
|
14006
14074
|
const size = dimensions ?? getCanvasSize(canvas);
|
|
14007
14075
|
const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
14008
14076
|
canvas.width = size.width * dpr;
|
|
@@ -14015,7 +14083,7 @@ function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
|
14015
14083
|
height: size.height / zoom,
|
|
14016
14084
|
x: 0,
|
|
14017
14085
|
y: 0
|
|
14018
|
-
}, runtime, ctx);
|
|
14086
|
+
}, runtime, ctx, options);
|
|
14019
14087
|
drawBackground(ctx, config);
|
|
14020
14088
|
drawGauge(ctx, config);
|
|
14021
14089
|
drawInflectionValues(ctx, config);
|
|
@@ -14103,7 +14171,8 @@ function drawTitle(ctx, config) {
|
|
|
14103
14171
|
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
14104
14172
|
ctx.restore();
|
|
14105
14173
|
}
|
|
14106
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
14174
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
|
|
14175
|
+
const labelFontSize = options?.labelFontSize ?? 12;
|
|
14107
14176
|
const maxValue = runtime.maxValue;
|
|
14108
14177
|
const minValue = runtime.minValue;
|
|
14109
14178
|
const gaugeValue = getGaugeValue(runtime, "animated");
|
|
@@ -14121,14 +14190,14 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14121
14190
|
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
14122
14191
|
const minLabelPosition = {
|
|
14123
14192
|
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
14124
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14193
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14125
14194
|
};
|
|
14126
14195
|
const maxLabelPosition = {
|
|
14127
14196
|
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
14128
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14197
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14129
14198
|
};
|
|
14130
14199
|
const textColor = chartMutedFontColor(runtime.background);
|
|
14131
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
14200
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
|
|
14132
14201
|
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
14133
14202
|
if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
|
|
14134
14203
|
fontSize: 16,
|
|
@@ -14176,13 +14245,13 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
14176
14245
|
minLabel: {
|
|
14177
14246
|
label: runtime.minValue.label,
|
|
14178
14247
|
textPosition: minLabelPosition,
|
|
14179
|
-
fontSize:
|
|
14248
|
+
fontSize: labelFontSize,
|
|
14180
14249
|
color: textColor
|
|
14181
14250
|
},
|
|
14182
14251
|
maxLabel: {
|
|
14183
14252
|
label: runtime.maxValue.label,
|
|
14184
14253
|
textPosition: maxLabelPosition,
|
|
14185
|
-
fontSize:
|
|
14254
|
+
fontSize: labelFontSize,
|
|
14186
14255
|
color: textColor
|
|
14187
14256
|
}
|
|
14188
14257
|
};
|
|
@@ -14216,27 +14285,27 @@ function getGaugeRect(boundingRect, title) {
|
|
|
14216
14285
|
*
|
|
14217
14286
|
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
14218
14287
|
*/
|
|
14219
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
14288
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
|
|
14220
14289
|
const maxValue = runtime.maxValue;
|
|
14221
14290
|
const minValue = runtime.minValue;
|
|
14222
14291
|
const gaugeCircleCenter = {
|
|
14223
14292
|
x: gaugeRect.x + gaugeRect.width / 2,
|
|
14224
14293
|
y: gaugeRect.y + gaugeRect.height
|
|
14225
14294
|
};
|
|
14226
|
-
const textStyle = { fontSize:
|
|
14295
|
+
const textStyle = { fontSize: labelFontSize };
|
|
14227
14296
|
const inflectionValues = [];
|
|
14228
14297
|
const inflectionValuesTextRects = [];
|
|
14229
14298
|
for (const inflectionValue of runtime.inflectionValues) {
|
|
14230
14299
|
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
14231
14300
|
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
14232
14301
|
const angle = Math.PI - Math.PI * percentage;
|
|
14233
|
-
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2,
|
|
14234
|
-
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;
|
|
14235
14304
|
inflectionValuesTextRects.push(textRect);
|
|
14236
14305
|
inflectionValues.push({
|
|
14237
14306
|
rotation: angle,
|
|
14238
14307
|
label: inflectionValue.label,
|
|
14239
|
-
fontSize:
|
|
14308
|
+
fontSize: labelFontSize,
|
|
14240
14309
|
color: textColor,
|
|
14241
14310
|
offset
|
|
14242
14311
|
});
|
|
@@ -14356,6 +14425,44 @@ const CHART_COMMON_OPTIONS = {
|
|
|
14356
14425
|
"mouseup"
|
|
14357
14426
|
]
|
|
14358
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
|
+
}
|
|
14359
14466
|
async function chartToImageUrl(runtime, figure, type) {
|
|
14360
14467
|
try {
|
|
14361
14468
|
const canvas = createRenderingSurface(figure.width, figure.height);
|
|
@@ -14403,7 +14510,7 @@ async function canvasToObjectUrl(canvas) {
|
|
|
14403
14510
|
*
|
|
14404
14511
|
* @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
|
|
14405
14512
|
*/
|
|
14406
|
-
function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
14513
|
+
function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
|
|
14407
14514
|
if ("chartJsConfig" in runtime) {
|
|
14408
14515
|
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
14409
14516
|
const extensionsLoaded = areChartJSExtensionsLoaded();
|
|
@@ -14419,8 +14526,8 @@ function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
|
14419
14526
|
chart.destroy();
|
|
14420
14527
|
if (!extensionsLoaded) unregisterChartJsExtensions();
|
|
14421
14528
|
};
|
|
14422
|
-
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
|
|
14423
|
-
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);
|
|
14424
14531
|
return () => {};
|
|
14425
14532
|
}
|
|
14426
14533
|
|
|
@@ -14958,7 +15065,7 @@ var ZoomableChartStore = class extends SpreadsheetStore {
|
|
|
14958
15065
|
var ZoomableChartJsComponent = class extends ChartJsComponent {
|
|
14959
15066
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
14960
15067
|
store;
|
|
14961
|
-
masterChartCanvas =
|
|
15068
|
+
masterChartCanvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
14962
15069
|
masterChart;
|
|
14963
15070
|
mode;
|
|
14964
15071
|
hasLinearScale;
|
|
@@ -15712,7 +15819,7 @@ var GaugeChartComponent = class extends Component {
|
|
|
15712
15819
|
chartId: types.string(),
|
|
15713
15820
|
isFullScreen: types.boolean().optional()
|
|
15714
15821
|
});
|
|
15715
|
-
canvas =
|
|
15822
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
15716
15823
|
animationStore;
|
|
15717
15824
|
zoomStore;
|
|
15718
15825
|
get runtime() {
|
|
@@ -15949,7 +16056,7 @@ var Menu = class extends Component {
|
|
|
15949
16056
|
onKeyDown: types.function().optional(),
|
|
15950
16057
|
disableKeyboardNavigation: types.boolean().optional()
|
|
15951
16058
|
});
|
|
15952
|
-
menuRef =
|
|
16059
|
+
menuRef = _odoo_owl.signal.ref();
|
|
15953
16060
|
setup() {
|
|
15954
16061
|
useLayoutEffect(() => {
|
|
15955
16062
|
const menuEl = this.menuRef();
|
|
@@ -16069,8 +16176,8 @@ var Popover = class extends Component {
|
|
|
16069
16176
|
zIndex: types.number().optional(),
|
|
16070
16177
|
class: types.string().optional()
|
|
16071
16178
|
});
|
|
16072
|
-
popoverRef =
|
|
16073
|
-
popoverContentRef =
|
|
16179
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
16180
|
+
popoverContentRef = _odoo_owl.signal.ref();
|
|
16074
16181
|
currentPosition = void 0;
|
|
16075
16182
|
currentDisplayValue = void 0;
|
|
16076
16183
|
spreadsheetRect = useSpreadsheetRect();
|
|
@@ -16258,7 +16365,7 @@ var MenuPopover = class MenuPopover extends Component {
|
|
|
16258
16365
|
isHoveringChild: false
|
|
16259
16366
|
});
|
|
16260
16367
|
state = (0, _odoo_owl.proxy)({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
|
|
16261
|
-
menuRef =
|
|
16368
|
+
menuRef = _odoo_owl.signal.ref();
|
|
16262
16369
|
openingTimeOut = useTimeOut();
|
|
16263
16370
|
setup() {
|
|
16264
16371
|
const domFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -16547,43 +16654,29 @@ var PositionMap = class {
|
|
|
16547
16654
|
};
|
|
16548
16655
|
|
|
16549
16656
|
//#endregion
|
|
16550
|
-
//#region src/
|
|
16551
|
-
var
|
|
16552
|
-
mutators = [
|
|
16553
|
-
|
|
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 = [];
|
|
16554
16666
|
overlayColors = new PositionMap();
|
|
16555
|
-
hover(
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
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
|
+
}
|
|
16559
16674
|
}
|
|
16560
|
-
|
|
16561
|
-
this.
|
|
16675
|
+
register(highlightProvider) {
|
|
16676
|
+
this.providers.push(highlightProvider);
|
|
16562
16677
|
}
|
|
16563
|
-
|
|
16564
|
-
this.
|
|
16565
|
-
if (!this.position) return;
|
|
16566
|
-
const { sheetId, col, row } = this.position;
|
|
16567
|
-
const table = this.getters.getTable({
|
|
16568
|
-
sheetId,
|
|
16569
|
-
col,
|
|
16570
|
-
row
|
|
16571
|
-
});
|
|
16572
|
-
if (!table) return;
|
|
16573
|
-
const { left, right, top } = table.range.zone;
|
|
16574
|
-
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
16575
|
-
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
16576
|
-
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
16577
|
-
sheetId,
|
|
16578
|
-
col,
|
|
16579
|
-
row
|
|
16580
|
-
}).formattedValue;
|
|
16581
|
-
});
|
|
16582
|
-
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
|
|
16583
|
-
sheetId,
|
|
16584
|
-
col,
|
|
16585
|
-
row
|
|
16586
|
-
}, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16678
|
+
unRegister(highlightProvider) {
|
|
16679
|
+
this.providers = this.providers.filter((h) => h !== highlightProvider);
|
|
16587
16680
|
}
|
|
16588
16681
|
};
|
|
16589
16682
|
|
|
@@ -16595,9 +16688,9 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16595
16688
|
position: types.CellPosition(),
|
|
16596
16689
|
sortDirection: types.or([types.SortDirection, types.literal("none")])
|
|
16597
16690
|
});
|
|
16598
|
-
|
|
16691
|
+
hoveredCellOverlayStore;
|
|
16599
16692
|
setup() {
|
|
16600
|
-
this.
|
|
16693
|
+
this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
|
|
16601
16694
|
}
|
|
16602
16695
|
get style() {
|
|
16603
16696
|
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
|
|
@@ -16617,7 +16710,7 @@ var ClickableCellSortIcon = class extends Component {
|
|
|
16617
16710
|
}
|
|
16618
16711
|
}
|
|
16619
16712
|
getBackgroundColor(cellStyle) {
|
|
16620
|
-
const overlayColor = this.
|
|
16713
|
+
const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
|
|
16621
16714
|
if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
|
|
16622
16715
|
return cellStyle.fillColor || "#FFFFFF";
|
|
16623
16716
|
}
|
|
@@ -21356,7 +21449,7 @@ var HoveredIconStore = class extends SpreadsheetStore {
|
|
|
21356
21449
|
//#region src/components/grid_overlay/grid_overlay.ts
|
|
21357
21450
|
function useCellHovered(env, gridRef) {
|
|
21358
21451
|
const delayedHoveredCell = useStore(DelayedHoveredCellStore);
|
|
21359
|
-
const
|
|
21452
|
+
const cellHoverOverlay = useStore(CellHoverOverlayStore);
|
|
21360
21453
|
const viewStore = useStore(ViewportsStore);
|
|
21361
21454
|
const hoveredPosition = {
|
|
21362
21455
|
col: void 0,
|
|
@@ -21400,7 +21493,9 @@ function useCellHovered(env, gridRef) {
|
|
|
21400
21493
|
if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
|
|
21401
21494
|
({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
|
|
21402
21495
|
lastMoved = Date.now();
|
|
21403
|
-
|
|
21496
|
+
const position = getPosition();
|
|
21497
|
+
if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
|
|
21498
|
+
else cellHoverOverlay.hover(getPosition());
|
|
21404
21499
|
}
|
|
21405
21500
|
}
|
|
21406
21501
|
function recompute() {
|
|
@@ -21411,7 +21506,7 @@ function useCellHovered(env, gridRef) {
|
|
|
21411
21506
|
const zoomedMouseEvent = withZoom(env, e);
|
|
21412
21507
|
const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
|
|
21413
21508
|
const gridRect = getElBoundingRect(gridRef());
|
|
21414
|
-
if (y
|
|
21509
|
+
if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
|
|
21415
21510
|
else return pause();
|
|
21416
21511
|
}
|
|
21417
21512
|
(0, _odoo_owl.useListener)(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
|
|
@@ -21448,7 +21543,7 @@ var GridOverlay = class extends Component {
|
|
|
21448
21543
|
gridOverlayDimensions: types.string(),
|
|
21449
21544
|
hasFooter: types.boolean().optional(() => true)
|
|
21450
21545
|
});
|
|
21451
|
-
gridOverlayRef =
|
|
21546
|
+
gridOverlayRef = _odoo_owl.signal.ref();
|
|
21452
21547
|
cellPopovers;
|
|
21453
21548
|
paintFormatStore;
|
|
21454
21549
|
hoveredIconStore;
|
|
@@ -21475,7 +21570,7 @@ var GridOverlay = class extends Component {
|
|
|
21475
21570
|
return el;
|
|
21476
21571
|
}
|
|
21477
21572
|
get style() {
|
|
21478
|
-
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "
|
|
21573
|
+
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
|
|
21479
21574
|
}
|
|
21480
21575
|
get isPaintingFormat() {
|
|
21481
21576
|
return this.paintFormatStore.isActive;
|
|
@@ -22115,8 +22210,9 @@ var FormulaFingerprintStore = class extends SpreadsheetStore {
|
|
|
22115
22210
|
const CELL_ANIMATION_DURATION = 200;
|
|
22116
22211
|
var GridRenderer = class extends DisposableStore {
|
|
22117
22212
|
renderer;
|
|
22213
|
+
storeGetters = ["drawLayer"];
|
|
22118
22214
|
fingerprints;
|
|
22119
|
-
|
|
22215
|
+
cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
22120
22216
|
hoveredIcon;
|
|
22121
22217
|
lastRenderSheetId = void 0;
|
|
22122
22218
|
lastRenderBoxes = /* @__PURE__ */ new Map();
|
|
@@ -22130,15 +22226,14 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22130
22226
|
const model = get(ModelStore);
|
|
22131
22227
|
this.getters = model.getters;
|
|
22132
22228
|
this.fingerprints = get(FormulaFingerprintStore);
|
|
22133
|
-
this.hoveredTables = get(HoveredTableStore);
|
|
22134
22229
|
this.hoveredIcon = get(HoveredIconStore);
|
|
22135
22230
|
model.on("command-dispatched", this, this.handle);
|
|
22136
22231
|
model.on("command-finalized", this, this.finalize);
|
|
22137
|
-
this.renderer.register(this);
|
|
22232
|
+
if (this.renderer) this.renderer.register(this);
|
|
22138
22233
|
this.onDispose(() => {
|
|
22139
22234
|
model.off("command-dispatched", this);
|
|
22140
22235
|
model.off("command-finalized", this);
|
|
22141
|
-
this.renderer.unRegister(this);
|
|
22236
|
+
if (this.renderer) this.renderer.unRegister(this);
|
|
22142
22237
|
});
|
|
22143
22238
|
}
|
|
22144
22239
|
handle(cmd) {
|
|
@@ -22682,7 +22777,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22682
22777
|
border: this.getters.getCellComputedBorder(position) || void 0,
|
|
22683
22778
|
style,
|
|
22684
22779
|
dataBarFill,
|
|
22685
|
-
overlayColor: this.
|
|
22780
|
+
overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
|
|
22686
22781
|
isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
|
|
22687
22782
|
icons: cellIcons,
|
|
22688
22783
|
disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
|
|
@@ -22858,6 +22953,7 @@ var GridRenderer = class extends DisposableStore {
|
|
|
22858
22953
|
return boxes;
|
|
22859
22954
|
}
|
|
22860
22955
|
getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
|
|
22956
|
+
if (!this.renderer) return boxes;
|
|
22861
22957
|
this.updateAnimationsProgress(timeStamp);
|
|
22862
22958
|
this.addNewAnimations(boxes, oldBoxes, timeStamp);
|
|
22863
22959
|
if (this.animations.size > 0) {
|
|
@@ -23002,7 +23098,7 @@ var ScrollBar = class extends Component {
|
|
|
23002
23098
|
offset: types.Pixel(),
|
|
23003
23099
|
onScroll: types.function()
|
|
23004
23100
|
});
|
|
23005
|
-
scrollbarRef =
|
|
23101
|
+
scrollbarRef = _odoo_owl.signal.ref();
|
|
23006
23102
|
scrollbar;
|
|
23007
23103
|
setup() {
|
|
23008
23104
|
this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
|
|
@@ -23104,6 +23200,9 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23104
23200
|
zoneToDisplay: this.range.zone,
|
|
23105
23201
|
getFooterSize: () => 0
|
|
23106
23202
|
});
|
|
23203
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
23204
|
+
cellHoverOverlayStore.register(this);
|
|
23205
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
23107
23206
|
}
|
|
23108
23207
|
handle(cmd) {
|
|
23109
23208
|
this.cachedColDimensions = void 0;
|
|
@@ -23252,6 +23351,16 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
|
|
|
23252
23351
|
}
|
|
23253
23352
|
return normalizedWeights;
|
|
23254
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
|
+
}
|
|
23255
23364
|
};
|
|
23256
23365
|
|
|
23257
23366
|
//#endregion
|
|
@@ -23268,10 +23377,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23268
23377
|
range: types.Range(),
|
|
23269
23378
|
canResizeColumns: types.boolean().optional(true),
|
|
23270
23379
|
onResizeColumns: types.function().optional(),
|
|
23271
|
-
columnWeights: types.array().optional()
|
|
23380
|
+
columnWeights: types.array().optional(),
|
|
23381
|
+
size: types.object()
|
|
23272
23382
|
});
|
|
23273
|
-
canvasRef =
|
|
23274
|
-
containerRef =
|
|
23383
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
23384
|
+
containerRef = _odoo_owl.signal.ref();
|
|
23275
23385
|
store;
|
|
23276
23386
|
dndState = (0, _odoo_owl.proxy)({ col: void 0 });
|
|
23277
23387
|
onMouseWheel;
|
|
@@ -23283,8 +23393,8 @@ var StandaloneViewport = class extends Component {
|
|
|
23283
23393
|
useChildStoreProvider([
|
|
23284
23394
|
ViewportsStore,
|
|
23285
23395
|
HoveredIconStore,
|
|
23286
|
-
HoveredTableStore,
|
|
23287
23396
|
ClickableCellsStore,
|
|
23397
|
+
CellHoverOverlayStore,
|
|
23288
23398
|
DelayedHoveredCellStore,
|
|
23289
23399
|
CellPopoverStore
|
|
23290
23400
|
]);
|
|
@@ -23293,18 +23403,10 @@ var StandaloneViewport = class extends Component {
|
|
|
23293
23403
|
this.zoomStore = useStore(ZoomStore);
|
|
23294
23404
|
this.cellPopoverStore = useStore(CellPopoverStore);
|
|
23295
23405
|
this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
|
|
23296
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
23297
|
-
this.store.setContainerSize(this.containerWidth, this.containerHeight);
|
|
23298
|
-
});
|
|
23299
|
-
(0, _odoo_owl.useEffect)(() => {
|
|
23300
|
-
const el = this.containerRef();
|
|
23301
|
-
if (el) resizeObserver.observe(el);
|
|
23302
|
-
return () => resizeObserver.disconnect();
|
|
23303
|
-
});
|
|
23304
23406
|
(0, _odoo_owl.useEffect)(() => {
|
|
23305
23407
|
this.store.setRange(this.props.range);
|
|
23306
23408
|
if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
|
|
23307
|
-
this.store.setContainerSize(this.
|
|
23409
|
+
this.store.setContainerSize(this.contentWidth, this.props.size.height);
|
|
23308
23410
|
});
|
|
23309
23411
|
useGridDrawing({
|
|
23310
23412
|
canvasRef: this.canvasRef,
|
|
@@ -23323,14 +23425,11 @@ var StandaloneViewport = class extends Component {
|
|
|
23323
23425
|
}
|
|
23324
23426
|
});
|
|
23325
23427
|
}
|
|
23326
|
-
get
|
|
23327
|
-
return
|
|
23328
|
-
}
|
|
23329
|
-
get containerHeight() {
|
|
23330
|
-
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;
|
|
23331
23430
|
}
|
|
23332
23431
|
get hasVerticalScrollBar() {
|
|
23333
|
-
return this.
|
|
23432
|
+
return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
|
|
23334
23433
|
}
|
|
23335
23434
|
get scrollBarContainerStyle() {
|
|
23336
23435
|
return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
|
|
@@ -23382,7 +23481,7 @@ var StandaloneViewport = class extends Component {
|
|
|
23382
23481
|
};
|
|
23383
23482
|
const onMouseMove = (ev) => {
|
|
23384
23483
|
deltaX = withZoom(this.env, ev).clientX - initialX;
|
|
23385
|
-
const weightDelta = deltaX / this.
|
|
23484
|
+
const weightDelta = deltaX / this.props.size.width * totalWeight;
|
|
23386
23485
|
this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
|
|
23387
23486
|
};
|
|
23388
23487
|
startDnd(onMouseMove, onMouseUp);
|
|
@@ -23412,8 +23511,8 @@ var Select = class extends Component {
|
|
|
23412
23511
|
name: types.string().optional(),
|
|
23413
23512
|
title: types.string().optional()
|
|
23414
23513
|
});
|
|
23415
|
-
selectRef =
|
|
23416
|
-
dropdownRef =
|
|
23514
|
+
selectRef = _odoo_owl.signal.ref();
|
|
23515
|
+
dropdownRef = _odoo_owl.signal.ref();
|
|
23417
23516
|
state = (0, _odoo_owl.proxy)({
|
|
23418
23517
|
isPopoverOpen: false,
|
|
23419
23518
|
hoveredValue: void 0
|
|
@@ -23523,7 +23622,7 @@ var InfoPopover = class extends Component {
|
|
|
23523
23622
|
setup() {
|
|
23524
23623
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
23525
23624
|
}
|
|
23526
|
-
infoRef =
|
|
23625
|
+
infoRef = _odoo_owl.signal.ref();
|
|
23527
23626
|
get popoverProps() {
|
|
23528
23627
|
return {
|
|
23529
23628
|
anchorRect: this.props.anchorRect,
|
|
@@ -23668,8 +23767,8 @@ var CarouselFigure = class extends Component {
|
|
|
23668
23767
|
isFullScreen: types.boolean().optional(),
|
|
23669
23768
|
openContextMenu: types.function().optional()
|
|
23670
23769
|
});
|
|
23671
|
-
carouselTabsRef =
|
|
23672
|
-
carouselTabsDropdownRef =
|
|
23770
|
+
carouselTabsRef = _odoo_owl.signal.ref();
|
|
23771
|
+
carouselTabsDropdownRef = _odoo_owl.signal.ref();
|
|
23673
23772
|
menuState = (0, _odoo_owl.proxy)({
|
|
23674
23773
|
isOpen: false,
|
|
23675
23774
|
anchorRect: null,
|
|
@@ -23737,6 +23836,19 @@ var CarouselFigure = class extends Component {
|
|
|
23737
23836
|
} else cssProperties["background-color"] = backgroundColor;
|
|
23738
23837
|
return cssPropertiesToCss(cssProperties);
|
|
23739
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
|
+
}
|
|
23740
23852
|
get title() {
|
|
23741
23853
|
return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
|
|
23742
23854
|
}
|
|
@@ -23746,6 +23858,14 @@ var CarouselFigure = class extends Component {
|
|
|
23746
23858
|
...this.carousel.title
|
|
23747
23859
|
})));
|
|
23748
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
|
+
}
|
|
23749
23869
|
updateTabsVisibility() {
|
|
23750
23870
|
const tabsContainerEl = this.carouselTabsRef();
|
|
23751
23871
|
const dropDownEl = this.carouselTabsDropdownRef();
|
|
@@ -23837,6 +23957,13 @@ var CarouselFigure = class extends Component {
|
|
|
23837
23957
|
get canResizeDataViewColumns() {
|
|
23838
23958
|
return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
23839
23959
|
}
|
|
23960
|
+
get carouselLayout() {
|
|
23961
|
+
return getCarouselLayout({
|
|
23962
|
+
...this.props.figureUI,
|
|
23963
|
+
x: 0,
|
|
23964
|
+
y: 0
|
|
23965
|
+
}, this.carousel, this.selectedCarouselItem);
|
|
23966
|
+
}
|
|
23840
23967
|
};
|
|
23841
23968
|
|
|
23842
23969
|
//#endregion
|
|
@@ -23877,7 +24004,7 @@ var ImageFigure = class extends Component {
|
|
|
23877
24004
|
figureUI: types.FigureUI(),
|
|
23878
24005
|
openContextMenu: types.function()
|
|
23879
24006
|
});
|
|
23880
|
-
menuButtonRef =
|
|
24007
|
+
menuButtonRef = _odoo_owl.signal.ref();
|
|
23881
24008
|
showMenu(ev) {
|
|
23882
24009
|
if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
|
|
23883
24010
|
figureId: this.props.figureUI.id,
|
|
@@ -23905,7 +24032,8 @@ figureRegistry.add("chart", {
|
|
|
23905
24032
|
menuBuilder: getChartMenuActions,
|
|
23906
24033
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23907
24034
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23908
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24035
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24036
|
+
isThemeDependant: true
|
|
23909
24037
|
});
|
|
23910
24038
|
figureRegistry.add("image", {
|
|
23911
24039
|
Component: ImageFigure,
|
|
@@ -23914,14 +24042,16 @@ figureRegistry.add("image", {
|
|
|
23914
24042
|
borderWidth: () => 0,
|
|
23915
24043
|
hasShadow: () => false,
|
|
23916
24044
|
isRounded: () => false,
|
|
23917
|
-
menuBuilder: getImageMenuActions
|
|
24045
|
+
menuBuilder: getImageMenuActions,
|
|
24046
|
+
isThemeDependant: false
|
|
23918
24047
|
});
|
|
23919
24048
|
figureRegistry.add("carousel", {
|
|
23920
24049
|
Component: CarouselFigure,
|
|
23921
24050
|
menuBuilder: getCarouselMenuActions,
|
|
23922
24051
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23923
24052
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23924
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24053
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24054
|
+
isThemeDependant: true
|
|
23925
24055
|
});
|
|
23926
24056
|
|
|
23927
24057
|
//#endregion
|
|
@@ -23943,10 +24073,13 @@ var FigureComponent = class extends Component {
|
|
|
23943
24073
|
anchorRect: null,
|
|
23944
24074
|
menuItems: []
|
|
23945
24075
|
});
|
|
23946
|
-
figureRef =
|
|
24076
|
+
figureRef = _odoo_owl.signal.ref();
|
|
23947
24077
|
get isSelected() {
|
|
23948
24078
|
return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
|
|
23949
24079
|
}
|
|
24080
|
+
get isThemeDependant() {
|
|
24081
|
+
return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
|
|
24082
|
+
}
|
|
23950
24083
|
get figureRegistry() {
|
|
23951
24084
|
return figureRegistry;
|
|
23952
24085
|
}
|
|
@@ -29545,6 +29678,35 @@ async function interactivePasteFromOS(env, target, parsedClipboardContent, paste
|
|
|
29545
29678
|
}));
|
|
29546
29679
|
}
|
|
29547
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
|
+
|
|
29548
29710
|
//#endregion
|
|
29549
29711
|
//#region src/helpers/figures/charts/smart_chart_engine.ts
|
|
29550
29712
|
const DEFAULT_BAR_CHART_CONFIG = {
|
|
@@ -29575,9 +29737,6 @@ const DEFAULT_LINE_CHART_CONFIG = {
|
|
|
29575
29737
|
labelsAsText: false,
|
|
29576
29738
|
humanize: true
|
|
29577
29739
|
};
|
|
29578
|
-
function getUnboundRange(getters, zone) {
|
|
29579
|
-
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29580
|
-
}
|
|
29581
29740
|
function detectColumnType(cells) {
|
|
29582
29741
|
if (!cells.length) return "empty";
|
|
29583
29742
|
const counts = {
|
|
@@ -29624,14 +29783,6 @@ function getCellStats(getters, zone) {
|
|
|
29624
29783
|
totalCount: values.length
|
|
29625
29784
|
};
|
|
29626
29785
|
}
|
|
29627
|
-
function isDatasetTitled(getters, column) {
|
|
29628
|
-
const titleCell = getters.getEvaluatedCell({
|
|
29629
|
-
sheetId: getters.getActiveSheetId(),
|
|
29630
|
-
col: column.zone.left,
|
|
29631
|
-
row: column.zone.top
|
|
29632
|
-
});
|
|
29633
|
-
return !["number", "empty"].includes(titleCell.type);
|
|
29634
|
-
}
|
|
29635
29786
|
/**
|
|
29636
29787
|
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
29637
29788
|
* - If the column contains a single cell, create a scorecard.
|
|
@@ -29643,8 +29794,7 @@ function isDatasetTitled(getters, column) {
|
|
|
29643
29794
|
function buildSingleColumnChart(column, getters) {
|
|
29644
29795
|
const { type, zone } = column;
|
|
29645
29796
|
const sheetId = getters.getActiveSheetId();
|
|
29646
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
29647
|
-
const dataRange = getUnboundRange(getters, zone);
|
|
29797
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
|
|
29648
29798
|
const titleCell = getters.getEvaluatedCell({
|
|
29649
29799
|
sheetId,
|
|
29650
29800
|
col: zone.left,
|
|
@@ -29655,14 +29805,7 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29655
29805
|
case "percentage": return {
|
|
29656
29806
|
type: "pie",
|
|
29657
29807
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29658
|
-
dataSource:
|
|
29659
|
-
type: "range",
|
|
29660
|
-
dataSets: [{
|
|
29661
|
-
dataRange,
|
|
29662
|
-
dataSetId: "0"
|
|
29663
|
-
}],
|
|
29664
|
-
dataSetsHaveTitle
|
|
29665
|
-
},
|
|
29808
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29666
29809
|
dataSetStyles: {},
|
|
29667
29810
|
legendPosition: "none"
|
|
29668
29811
|
};
|
|
@@ -29673,45 +29816,28 @@ function buildSingleColumnChart(column, getters) {
|
|
|
29673
29816
|
return {
|
|
29674
29817
|
type: "pie",
|
|
29675
29818
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
29676
|
-
dataSource:
|
|
29677
|
-
type: "range",
|
|
29678
|
-
dataSets: [{
|
|
29679
|
-
dataRange,
|
|
29680
|
-
dataSetId: "0"
|
|
29681
|
-
}],
|
|
29682
|
-
labelRange: dataRange,
|
|
29683
|
-
dataSetsHaveTitle: hasUniqueTitle
|
|
29684
|
-
},
|
|
29819
|
+
dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
|
|
29685
29820
|
dataSetStyles: {},
|
|
29686
29821
|
aggregated: true,
|
|
29687
29822
|
legendPosition: "top"
|
|
29688
29823
|
};
|
|
29689
|
-
case "date":
|
|
29690
|
-
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
29697
|
-
|
|
29698
|
-
|
|
29699
|
-
|
|
29700
|
-
|
|
29701
|
-
verticalGroupBy: "month_number"
|
|
29702
|
-
};
|
|
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
|
+
}
|
|
29703
29836
|
}
|
|
29704
29837
|
return {
|
|
29705
29838
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29706
29839
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29707
|
-
dataSource:
|
|
29708
|
-
type: "range",
|
|
29709
|
-
dataSets: [{
|
|
29710
|
-
dataRange,
|
|
29711
|
-
dataSetId: "0"
|
|
29712
|
-
}],
|
|
29713
|
-
dataSetsHaveTitle
|
|
29714
|
-
},
|
|
29840
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29715
29841
|
dataSetStyles: {}
|
|
29716
29842
|
};
|
|
29717
29843
|
}
|
|
@@ -29729,15 +29855,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29729
29855
|
if (columns[1].type === "percentage") return {
|
|
29730
29856
|
type: "pie",
|
|
29731
29857
|
title: {},
|
|
29732
|
-
dataSource:
|
|
29733
|
-
type: "range",
|
|
29734
|
-
dataSets: [{
|
|
29735
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29736
|
-
dataSetId: "0"
|
|
29737
|
-
}],
|
|
29738
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29739
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29740
|
-
},
|
|
29858
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29741
29859
|
dataSetStyles: {},
|
|
29742
29860
|
aggregated: true,
|
|
29743
29861
|
legendPosition: "none"
|
|
@@ -29745,15 +29863,7 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29745
29863
|
if (columns[0].type === "number" && columns[1].type === "number") return {
|
|
29746
29864
|
type: "scatter",
|
|
29747
29865
|
title: {},
|
|
29748
|
-
dataSource:
|
|
29749
|
-
type: "range",
|
|
29750
|
-
dataSets: [{
|
|
29751
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29752
|
-
dataSetId: "0"
|
|
29753
|
-
}],
|
|
29754
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29755
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29756
|
-
},
|
|
29866
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29757
29867
|
dataSetStyles: {},
|
|
29758
29868
|
labelsAsText: false,
|
|
29759
29869
|
legendPosition: "none"
|
|
@@ -29761,49 +29871,25 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29761
29871
|
if (columns[0].type === "date" && columns[1].type === "number") return {
|
|
29762
29872
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29763
29873
|
type: "line",
|
|
29764
|
-
dataSource:
|
|
29765
|
-
type: "range",
|
|
29766
|
-
dataSets: [{
|
|
29767
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29768
|
-
dataSetId: "0"
|
|
29769
|
-
}],
|
|
29770
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29771
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
|
|
29772
|
-
},
|
|
29874
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29773
29875
|
dataSetStyles: {}
|
|
29774
29876
|
};
|
|
29775
29877
|
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
29776
29878
|
const textColumn = columns[0];
|
|
29777
29879
|
const numberColumn = columns[1];
|
|
29778
29880
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
29779
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
29881
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
|
|
29780
29882
|
if (uniqueCount !== totalCount) return {
|
|
29781
29883
|
type: "treemap",
|
|
29782
29884
|
title: {},
|
|
29783
|
-
dataSource:
|
|
29784
|
-
type: "range",
|
|
29785
|
-
dataSets: [{
|
|
29786
|
-
dataRange: getUnboundRange(getters, textColumn.zone),
|
|
29787
|
-
dataSetId: "0"
|
|
29788
|
-
}],
|
|
29789
|
-
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
29790
|
-
dataSetsHaveTitle
|
|
29791
|
-
},
|
|
29885
|
+
dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
|
|
29792
29886
|
dataSetStyles: {},
|
|
29793
29887
|
legendPosition: "none"
|
|
29794
29888
|
};
|
|
29795
29889
|
}
|
|
29796
29890
|
return {
|
|
29797
29891
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29798
|
-
dataSource:
|
|
29799
|
-
type: "range",
|
|
29800
|
-
dataSets: [{
|
|
29801
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29802
|
-
dataSetId: "0"
|
|
29803
|
-
}],
|
|
29804
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29805
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29806
|
-
},
|
|
29892
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29807
29893
|
dataSetStyles: {}
|
|
29808
29894
|
};
|
|
29809
29895
|
}
|
|
@@ -29817,42 +29903,26 @@ function buildTwoColumnChart(columns, getters) {
|
|
|
29817
29903
|
*/
|
|
29818
29904
|
function buildMultiColumnChart(columns, getters) {
|
|
29819
29905
|
if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
|
|
29820
|
-
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
29906
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
|
|
29821
29907
|
const lastColumn = columns[columns.length - 1];
|
|
29822
29908
|
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
29823
29909
|
if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
|
|
29824
|
-
const dataSets = columnsExceptLast.map(({ zone }, i) => (
|
|
29825
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29826
|
-
dataSetId: i.toString()
|
|
29827
|
-
}));
|
|
29910
|
+
const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29828
29911
|
return {
|
|
29829
29912
|
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
29830
29913
|
title: {},
|
|
29831
|
-
dataSource:
|
|
29832
|
-
type: "range",
|
|
29833
|
-
dataSets,
|
|
29834
|
-
dataSetsHaveTitle,
|
|
29835
|
-
labelRange: getUnboundRange(getters, lastColumn.zone)
|
|
29836
|
-
},
|
|
29914
|
+
dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
|
|
29837
29915
|
dataSetStyles: {},
|
|
29838
29916
|
legendPosition: "none"
|
|
29839
29917
|
};
|
|
29840
29918
|
}
|
|
29841
29919
|
const firstColumn = columns[0];
|
|
29842
29920
|
const columnsExceptFirst = columns.slice(1);
|
|
29843
|
-
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => (
|
|
29844
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29845
|
-
dataSetId: i.toString()
|
|
29846
|
-
}));
|
|
29921
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29847
29922
|
if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
|
|
29848
29923
|
type: "pie",
|
|
29849
29924
|
title: {},
|
|
29850
|
-
dataSource:
|
|
29851
|
-
type: "range",
|
|
29852
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29853
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29854
|
-
dataSetsHaveTitle
|
|
29855
|
-
},
|
|
29925
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29856
29926
|
dataSetStyles: {},
|
|
29857
29927
|
aggregated: false,
|
|
29858
29928
|
legendPosition: "top"
|
|
@@ -29860,23 +29930,13 @@ function buildMultiColumnChart(columns, getters) {
|
|
|
29860
29930
|
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
|
|
29861
29931
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29862
29932
|
type: "line",
|
|
29863
|
-
dataSource:
|
|
29864
|
-
type: "range",
|
|
29865
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29866
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29867
|
-
dataSetsHaveTitle
|
|
29868
|
-
},
|
|
29933
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29869
29934
|
dataSetStyles: {},
|
|
29870
29935
|
legendPosition: "top"
|
|
29871
29936
|
};
|
|
29872
29937
|
return {
|
|
29873
29938
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29874
|
-
dataSource:
|
|
29875
|
-
type: "range",
|
|
29876
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29877
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29878
|
-
dataSetsHaveTitle
|
|
29879
|
-
},
|
|
29939
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29880
29940
|
dataSetStyles: {},
|
|
29881
29941
|
legendPosition: "top"
|
|
29882
29942
|
};
|
|
@@ -29903,17 +29963,10 @@ function buildScorecard(zone, getters) {
|
|
|
29903
29963
|
function getSmartChartDefinition(zones, getters) {
|
|
29904
29964
|
const columns = categorizeColumns(zones, getters);
|
|
29905
29965
|
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
29906
|
-
const dataSets = columns.map(({ zone }, i) => (
|
|
29907
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29908
|
-
dataSetId: i.toString()
|
|
29909
|
-
}));
|
|
29966
|
+
const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29910
29967
|
return {
|
|
29911
29968
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29912
|
-
dataSource:
|
|
29913
|
-
type: "range",
|
|
29914
|
-
dataSets,
|
|
29915
|
-
dataSetsHaveTitle: false
|
|
29916
|
-
}
|
|
29969
|
+
dataSource: rangeSource(dataSets, false)
|
|
29917
29970
|
};
|
|
29918
29971
|
}
|
|
29919
29972
|
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
@@ -31601,7 +31654,7 @@ var SelectionInput = class extends Component {
|
|
|
31601
31654
|
dragAndDrop = useDragAndDropListItems();
|
|
31602
31655
|
focusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31603
31656
|
unfocusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31604
|
-
selectionRef =
|
|
31657
|
+
selectionRef = _odoo_owl.signal.ref();
|
|
31605
31658
|
DOMFocusableElementStore;
|
|
31606
31659
|
store;
|
|
31607
31660
|
get ranges() {
|
|
@@ -32108,7 +32161,7 @@ var ColorPickerWidget = class extends Component {
|
|
|
32108
32161
|
dropdownMaxHeight: types.Pixel().optional(),
|
|
32109
32162
|
class: types.string().optional()
|
|
32110
32163
|
});
|
|
32111
|
-
colorPickerButtonRef =
|
|
32164
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
32112
32165
|
get iconStyle() {
|
|
32113
32166
|
return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
|
|
32114
32167
|
}
|
|
@@ -32136,8 +32189,8 @@ var NumberEditor = class extends Component {
|
|
|
32136
32189
|
});
|
|
32137
32190
|
dropdown = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
32138
32191
|
inputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
32139
|
-
rootEditorRef =
|
|
32140
|
-
valueListRef =
|
|
32192
|
+
rootEditorRef = _odoo_owl.signal.ref();
|
|
32193
|
+
valueListRef = _odoo_owl.signal.ref();
|
|
32141
32194
|
DOMFocusableElementStore;
|
|
32142
32195
|
setup() {
|
|
32143
32196
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -32443,7 +32496,7 @@ var ChartTypePickerPopover = class extends Component {
|
|
|
32443
32496
|
});
|
|
32444
32497
|
categories = chartCategories;
|
|
32445
32498
|
chartTypeByCategories = {};
|
|
32446
|
-
popoverRef =
|
|
32499
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
32447
32500
|
setup() {
|
|
32448
32501
|
(0, _odoo_owl.useListener)(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
|
|
32449
32502
|
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
@@ -32468,7 +32521,7 @@ var CogWheelMenu = class extends Component {
|
|
|
32468
32521
|
static template = "o-spreadsheet-CogWheelMenu";
|
|
32469
32522
|
static components = { MenuPopover };
|
|
32470
32523
|
props = (0, _odoo_owl.useProps)({ items: types.array(types.ActionSpec()) });
|
|
32471
|
-
buttonRef =
|
|
32524
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
32472
32525
|
menuState = (0, _odoo_owl.proxy)({
|
|
32473
32526
|
isOpen: false,
|
|
32474
32527
|
anchorRect: null,
|
|
@@ -32512,8 +32565,8 @@ var CarouselPanel = class extends Component {
|
|
|
32512
32565
|
});
|
|
32513
32566
|
DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
|
|
32514
32567
|
dragAndDrop = useDragAndDropListItems();
|
|
32515
|
-
previewListRef =
|
|
32516
|
-
addChartButton =
|
|
32568
|
+
previewListRef = _odoo_owl.signal.ref();
|
|
32569
|
+
addChartButton = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
32517
32570
|
state = (0, _odoo_owl.proxy)({
|
|
32518
32571
|
popoverProps: void 0,
|
|
32519
32572
|
currentRange: void 0
|
|
@@ -32957,7 +33010,7 @@ var BarConfigPanel = class extends GenericChartConfigPanel {
|
|
|
32957
33010
|
var Collapse = class extends Component {
|
|
32958
33011
|
static template = "o-spreadsheet-Collapse";
|
|
32959
33012
|
props = (0, _odoo_owl.useProps)({ isCollapsed: types.boolean() });
|
|
32960
|
-
contentRef =
|
|
33013
|
+
contentRef = _odoo_owl.signal.ref();
|
|
32961
33014
|
setup() {
|
|
32962
33015
|
(0, _odoo_owl.onMounted)(() => {
|
|
32963
33016
|
if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
|
|
@@ -33010,7 +33063,7 @@ var ChartAnnotation = class extends Component {
|
|
|
33010
33063
|
TextInput
|
|
33011
33064
|
};
|
|
33012
33065
|
props = (0, _odoo_owl.useProps)(chartSidePanelPropsDefinition);
|
|
33013
|
-
editorRef =
|
|
33066
|
+
editorRef = _odoo_owl.signal.ref();
|
|
33014
33067
|
state = (0, _odoo_owl.proxy)({
|
|
33015
33068
|
annotationTextInput: this.props.definition.annotationText || "",
|
|
33016
33069
|
isOverflowing: false
|
|
@@ -33332,7 +33385,7 @@ var RoundColorPicker = class extends Component {
|
|
|
33332
33385
|
onColorPicked: types.function(),
|
|
33333
33386
|
disableNoColor: types.boolean().optional()
|
|
33334
33387
|
});
|
|
33335
|
-
colorPickerButtonRef =
|
|
33388
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
33336
33389
|
state;
|
|
33337
33390
|
setup() {
|
|
33338
33391
|
this.state = (0, _odoo_owl.proxy)({ pickerOpened: false });
|
|
@@ -36174,6 +36227,7 @@ var helpers_index_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
36174
36227
|
getDefinedAxis: () => getDefinedAxis,
|
|
36175
36228
|
getPieColors: () => getPieColors,
|
|
36176
36229
|
isTrendLineAxis: () => isTrendLineAxis,
|
|
36230
|
+
limitChartConfigDataPoints: () => limitChartConfigDataPoints,
|
|
36177
36231
|
shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
|
|
36178
36232
|
toExcelDataset: () => toExcelDataset,
|
|
36179
36233
|
toExcelLabelRange: () => toExcelLabelRange,
|
|
@@ -36944,7 +36998,7 @@ var ColorScalePicker = class extends Component {
|
|
|
36944
36998
|
popoverProps: void 0,
|
|
36945
36999
|
popoverStyle: ""
|
|
36946
37000
|
});
|
|
36947
|
-
popoverRef =
|
|
37001
|
+
popoverRef = _odoo_owl.signal.ref(HTMLTableElement);
|
|
36948
37002
|
setup() {
|
|
36949
37003
|
useExternalListener(window, "click", this.closePopover);
|
|
36950
37004
|
}
|
|
@@ -37193,7 +37247,7 @@ var TextValueProvider = class extends Component {
|
|
|
37193
37247
|
onValueSelected: types.function(),
|
|
37194
37248
|
onValueHovered: types.function()
|
|
37195
37249
|
});
|
|
37196
|
-
autoCompleteListRef =
|
|
37250
|
+
autoCompleteListRef = _odoo_owl.signal.ref();
|
|
37197
37251
|
setup() {
|
|
37198
37252
|
(0, _odoo_owl.useEffect)(() => {
|
|
37199
37253
|
const selectedIndex = this.props.selectedIndex;
|
|
@@ -37484,7 +37538,7 @@ var SpeechBubble = class extends Component {
|
|
|
37484
37538
|
anchorRect: types.Rect()
|
|
37485
37539
|
});
|
|
37486
37540
|
spreadsheetRect = useSpreadsheetRect();
|
|
37487
|
-
bubbleRef =
|
|
37541
|
+
bubbleRef = _odoo_owl.signal.ref();
|
|
37488
37542
|
setup() {
|
|
37489
37543
|
(0, _odoo_owl.useEffect)(() => {
|
|
37490
37544
|
const el = this.bubbleRef();
|
|
@@ -37529,8 +37583,8 @@ var Composer = class extends Component {
|
|
|
37529
37583
|
showAssistant: types.boolean().optional(true)
|
|
37530
37584
|
});
|
|
37531
37585
|
DOMFocusableElementStore;
|
|
37532
|
-
composerRef =
|
|
37533
|
-
containerRef =
|
|
37586
|
+
composerRef = _odoo_owl.signal.ref();
|
|
37587
|
+
containerRef = _odoo_owl.signal.ref();
|
|
37534
37588
|
contentHelper = new ContentEditableHelper(this.composerRef());
|
|
37535
37589
|
composerState = (0, _odoo_owl.proxy)({
|
|
37536
37590
|
positionStart: 0,
|
|
@@ -40032,7 +40086,7 @@ var ChartTypePicker = class extends Component {
|
|
|
40032
40086
|
chartId: types.UID(),
|
|
40033
40087
|
chartPanelStore: types.Store()
|
|
40034
40088
|
});
|
|
40035
|
-
selectRef =
|
|
40089
|
+
selectRef = _odoo_owl.signal.ref();
|
|
40036
40090
|
state = (0, _odoo_owl.proxy)({ popoverProps: void 0 });
|
|
40037
40091
|
getSupportedChartTypes() {
|
|
40038
40092
|
let supportedTypes;
|
|
@@ -40600,7 +40654,7 @@ var ColumnStatsPanel = class extends Component {
|
|
|
40600
40654
|
highlightPositions: []
|
|
40601
40655
|
});
|
|
40602
40656
|
store;
|
|
40603
|
-
chartCanvasRef =
|
|
40657
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
40604
40658
|
chart;
|
|
40605
40659
|
setup() {
|
|
40606
40660
|
this.store = useStore(ColumnStatisticsStore);
|
|
@@ -42028,7 +42082,7 @@ var ConditionalFormatPreview = class extends Component {
|
|
|
42028
42082
|
class: types.string()
|
|
42029
42083
|
});
|
|
42030
42084
|
icons = ICONS;
|
|
42031
|
-
cfPreviewRef =
|
|
42085
|
+
cfPreviewRef = _odoo_owl.signal.ref();
|
|
42032
42086
|
setup() {
|
|
42033
42087
|
useHighlightsOnHover(this.cfPreviewRef, this);
|
|
42034
42088
|
}
|
|
@@ -42085,7 +42139,7 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42085
42139
|
static components = { ConditionalFormatPreview };
|
|
42086
42140
|
props = (0, _odoo_owl.useProps)({ onCloseSidePanel: types.function() });
|
|
42087
42141
|
dragAndDrop = useDragAndDropListItems();
|
|
42088
|
-
cfListRef =
|
|
42142
|
+
cfListRef = _odoo_owl.signal.ref();
|
|
42089
42143
|
get conditionalFormats() {
|
|
42090
42144
|
return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
|
|
42091
42145
|
}
|
|
@@ -42145,6 +42199,1477 @@ var ConditionalFormatPreviewList = class extends Component {
|
|
|
42145
42199
|
}
|
|
42146
42200
|
};
|
|
42147
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
|
+
|
|
42148
43673
|
//#endregion
|
|
42149
43674
|
//#region src/registries/data_source_registry.ts
|
|
42150
43675
|
const unusedDataSourceRegistry = new Registry();
|
|
@@ -42221,7 +43746,7 @@ var DataSourcesCleanup = class extends Component {
|
|
|
42221
43746
|
var DataValidationPreview = class extends Component {
|
|
42222
43747
|
static template = "o-spreadsheet-DataValidationPreview";
|
|
42223
43748
|
props = (0, _odoo_owl.useProps)({ rule: types.DataValidationRule() });
|
|
42224
|
-
dvPreviewRef =
|
|
43749
|
+
dvPreviewRef = _odoo_owl.signal.ref();
|
|
42225
43750
|
setup() {
|
|
42226
43751
|
useHighlightsOnHover(this.dvPreviewRef, this);
|
|
42227
43752
|
}
|
|
@@ -43720,7 +45245,7 @@ var NamedRangePreview = class extends Component {
|
|
|
43720
45245
|
};
|
|
43721
45246
|
props = (0, _odoo_owl.useProps)({ namedRange: types.NamedRange() });
|
|
43722
45247
|
state = (0, _odoo_owl.proxy)({});
|
|
43723
|
-
namedRangePreviewRef =
|
|
45248
|
+
namedRangePreviewRef = _odoo_owl.signal.ref();
|
|
43724
45249
|
setup() {
|
|
43725
45250
|
useHighlightsOnHover(this.namedRangePreviewRef, this);
|
|
43726
45251
|
}
|
|
@@ -44211,7 +45736,7 @@ var FilterMenuValueItem = class extends Component {
|
|
|
44211
45736
|
onClick: types.function(),
|
|
44212
45737
|
scrolledTo: types.or([types.literal("top"), types.literal("bottom")]).optional()
|
|
44213
45738
|
});
|
|
44214
|
-
itemRef =
|
|
45739
|
+
itemRef = _odoo_owl.signal.ref();
|
|
44215
45740
|
setup() {
|
|
44216
45741
|
(0, _odoo_owl.onWillPatch)(() => {
|
|
44217
45742
|
if (this.props.scrolledTo) this.scrollListToSelectedValue();
|
|
@@ -44451,7 +45976,7 @@ var PivotFilterEditor = class extends Component {
|
|
|
44451
45976
|
onFiltersUpdated: types.function()
|
|
44452
45977
|
});
|
|
44453
45978
|
state;
|
|
44454
|
-
buttonFilter =
|
|
45979
|
+
buttonFilter = _odoo_owl.signal.ref();
|
|
44455
45980
|
popover;
|
|
44456
45981
|
setup() {
|
|
44457
45982
|
useExternalListener(window, "click", (ev) => {
|
|
@@ -44580,11 +46105,11 @@ var AddDimensionButton = class extends Component {
|
|
|
44580
46105
|
onFieldPicked: types.function(),
|
|
44581
46106
|
fields: types.array()
|
|
44582
46107
|
});
|
|
44583
|
-
buttonRef =
|
|
46108
|
+
buttonRef = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
44584
46109
|
popover = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
44585
46110
|
search = (0, _odoo_owl.proxy)({ input: "" });
|
|
44586
46111
|
autoComplete;
|
|
44587
|
-
autofocusRef =
|
|
46112
|
+
autofocusRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
44588
46113
|
setup() {
|
|
44589
46114
|
this.autoComplete = useLocalStore(AutoCompleteStore);
|
|
44590
46115
|
this.autoComplete.useProvider(this.getProvider());
|
|
@@ -44993,7 +46518,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
44993
46518
|
getScrollableContainerEl: types.function().optional(),
|
|
44994
46519
|
pivotId: types.UID()
|
|
44995
46520
|
});
|
|
44996
|
-
dimensionsRef =
|
|
46521
|
+
dimensionsRef = _odoo_owl.signal.ref();
|
|
44997
46522
|
dragAndDrop = useDragAndDropListItems();
|
|
44998
46523
|
AGGREGATORS = AGGREGATORS;
|
|
44999
46524
|
isDateOrDatetimeField = isDateOrDatetimeField;
|
|
@@ -45064,16 +46589,7 @@ var PivotLayoutConfigurator = class extends Component {
|
|
|
45064
46589
|
});
|
|
45065
46590
|
}
|
|
45066
46591
|
getDimensionElementsRects() {
|
|
45067
|
-
return Array.from(this.dimensionsRef().children).map((el) =>
|
|
45068
|
-
const style = getComputedStyle(el);
|
|
45069
|
-
const rect = el.getBoundingClientRect();
|
|
45070
|
-
return {
|
|
45071
|
-
x: rect.x,
|
|
45072
|
-
y: rect.y,
|
|
45073
|
-
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
45074
|
-
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
45075
|
-
};
|
|
45076
|
-
});
|
|
46592
|
+
return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
|
|
45077
46593
|
}
|
|
45078
46594
|
removeDimension(dimension) {
|
|
45079
46595
|
const { columns, rows } = this.props.definition;
|
|
@@ -45638,7 +47154,7 @@ var PivotSpreadsheetSidePanel = class extends Component {
|
|
|
45638
47154
|
});
|
|
45639
47155
|
store;
|
|
45640
47156
|
state;
|
|
45641
|
-
pivotSidePanelRef =
|
|
47157
|
+
pivotSidePanelRef = _odoo_owl.signal.ref();
|
|
45642
47158
|
setup() {
|
|
45643
47159
|
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
|
|
45644
47160
|
this.state = (0, _odoo_owl.proxy)({
|
|
@@ -46878,7 +48394,7 @@ var TableStylePreview = class extends Component {
|
|
|
46878
48394
|
selected: types.boolean().optional(),
|
|
46879
48395
|
onClick: types.function().optional()
|
|
46880
48396
|
});
|
|
46881
|
-
canvasRef =
|
|
48397
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
46882
48398
|
menu = (0, _odoo_owl.proxy)({
|
|
46883
48399
|
isOpen: false,
|
|
46884
48400
|
anchorRect: null,
|
|
@@ -46972,7 +48488,7 @@ var TableStylesPopover = class extends Component {
|
|
|
46972
48488
|
tableStyles: types.RecordOf(),
|
|
46973
48489
|
type: types.or([types.literal("table"), types.literal("pivot")])
|
|
46974
48490
|
});
|
|
46975
|
-
tableStyleListRef =
|
|
48491
|
+
tableStyleListRef = _odoo_owl.signal.ref();
|
|
46976
48492
|
state = (0, _odoo_owl.proxy)({ selectedCategory: this.initialSelectedCategory });
|
|
46977
48493
|
setup() {
|
|
46978
48494
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
@@ -48343,6 +49859,10 @@ sidePanelRegistry.add("ColumnStats", {
|
|
|
48343
49859
|
title: _t("Column statistics"),
|
|
48344
49860
|
Body: ColumnStatsPanel
|
|
48345
49861
|
});
|
|
49862
|
+
sidePanelRegistry.add("DataAnalysisPanel", {
|
|
49863
|
+
title: _t("Data analysis"),
|
|
49864
|
+
Body: DataAnalysisPanel
|
|
49865
|
+
});
|
|
48346
49866
|
sidePanelRegistry.add("TableSidePanel", {
|
|
48347
49867
|
title: _t("Edit table"),
|
|
48348
49868
|
Body: TablePanel,
|
|
@@ -48450,6 +49970,7 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48450
49970
|
"changePanelSize",
|
|
48451
49971
|
"resetPanelSize",
|
|
48452
49972
|
"togglePinPanel",
|
|
49973
|
+
"togglePinnedSidePanel",
|
|
48453
49974
|
"closeMainPanel",
|
|
48454
49975
|
"changeSpreadsheetWidth",
|
|
48455
49976
|
"toggleCollapsePanel"
|
|
@@ -48601,6 +50122,19 @@ var SidePanelStore = class extends SpreadsheetStore {
|
|
|
48601
50122
|
this.secondaryPanel = void 0;
|
|
48602
50123
|
}
|
|
48603
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
|
+
}
|
|
48604
50138
|
toggleCollapsePanel(panel) {
|
|
48605
50139
|
const panelInfo = this[panel];
|
|
48606
50140
|
if (!panelInfo) return;
|
|
@@ -48979,6 +50513,219 @@ var MenuItemRegistry = class extends Registry {
|
|
|
48979
50513
|
}
|
|
48980
50514
|
};
|
|
48981
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
|
+
|
|
48982
50729
|
//#endregion
|
|
48983
50730
|
//#region src/helpers/ui/merge_interactive.ts
|
|
48984
50731
|
const AddMergeInteractiveContent = {
|
|
@@ -49193,6 +50940,10 @@ cellMenuRegistry.add("cut", {
|
|
|
49193
50940
|
}).addChild("paste_format_only", ["paste_special"], {
|
|
49194
50941
|
...pasteSpecialFormat,
|
|
49195
50942
|
sequence: 20
|
|
50943
|
+
}).add("data_analysis", {
|
|
50944
|
+
...dataAnalysis,
|
|
50945
|
+
sequence: 50,
|
|
50946
|
+
separator: true
|
|
49196
50947
|
}).add("add_row_before", {
|
|
49197
50948
|
...cellInsertRowsBefore,
|
|
49198
50949
|
sequence: 70
|
|
@@ -49300,211 +51051,6 @@ cellMenuRegistry.add("cut", {
|
|
|
49300
51051
|
sequence: 30
|
|
49301
51052
|
});
|
|
49302
51053
|
|
|
49303
|
-
//#endregion
|
|
49304
|
-
//#region src/helpers/sort_interactive.ts
|
|
49305
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
49306
|
-
let multiColumns = zone.right > zone.left;
|
|
49307
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
49308
|
-
multiColumns = false;
|
|
49309
|
-
let table;
|
|
49310
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
49311
|
-
table = [];
|
|
49312
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
49313
|
-
const merge = env.model.getters.getMerge({
|
|
49314
|
-
sheetId,
|
|
49315
|
-
col,
|
|
49316
|
-
row
|
|
49317
|
-
});
|
|
49318
|
-
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
49319
|
-
}
|
|
49320
|
-
if (table.length >= 2) {
|
|
49321
|
-
multiColumns = true;
|
|
49322
|
-
break;
|
|
49323
|
-
}
|
|
49324
|
-
}
|
|
49325
|
-
}
|
|
49326
|
-
if (multiColumns) {
|
|
49327
|
-
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49328
|
-
return;
|
|
49329
|
-
}
|
|
49330
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
49331
|
-
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49332
|
-
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));
|
|
49333
|
-
}
|
|
49334
|
-
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
49335
|
-
const result = env.model.dispatch("SORT_CELLS", {
|
|
49336
|
-
sheetId,
|
|
49337
|
-
col: anchor.col,
|
|
49338
|
-
row: anchor.row,
|
|
49339
|
-
zone,
|
|
49340
|
-
sortDirection,
|
|
49341
|
-
sortOptions
|
|
49342
|
-
});
|
|
49343
|
-
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
49344
|
-
const { col, row } = anchor;
|
|
49345
|
-
env.model.selection.selectZone({
|
|
49346
|
-
cell: {
|
|
49347
|
-
col,
|
|
49348
|
-
row
|
|
49349
|
-
},
|
|
49350
|
-
zone
|
|
49351
|
-
});
|
|
49352
|
-
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
49353
|
-
}
|
|
49354
|
-
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
49355
|
-
const { col, row } = anchor;
|
|
49356
|
-
env.model.selection.selectZone({
|
|
49357
|
-
cell: {
|
|
49358
|
-
col,
|
|
49359
|
-
row
|
|
49360
|
-
},
|
|
49361
|
-
zone
|
|
49362
|
-
});
|
|
49363
|
-
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
49364
|
-
}
|
|
49365
|
-
}
|
|
49366
|
-
|
|
49367
|
-
//#endregion
|
|
49368
|
-
//#region src/stores/trim_whitespace_store.ts
|
|
49369
|
-
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
49370
|
-
mutators = ["trimWhitespace"];
|
|
49371
|
-
notificationStore = this.get(NotificationStore);
|
|
49372
|
-
trimWhitespace() {
|
|
49373
|
-
this.model.dispatch("TRIM_WHITESPACE");
|
|
49374
|
-
}
|
|
49375
|
-
handle(cmd) {
|
|
49376
|
-
switch (cmd.type) {
|
|
49377
|
-
case "TRIM_WHITESPACE":
|
|
49378
|
-
this._trimWhitespace();
|
|
49379
|
-
break;
|
|
49380
|
-
}
|
|
49381
|
-
}
|
|
49382
|
-
_trimWhitespace() {
|
|
49383
|
-
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
49384
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
49385
|
-
let count = 0;
|
|
49386
|
-
for (const { col, row } of zones.map(positions).flat()) {
|
|
49387
|
-
const cell = this.getters.getCell({
|
|
49388
|
-
col,
|
|
49389
|
-
row,
|
|
49390
|
-
sheetId
|
|
49391
|
-
});
|
|
49392
|
-
if (!cell) continue;
|
|
49393
|
-
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
49394
|
-
const trimmedContent = trimContent(currentContent);
|
|
49395
|
-
if (trimmedContent !== currentContent) {
|
|
49396
|
-
count += 1;
|
|
49397
|
-
this.model.dispatch("UPDATE_CELL", {
|
|
49398
|
-
sheetId,
|
|
49399
|
-
col,
|
|
49400
|
-
row,
|
|
49401
|
-
content: trimmedContent
|
|
49402
|
-
});
|
|
49403
|
-
}
|
|
49404
|
-
}
|
|
49405
|
-
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
49406
|
-
this.notificationStore.notifyUser({
|
|
49407
|
-
type: "info",
|
|
49408
|
-
text,
|
|
49409
|
-
sticky: false
|
|
49410
|
-
});
|
|
49411
|
-
}
|
|
49412
|
-
};
|
|
49413
|
-
|
|
49414
|
-
//#endregion
|
|
49415
|
-
//#region src/actions/data_actions.ts
|
|
49416
|
-
const sortRange = {
|
|
49417
|
-
name: _t("Sort range"),
|
|
49418
|
-
isVisible: IS_ONLY_ONE_RANGE,
|
|
49419
|
-
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
49420
|
-
};
|
|
49421
|
-
const sortAscending = {
|
|
49422
|
-
name: _t("Ascending (A ⟶ Z)"),
|
|
49423
|
-
execute: (env) => {
|
|
49424
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49425
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
49426
|
-
},
|
|
49427
|
-
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
49428
|
-
};
|
|
49429
|
-
const dataCleanup = {
|
|
49430
|
-
name: _t("Data cleanup"),
|
|
49431
|
-
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
49432
|
-
};
|
|
49433
|
-
const removeDuplicates = {
|
|
49434
|
-
name: _t("Remove duplicates"),
|
|
49435
|
-
execute: (env) => {
|
|
49436
|
-
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
49437
|
-
env.openSidePanel("RemoveDuplicates", {});
|
|
49438
|
-
},
|
|
49439
|
-
isEnabled: (env) => !env.isSmall
|
|
49440
|
-
};
|
|
49441
|
-
const trimWhitespace = {
|
|
49442
|
-
name: _t("Trim whitespace"),
|
|
49443
|
-
execute: (env) => {
|
|
49444
|
-
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
49445
|
-
}
|
|
49446
|
-
};
|
|
49447
|
-
const cleanupDataSources = {
|
|
49448
|
-
name: _t("Remove unused data sources"),
|
|
49449
|
-
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
49450
|
-
isEnabled: (env) => !env.isSmall
|
|
49451
|
-
};
|
|
49452
|
-
const sortDescending = {
|
|
49453
|
-
name: _t("Descending (Z ⟶ A)"),
|
|
49454
|
-
execute: (env) => {
|
|
49455
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49456
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
49457
|
-
},
|
|
49458
|
-
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
49459
|
-
};
|
|
49460
|
-
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
49461
|
-
const createRemoveFilterTool = {
|
|
49462
|
-
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
49463
|
-
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
49464
|
-
};
|
|
49465
|
-
const splitToColumns = {
|
|
49466
|
-
name: _t("Split text to columns"),
|
|
49467
|
-
sequence: 1,
|
|
49468
|
-
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
49469
|
-
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
49470
|
-
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
49471
|
-
};
|
|
49472
|
-
const columnStatistics = {
|
|
49473
|
-
name: _t("Column statistics"),
|
|
49474
|
-
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
49475
|
-
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
49476
|
-
};
|
|
49477
|
-
const reinsertDynamicPivotMenu = {
|
|
49478
|
-
id: "reinsert_dynamic_pivot",
|
|
49479
|
-
name: _t("Re-insert dynamic pivot"),
|
|
49480
|
-
sequence: 60,
|
|
49481
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49482
|
-
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
49483
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49484
|
-
};
|
|
49485
|
-
const reinsertStaticPivotMenu = {
|
|
49486
|
-
id: "reinsert_static_pivot",
|
|
49487
|
-
name: _t("Re-insert static pivot"),
|
|
49488
|
-
sequence: 70,
|
|
49489
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49490
|
-
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
49491
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49492
|
-
};
|
|
49493
|
-
const calculationMode = {
|
|
49494
|
-
name: _t("Calculation"),
|
|
49495
|
-
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
49496
|
-
};
|
|
49497
|
-
const automaticCalculation = {
|
|
49498
|
-
name: _t("Automatic"),
|
|
49499
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
49500
|
-
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
49501
|
-
};
|
|
49502
|
-
const manualCalculation = {
|
|
49503
|
-
name: _t("Manual"),
|
|
49504
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
49505
|
-
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
49506
|
-
};
|
|
49507
|
-
|
|
49508
51054
|
//#endregion
|
|
49509
51055
|
//#region src/registries/menus/col_menu_registry.ts
|
|
49510
51056
|
const colMenuRegistry = new MenuItemRegistry();
|
|
@@ -49538,6 +51084,9 @@ colMenuRegistry.add("cut", {
|
|
|
49538
51084
|
}).addChild("sort_descending", ["sort_columns"], {
|
|
49539
51085
|
...sortDescending,
|
|
49540
51086
|
sequence: 20
|
|
51087
|
+
}).add("data_analysis", {
|
|
51088
|
+
...dataAnalysis,
|
|
51089
|
+
sequence: 55
|
|
49541
51090
|
}).add("column_statistics", {
|
|
49542
51091
|
...columnStatistics,
|
|
49543
51092
|
sequence: 60,
|
|
@@ -52353,14 +53902,15 @@ var FiguresContainer = class extends Component {
|
|
|
52353
53902
|
selectedRect: void 0,
|
|
52354
53903
|
horizontalSnap: void 0,
|
|
52355
53904
|
verticalSnap: void 0,
|
|
52356
|
-
cancelDnd: void 0
|
|
52357
|
-
overlappingChartOrCarousel: void 0
|
|
53905
|
+
cancelDnd: void 0
|
|
52358
53906
|
});
|
|
52359
53907
|
viewStore;
|
|
52360
53908
|
zoomStore;
|
|
53909
|
+
chartDragStore;
|
|
52361
53910
|
setup() {
|
|
52362
53911
|
this.viewStore = useStore(ViewportsStore);
|
|
52363
53912
|
this.zoomStore = useStore(ZoomStore);
|
|
53913
|
+
this.chartDragStore = useStore(ChartDragStore);
|
|
52364
53914
|
(0, _odoo_owl.onMounted)(() => {
|
|
52365
53915
|
this.render();
|
|
52366
53916
|
});
|
|
@@ -52374,7 +53924,7 @@ var FiguresContainer = class extends Component {
|
|
|
52374
53924
|
this.dnd.selectedRect = void 0;
|
|
52375
53925
|
this.dnd.horizontalSnap = void 0;
|
|
52376
53926
|
this.dnd.verticalSnap = void 0;
|
|
52377
|
-
this.
|
|
53927
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52378
53928
|
this.dnd.cancelDnd = void 0;
|
|
52379
53929
|
}
|
|
52380
53930
|
});
|
|
@@ -52500,6 +54050,7 @@ var FiguresContainer = class extends Component {
|
|
|
52500
54050
|
maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
|
|
52501
54051
|
};
|
|
52502
54052
|
let hasStartedDnd = false;
|
|
54053
|
+
let overlappingChartOrCarousel = void 0;
|
|
52503
54054
|
const onMouseMove = (ev) => {
|
|
52504
54055
|
const currentMousePosition = {
|
|
52505
54056
|
x: ev.clientX / zoom,
|
|
@@ -52511,10 +54062,10 @@ var FiguresContainer = class extends Component {
|
|
|
52511
54062
|
hasStartedDnd = true;
|
|
52512
54063
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
|
|
52513
54064
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
52514
|
-
|
|
54065
|
+
overlappingChartOrCarousel = void 0;
|
|
52515
54066
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
52516
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel =
|
|
52517
|
-
this.
|
|
54067
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
|
|
54068
|
+
this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
|
|
52518
54069
|
if (!overlappingChartOrCarousel) {
|
|
52519
54070
|
const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
|
|
52520
54071
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
@@ -52536,7 +54087,7 @@ var FiguresContainer = class extends Component {
|
|
|
52536
54087
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
52537
54088
|
return;
|
|
52538
54089
|
}
|
|
52539
|
-
if (!
|
|
54090
|
+
if (!overlappingChartOrCarousel) {
|
|
52540
54091
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
52541
54092
|
return {
|
|
52542
54093
|
sheetId,
|
|
@@ -52546,14 +54097,14 @@ var FiguresContainer = class extends Component {
|
|
|
52546
54097
|
}) || [];
|
|
52547
54098
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
52548
54099
|
} else {
|
|
52549
|
-
const overlappingFigureId =
|
|
54100
|
+
const overlappingFigureId = overlappingChartOrCarousel.id;
|
|
52550
54101
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
52551
|
-
if (
|
|
54102
|
+
if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
52552
54103
|
sheetId,
|
|
52553
54104
|
carouselFigureId: overlappingFigureId,
|
|
52554
54105
|
chartFigureIds
|
|
52555
54106
|
});
|
|
52556
|
-
else if (
|
|
54107
|
+
else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
52557
54108
|
sheetId,
|
|
52558
54109
|
baseFigureId: overlappingFigureId,
|
|
52559
54110
|
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
@@ -52564,7 +54115,7 @@ var FiguresContainer = class extends Component {
|
|
|
52564
54115
|
this.dnd.selectedRect = void 0;
|
|
52565
54116
|
this.dnd.horizontalSnap = void 0;
|
|
52566
54117
|
this.dnd.verticalSnap = void 0;
|
|
52567
|
-
this.
|
|
54118
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52568
54119
|
};
|
|
52569
54120
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52570
54121
|
}
|
|
@@ -52620,7 +54171,6 @@ var FiguresContainer = class extends Component {
|
|
|
52620
54171
|
this.dnd.selectedRect = void 0;
|
|
52621
54172
|
this.dnd.horizontalSnap = void 0;
|
|
52622
54173
|
this.dnd.verticalSnap = void 0;
|
|
52623
|
-
this.dnd.overlappingChartOrCarousel = void 0;
|
|
52624
54174
|
};
|
|
52625
54175
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52626
54176
|
}
|
|
@@ -52630,12 +54180,12 @@ var FiguresContainer = class extends Component {
|
|
|
52630
54180
|
getFigureStyle(figureUI) {
|
|
52631
54181
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
52632
54182
|
return cssPropertiesToCss({
|
|
52633
|
-
opacity: this.
|
|
54183
|
+
opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
|
|
52634
54184
|
cursor: "grabbing"
|
|
52635
54185
|
});
|
|
52636
54186
|
}
|
|
52637
54187
|
getFigureClass(figureUI) {
|
|
52638
|
-
if (figureUI.id !== this.
|
|
54188
|
+
if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
|
|
52639
54189
|
return "o-add-to-carousel";
|
|
52640
54190
|
}
|
|
52641
54191
|
getSnap(snapLine) {
|
|
@@ -52678,26 +54228,6 @@ var FiguresContainer = class extends Component {
|
|
|
52678
54228
|
height: `100%`
|
|
52679
54229
|
});
|
|
52680
54230
|
}
|
|
52681
|
-
getOverlappingFigure(figureUI, otherFigures) {
|
|
52682
|
-
if (figureUI.tag !== "chart") return;
|
|
52683
|
-
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
52684
|
-
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
52685
|
-
let bestMatch;
|
|
52686
|
-
let smallestDistance = Infinity;
|
|
52687
|
-
for (const figure of otherFigures) {
|
|
52688
|
-
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
52689
|
-
const targetCenterX = figure.x + figure.width / 2;
|
|
52690
|
-
const targetCenterY = figure.y + figure.height / 2;
|
|
52691
|
-
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
52692
|
-
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
52693
|
-
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
52694
|
-
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
52695
|
-
smallestDistance = squaredDistance;
|
|
52696
|
-
bestMatch = figure;
|
|
52697
|
-
}
|
|
52698
|
-
}
|
|
52699
|
-
return bestMatch;
|
|
52700
|
-
}
|
|
52701
54231
|
};
|
|
52702
54232
|
|
|
52703
54233
|
//#endregion
|
|
@@ -52997,7 +54527,7 @@ var AbstractResizer = class extends Component {
|
|
|
52997
54527
|
var ColResizer = class extends AbstractResizer {
|
|
52998
54528
|
static template = "o-spreadsheet-ColResizer";
|
|
52999
54529
|
static components = { UnhideColumnHeaders };
|
|
53000
|
-
colResizerRef =
|
|
54530
|
+
colResizerRef = _odoo_owl.signal.ref();
|
|
53001
54531
|
setup() {
|
|
53002
54532
|
super.setup();
|
|
53003
54533
|
this.PADDING = 15;
|
|
@@ -53131,7 +54661,7 @@ var ColResizer = class extends AbstractResizer {
|
|
|
53131
54661
|
var RowResizer = class extends AbstractResizer {
|
|
53132
54662
|
static template = "o-spreadsheet-RowResizer";
|
|
53133
54663
|
static components = { UnhideRowHeaders };
|
|
53134
|
-
rowResizerRef =
|
|
54664
|
+
rowResizerRef = _odoo_owl.signal.ref();
|
|
53135
54665
|
setup() {
|
|
53136
54666
|
super.setup();
|
|
53137
54667
|
this.PADDING = 5;
|
|
@@ -53740,9 +55270,8 @@ var TableResizer = class extends Component {
|
|
|
53740
55270
|
useStore(TableResizeStore);
|
|
53741
55271
|
}
|
|
53742
55272
|
get containerStyle() {
|
|
53743
|
-
const tableZone = this.props.table.range.zone;
|
|
53744
55273
|
const sheetId = this.props.table.range.sheetId;
|
|
53745
|
-
|
|
55274
|
+
const tableZone = this.props.table.range.zone;
|
|
53746
55275
|
const bottomRight = {
|
|
53747
55276
|
...tableZone,
|
|
53748
55277
|
left: tableZone.right,
|
|
@@ -53829,8 +55358,8 @@ var Grid = class extends Component {
|
|
|
53829
55358
|
HEADER_HEIGHT = 26;
|
|
53830
55359
|
HEADER_WIDTH = 48;
|
|
53831
55360
|
menuState;
|
|
53832
|
-
gridRef =
|
|
53833
|
-
canvasRef =
|
|
55361
|
+
gridRef = _odoo_owl.signal.ref();
|
|
55362
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
53834
55363
|
highlightStore;
|
|
53835
55364
|
viewStore;
|
|
53836
55365
|
zoomStore;
|
|
@@ -54461,6 +55990,9 @@ var Grid = class extends Component {
|
|
|
54461
55990
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
54462
55991
|
return this.env.model.getters.getClientsToDisplay(sheetId);
|
|
54463
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
|
+
}
|
|
54464
55996
|
};
|
|
54465
55997
|
|
|
54466
55998
|
//#endregion
|
|
@@ -54479,7 +56011,7 @@ var FullScreenFigure = class extends Component {
|
|
|
54479
56011
|
static template = "o-spreadsheet-FullScreenFigure";
|
|
54480
56012
|
static components = { ChartFigure };
|
|
54481
56013
|
fullScreenFigureStore;
|
|
54482
|
-
fullScreenFigureRef =
|
|
56014
|
+
fullScreenFigureRef = _odoo_owl.signal.ref();
|
|
54483
56015
|
spreadsheetRect = useSpreadsheetRect();
|
|
54484
56016
|
figureRegistry = figureRegistry;
|
|
54485
56017
|
setup() {
|
|
@@ -54727,6 +56259,41 @@ var PivotHTMLRenderer = class extends Component {
|
|
|
54727
56259
|
}
|
|
54728
56260
|
};
|
|
54729
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
|
+
|
|
54730
56297
|
//#endregion
|
|
54731
56298
|
//#region src/helpers/figures/charts/runtime/helpers_index.ts
|
|
54732
56299
|
var helpers_index_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -68013,6 +69580,12 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
68013
69580
|
const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
|
|
68014
69581
|
const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
68015
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;
|
|
68016
69589
|
}
|
|
68017
69590
|
return "Success";
|
|
68018
69591
|
}
|
|
@@ -68048,6 +69621,29 @@ var FigureUIPlugin = class extends UIPlugin {
|
|
|
68048
69621
|
carouselFigureId,
|
|
68049
69622
|
chartFigureIds: cmd.chartFigureIds
|
|
68050
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;
|
|
68051
69647
|
}
|
|
68052
69648
|
}
|
|
68053
69649
|
getFigureUI(sheetId, figure) {
|
|
@@ -69737,6 +71333,9 @@ topbarMenuRegistry.add("file", {
|
|
|
69737
71333
|
}).addChild("split_to_columns", ["data"], {
|
|
69738
71334
|
...splitToColumns,
|
|
69739
71335
|
sequence: 20
|
|
71336
|
+
}).addChild("data_analysis", ["data"], {
|
|
71337
|
+
...dataAnalysis,
|
|
71338
|
+
sequence: 24
|
|
69740
71339
|
}).addChild("column_statistics", ["data"], {
|
|
69741
71340
|
...columnStatistics,
|
|
69742
71341
|
sequence: 25
|
|
@@ -70138,7 +71737,7 @@ var RippleEffect = class extends Component {
|
|
|
70138
71737
|
onAnimationEnd: types.function(),
|
|
70139
71738
|
style: types.string()
|
|
70140
71739
|
});
|
|
70141
|
-
rippleRef =
|
|
71740
|
+
rippleRef = _odoo_owl.signal.ref();
|
|
70142
71741
|
setup() {
|
|
70143
71742
|
let animation = void 0;
|
|
70144
71743
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -70192,7 +71791,7 @@ var Ripple = class extends Component {
|
|
|
70192
71791
|
onAnimationEnd: types.function().optional(() => () => {}),
|
|
70193
71792
|
class: types.string().optional("")
|
|
70194
71793
|
});
|
|
70195
|
-
childContainerRef =
|
|
71794
|
+
childContainerRef = _odoo_owl.signal.ref();
|
|
70196
71795
|
state = (0, _odoo_owl.proxy)({ ripples: [] });
|
|
70197
71796
|
currentId = 1;
|
|
70198
71797
|
onClick(ev) {
|
|
@@ -70463,9 +72062,9 @@ var BottomBarSheet = class extends Component {
|
|
|
70463
72062
|
isEditing: false,
|
|
70464
72063
|
openedPicker: void 0
|
|
70465
72064
|
});
|
|
70466
|
-
sheetDivRef =
|
|
70467
|
-
iconRef =
|
|
70468
|
-
sheetNameRef =
|
|
72065
|
+
sheetDivRef = _odoo_owl.signal.ref();
|
|
72066
|
+
iconRef = _odoo_owl.signal.ref();
|
|
72067
|
+
sheetNameRef = _odoo_owl.signal.ref();
|
|
70469
72068
|
editionState = "initializing";
|
|
70470
72069
|
DOMFocusableElementStore;
|
|
70471
72070
|
setup() {
|
|
@@ -70749,8 +72348,10 @@ var BottomBarStatistic = class extends Component {
|
|
|
70749
72348
|
});
|
|
70750
72349
|
state = (0, _odoo_owl.proxy)({ selectedStatisticFn: "" });
|
|
70751
72350
|
store;
|
|
72351
|
+
sidePanelStore;
|
|
70752
72352
|
setup() {
|
|
70753
72353
|
this.store = useStore(AggregateStatisticsStore);
|
|
72354
|
+
this.sidePanelStore = useStore(SidePanelStore);
|
|
70754
72355
|
(0, _odoo_owl.onWillUpdateProps)(() => {
|
|
70755
72356
|
if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
|
|
70756
72357
|
});
|
|
@@ -70790,6 +72391,12 @@ var BottomBarStatistic = class extends Component {
|
|
|
70790
72391
|
format: fnValue.format()
|
|
70791
72392
|
});
|
|
70792
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
|
+
}
|
|
70793
72400
|
};
|
|
70794
72401
|
|
|
70795
72402
|
//#endregion
|
|
@@ -70804,8 +72411,8 @@ var BottomBar = class extends Component {
|
|
|
70804
72411
|
BottomBarSheet,
|
|
70805
72412
|
BottomBarStatistic
|
|
70806
72413
|
};
|
|
70807
|
-
bottomBarRef =
|
|
70808
|
-
sheetListRef =
|
|
72414
|
+
bottomBarRef = _odoo_owl.signal.ref();
|
|
72415
|
+
sheetListRef = _odoo_owl.signal.ref();
|
|
70809
72416
|
dragAndDrop = useDragAndDropListItems();
|
|
70810
72417
|
targetScroll = void 0;
|
|
70811
72418
|
state = (0, _odoo_owl.proxy)({
|
|
@@ -71018,12 +72625,13 @@ var SpreadsheetDashboard = class extends Component {
|
|
|
71018
72625
|
hoveredCell;
|
|
71019
72626
|
viewStore;
|
|
71020
72627
|
zoomStore;
|
|
71021
|
-
gridRef =
|
|
71022
|
-
canvasRef =
|
|
72628
|
+
gridRef = _odoo_owl.signal.ref();
|
|
72629
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
71023
72630
|
setup() {
|
|
71024
72631
|
this.hoveredCell = useStore(DelayedHoveredCellStore);
|
|
71025
72632
|
this.viewStore = useStore(ViewportsStore);
|
|
71026
72633
|
this.zoomStore = useStore(ZoomStore);
|
|
72634
|
+
useLocalStore(HoveredTableStore);
|
|
71027
72635
|
const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
|
|
71028
72636
|
useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
|
|
71029
72637
|
useGridDrawing({
|
|
@@ -71441,8 +73049,8 @@ var RibbonMenu = class extends Component {
|
|
|
71441
73049
|
static components = { Menu };
|
|
71442
73050
|
props = (0, _odoo_owl.useProps)({ onClose: types.function() });
|
|
71443
73051
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
71444
|
-
menuRef =
|
|
71445
|
-
containerRef =
|
|
73052
|
+
menuRef = _odoo_owl.signal.ref();
|
|
73053
|
+
containerRef = _odoo_owl.signal.ref();
|
|
71446
73054
|
state = (0, _odoo_owl.proxy)({
|
|
71447
73055
|
menuItems: this.rootItems,
|
|
71448
73056
|
title: _t("Menu Bar"),
|
|
@@ -71515,7 +73123,7 @@ var SmallBottomBar = class extends Component {
|
|
|
71515
73123
|
composerStore;
|
|
71516
73124
|
viewStore;
|
|
71517
73125
|
composerInterface;
|
|
71518
|
-
composerRef =
|
|
73126
|
+
composerRef = _odoo_owl.signal.ref();
|
|
71519
73127
|
menuState = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
71520
73128
|
setup() {
|
|
71521
73129
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -71594,6 +73202,49 @@ var SmallBottomBar = class extends Component {
|
|
|
71594
73202
|
}
|
|
71595
73203
|
};
|
|
71596
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
|
+
|
|
71597
73248
|
//#endregion
|
|
71598
73249
|
//#region src/components/standalone_grid_canvas/figure_renderer_store.ts
|
|
71599
73250
|
/** Store that draws the figures directly onto the canvas */
|
|
@@ -71619,29 +73270,25 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71619
73270
|
drawLayer(renderingCtx) {
|
|
71620
73271
|
const { viewports, ctx } = renderingCtx;
|
|
71621
73272
|
const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
|
|
71622
|
-
const
|
|
73273
|
+
const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
|
|
71623
73274
|
for (const figure of visibleFigures) {
|
|
71624
|
-
const
|
|
71625
|
-
|
|
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;
|
|
71626
73281
|
if (figure.tag === "chart") {
|
|
71627
73282
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71628
|
-
if (chartId)
|
|
71629
|
-
const chartRect = {
|
|
71630
|
-
x,
|
|
71631
|
-
y,
|
|
71632
|
-
width: figure.width,
|
|
71633
|
-
height: figure.height
|
|
71634
|
-
};
|
|
71635
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71636
|
-
}
|
|
73283
|
+
if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
|
|
71637
73284
|
} else if (figure.tag === "image") {
|
|
71638
73285
|
const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
|
|
71639
|
-
if (loadedImage) ctx.drawImage(loadedImage, x, y,
|
|
71640
|
-
} 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);
|
|
71641
73288
|
if (!this.getters.isDashboard()) {
|
|
71642
73289
|
ctx.strokeStyle = GRAY_400;
|
|
71643
73290
|
ctx.lineWidth = 1;
|
|
71644
|
-
ctx.strokeRect(x, y,
|
|
73291
|
+
ctx.strokeRect(x, y, width, height);
|
|
71645
73292
|
}
|
|
71646
73293
|
}
|
|
71647
73294
|
}
|
|
@@ -71659,53 +73306,37 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71659
73306
|
renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
|
|
71660
73307
|
cleanUp();
|
|
71661
73308
|
}
|
|
71662
|
-
drawCarousel(renderingCtx, figure) {
|
|
71663
|
-
const {
|
|
71664
|
-
const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
|
|
71665
|
-
const x = figure.x - offsetX;
|
|
71666
|
-
const y = figure.y - offsetY;
|
|
73309
|
+
drawCarousel(renderingCtx, figure, figureRect) {
|
|
73310
|
+
const { ctx } = renderingCtx;
|
|
71667
73311
|
const carousel = this.getters.getCarousel(figure.id);
|
|
71668
73312
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71669
73313
|
if (!carousel) return;
|
|
73314
|
+
const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
|
|
71670
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);
|
|
71671
73322
|
const title = {
|
|
71672
73323
|
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
71673
73324
|
...carousel.title
|
|
71674
73325
|
};
|
|
71675
|
-
|
|
71676
|
-
|
|
71677
|
-
if (!title.text && chartId) {
|
|
71678
|
-
const chartRect = {
|
|
71679
|
-
x,
|
|
71680
|
-
y,
|
|
71681
|
-
width: figure.width,
|
|
71682
|
-
height: figure.height
|
|
71683
|
-
};
|
|
71684
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71685
|
-
} else if (title.text) {
|
|
71686
|
-
ctx.save();
|
|
71687
|
-
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
71688
|
-
ctx.fillRect(x, y, figure.width, headerSize);
|
|
71689
|
-
ctx.font = computeTextFont(chartStyleToCellStyle(title));
|
|
73326
|
+
if (title.text) {
|
|
73327
|
+
ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
|
|
71690
73328
|
ctx.fillStyle = title.color;
|
|
71691
|
-
ctx.
|
|
71692
|
-
|
|
71693
|
-
|
|
71694
|
-
|
|
71695
|
-
|
|
71696
|
-
|
|
71697
|
-
|
|
71698
|
-
|
|
71699
|
-
if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
|
|
71700
|
-
else if (!this.getters.isDashboard()) {
|
|
71701
|
-
ctx.strokeStyle = GRAY_400;
|
|
71702
|
-
ctx.lineWidth = 1;
|
|
71703
|
-
ctx.beginPath();
|
|
71704
|
-
ctx.moveTo(x, y + headerSize);
|
|
71705
|
-
ctx.lineTo(x + figure.width, y + headerSize);
|
|
71706
|
-
ctx.stroke();
|
|
71707
|
-
}
|
|
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);
|
|
71708
73337
|
}
|
|
73338
|
+
ctx.restore();
|
|
73339
|
+
if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
|
|
71709
73340
|
}
|
|
71710
73341
|
};
|
|
71711
73342
|
|
|
@@ -71713,12 +73344,13 @@ var FigureRendererStore = class extends DisposableStore {
|
|
|
71713
73344
|
//#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
|
|
71714
73345
|
var StandaloneGridCanvas = class extends Component {
|
|
71715
73346
|
static template = "o-spreadsheet-StandaloneGridCanvas";
|
|
73347
|
+
static components = { CarouselDataViewPrint };
|
|
71716
73348
|
props = (0, _odoo_owl.useProps)({
|
|
71717
73349
|
sheetId: types.UID(),
|
|
71718
73350
|
zone: types.Zone(),
|
|
71719
73351
|
renderingCtx: types.object()
|
|
71720
73352
|
});
|
|
71721
|
-
canvasRef =
|
|
73353
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
71722
73354
|
rendererStore;
|
|
71723
73355
|
figureRendererStore;
|
|
71724
73356
|
setup() {
|
|
@@ -71740,6 +73372,28 @@ var StandaloneGridCanvas = class extends Component {
|
|
|
71740
73372
|
this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
|
|
71741
73373
|
}));
|
|
71742
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
|
+
}
|
|
71743
73397
|
};
|
|
71744
73398
|
|
|
71745
73399
|
//#endregion
|
|
@@ -72201,7 +73855,7 @@ var NamedRangeSelector = class extends Component {
|
|
|
72201
73855
|
anchorRect: null,
|
|
72202
73856
|
menuItems: []
|
|
72203
73857
|
});
|
|
72204
|
-
namedRangeSelectorRef =
|
|
73858
|
+
namedRangeSelectorRef = _odoo_owl.signal.ref();
|
|
72205
73859
|
setup() {
|
|
72206
73860
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72207
73861
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -72399,7 +74053,7 @@ var BorderEditor = class extends Component {
|
|
|
72399
74053
|
anchorRect: types.Rect()
|
|
72400
74054
|
});
|
|
72401
74055
|
BORDER_POSITIONS = BORDER_POSITIONS;
|
|
72402
|
-
lineStyleButtonRef =
|
|
74056
|
+
lineStyleButtonRef = _odoo_owl.signal.ref();
|
|
72403
74057
|
borderStyles = borderStyles;
|
|
72404
74058
|
state = (0, _odoo_owl.proxy)({ activeTool: void 0 });
|
|
72405
74059
|
toggleDropdownTool(tool) {
|
|
@@ -72466,7 +74120,7 @@ var BorderEditorWidget = class extends Component {
|
|
|
72466
74120
|
});
|
|
72467
74121
|
topBarToolStore;
|
|
72468
74122
|
viewStore;
|
|
72469
|
-
borderEditorButtonRef =
|
|
74123
|
+
borderEditorButtonRef = _odoo_owl.signal.ref();
|
|
72470
74124
|
state = (0, _odoo_owl.proxy)({
|
|
72471
74125
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
72472
74126
|
currentStyle: DEFAULT_BORDER_DESC.style,
|
|
@@ -72696,7 +74350,7 @@ var DropdownAction = class extends Component {
|
|
|
72696
74350
|
childClass: types.string()
|
|
72697
74351
|
});
|
|
72698
74352
|
topBarToolStore;
|
|
72699
|
-
actionRef =
|
|
74353
|
+
actionRef = _odoo_owl.signal.ref();
|
|
72700
74354
|
setup() {
|
|
72701
74355
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72702
74356
|
}
|
|
@@ -72759,7 +74413,7 @@ var NumberFormatsTool = class extends Component {
|
|
|
72759
74413
|
props = (0, _odoo_owl.useProps)({ class: types.string() });
|
|
72760
74414
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
72761
74415
|
topBarToolStore;
|
|
72762
|
-
buttonRef =
|
|
74416
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
72763
74417
|
state = (0, _odoo_owl.proxy)({
|
|
72764
74418
|
anchorRect: {
|
|
72765
74419
|
x: 0,
|
|
@@ -73048,12 +74702,12 @@ var TopBar = class extends Component {
|
|
|
73048
74702
|
composerFocusStore;
|
|
73049
74703
|
fingerprints;
|
|
73050
74704
|
topBarToolStore;
|
|
73051
|
-
toolBarContainerRef =
|
|
73052
|
-
toolbarRef =
|
|
73053
|
-
namedRangesRef =
|
|
73054
|
-
topBarTopRef =
|
|
73055
|
-
moreToolsContainerRef =
|
|
73056
|
-
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();
|
|
73057
74711
|
spreadsheetRect = useSpreadsheetRect();
|
|
73058
74712
|
setup() {
|
|
73059
74713
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -73315,7 +74969,7 @@ var Spreadsheet = class extends Component {
|
|
|
73315
74969
|
SpreadsheetPrint
|
|
73316
74970
|
};
|
|
73317
74971
|
sidePanel;
|
|
73318
|
-
spreadsheetRef =
|
|
74972
|
+
spreadsheetRef = _odoo_owl.signal.ref();
|
|
73319
74973
|
spreadsheetRect = useSpreadsheetRect();
|
|
73320
74974
|
state = (0, _odoo_owl.proxy)({ printModeEnabled: false });
|
|
73321
74975
|
_focusGrid;
|
|
@@ -79323,12 +80977,13 @@ var FilterMenu = class extends Component {
|
|
|
79323
80977
|
}
|
|
79324
80978
|
sortFilterZone(sortDirection) {
|
|
79325
80979
|
const filterPosition = this.props.filterPosition;
|
|
79326
|
-
const
|
|
80980
|
+
const table = this.table;
|
|
80981
|
+
const tableZone = table?.range.zone;
|
|
79327
80982
|
if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
|
|
79328
80983
|
const sheetId = this.viewStore.displayedSheetId;
|
|
79329
80984
|
const contentZone = {
|
|
79330
80985
|
...tableZone,
|
|
79331
|
-
top: tableZone.top +
|
|
80986
|
+
top: tableZone.top + table.config.numberOfHeaders
|
|
79332
80987
|
};
|
|
79333
80988
|
const sortAnchor = {
|
|
79334
80989
|
col: filterPosition.col,
|
|
@@ -79486,9 +81141,9 @@ var LinkEditor = class extends Component {
|
|
|
79486
81141
|
onClosed: types.function().optional()
|
|
79487
81142
|
});
|
|
79488
81143
|
static size = { maxHeight: 500 };
|
|
79489
|
-
urlInput =
|
|
79490
|
-
suggestionListRef =
|
|
79491
|
-
urlInputContainer =
|
|
81144
|
+
urlInput = _odoo_owl.signal.ref(HTMLInputElement);
|
|
81145
|
+
suggestionListRef = _odoo_owl.signal.ref();
|
|
81146
|
+
urlInputContainer = _odoo_owl.signal.ref();
|
|
79492
81147
|
state;
|
|
79493
81148
|
viewStore;
|
|
79494
81149
|
setup() {
|
|
@@ -81606,7 +83261,7 @@ const ARRAYTOTEXT = {
|
|
|
81606
83261
|
else if (_format === 0) {
|
|
81607
83262
|
const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
|
|
81608
83263
|
return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
|
|
81609
|
-
return isEvaluationError(value.value) ? value.value :
|
|
83264
|
+
return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
|
|
81610
83265
|
})).join(rowSeparator) };
|
|
81611
83266
|
} else return new EvaluationError(_t("Format must be 0 or 1"));
|
|
81612
83267
|
},
|
|
@@ -87654,7 +89309,7 @@ const CONCAT = {
|
|
|
87654
89309
|
description: _t("Concatenation of two values."),
|
|
87655
89310
|
args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
|
|
87656
89311
|
compute: function(value1, value2) {
|
|
87657
|
-
return { value:
|
|
89312
|
+
return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
|
|
87658
89313
|
},
|
|
87659
89314
|
isExported: true
|
|
87660
89315
|
};
|
|
@@ -88537,7 +90192,7 @@ const CLEAN = {
|
|
|
88537
90192
|
description: _t("Remove non-printable characters from a piece of text."),
|
|
88538
90193
|
args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
|
|
88539
90194
|
compute: function(text) {
|
|
88540
|
-
const _text =
|
|
90195
|
+
const _text = toLocaleString(text, this.locale);
|
|
88541
90196
|
let cleanedStr = "";
|
|
88542
90197
|
for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
|
|
88543
90198
|
return { value: cleanedStr };
|
|
@@ -88548,7 +90203,7 @@ const CONCATENATE = {
|
|
|
88548
90203
|
description: _t("Appends strings to one another."),
|
|
88549
90204
|
args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
|
|
88550
90205
|
compute: function(...datas) {
|
|
88551
|
-
return { value: reduceAny(datas, (acc, a) => acc +
|
|
90206
|
+
return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
|
|
88552
90207
|
},
|
|
88553
90208
|
isExported: true
|
|
88554
90209
|
};
|
|
@@ -88556,7 +90211,7 @@ const EXACT = {
|
|
|
88556
90211
|
description: _t("Tests whether two strings are identical."),
|
|
88557
90212
|
args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
|
|
88558
90213
|
compute: function(string1, string2) {
|
|
88559
|
-
return { value:
|
|
90214
|
+
return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
|
|
88560
90215
|
},
|
|
88561
90216
|
isExported: true
|
|
88562
90217
|
};
|
|
@@ -88568,8 +90223,8 @@ const FIND = {
|
|
|
88568
90223
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88569
90224
|
],
|
|
88570
90225
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88571
|
-
const _searchFor =
|
|
88572
|
-
const _textToSearch =
|
|
90226
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
90227
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
88573
90228
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88574
90229
|
if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
88575
90230
|
if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
@@ -88606,8 +90261,8 @@ const JOIN = {
|
|
|
88606
90261
|
description: _t("Concatenates elements of arrays with delimiter."),
|
|
88607
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."))],
|
|
88608
90263
|
compute: function(delimiter, ...valuesOrArrays) {
|
|
88609
|
-
const _delimiter =
|
|
88610
|
-
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), "") };
|
|
88611
90266
|
}
|
|
88612
90267
|
};
|
|
88613
90268
|
const LEFT = {
|
|
@@ -88616,7 +90271,7 @@ const LEFT = {
|
|
|
88616
90271
|
compute: function(text, ...args) {
|
|
88617
90272
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88618
90273
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
88619
|
-
return { value:
|
|
90274
|
+
return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
|
|
88620
90275
|
},
|
|
88621
90276
|
isExported: true
|
|
88622
90277
|
};
|
|
@@ -88624,7 +90279,7 @@ const LEN = {
|
|
|
88624
90279
|
description: _t("Length of a string."),
|
|
88625
90280
|
args: [arg("text (string)", _t("The string whose length will be returned."))],
|
|
88626
90281
|
compute: function(text) {
|
|
88627
|
-
return { value:
|
|
90282
|
+
return { value: toLocaleString(text, this.locale).length };
|
|
88628
90283
|
},
|
|
88629
90284
|
isExported: true
|
|
88630
90285
|
};
|
|
@@ -88632,7 +90287,7 @@ const LOWER = {
|
|
|
88632
90287
|
description: _t("Converts a specified string to lowercase."),
|
|
88633
90288
|
args: [arg("text (string)", _t("The string to convert to lowercase."))],
|
|
88634
90289
|
compute: function(text) {
|
|
88635
|
-
return { value:
|
|
90290
|
+
return { value: toLocaleString(text, this.locale).toLowerCase() };
|
|
88636
90291
|
},
|
|
88637
90292
|
isExported: true
|
|
88638
90293
|
};
|
|
@@ -88644,7 +90299,7 @@ const MID = {
|
|
|
88644
90299
|
arg("extract_length (number)", _t("The length of the segment to extract."))
|
|
88645
90300
|
],
|
|
88646
90301
|
compute: function(text, starting_at, extract_length) {
|
|
88647
|
-
const _text =
|
|
90302
|
+
const _text = toLocaleString(text, this.locale);
|
|
88648
90303
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
88649
90304
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
88650
90305
|
if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
@@ -88657,7 +90312,7 @@ const PROPER = {
|
|
|
88657
90312
|
description: _t("Capitalizes each word in a specified string."),
|
|
88658
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."))],
|
|
88659
90314
|
compute: function(text) {
|
|
88660
|
-
return { value:
|
|
90315
|
+
return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
|
|
88661
90316
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
88662
90317
|
}) };
|
|
88663
90318
|
},
|
|
@@ -88807,8 +90462,8 @@ const REPLACE = {
|
|
|
88807
90462
|
if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
88808
90463
|
const _length = toNumber(length, this.locale);
|
|
88809
90464
|
if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
|
|
88810
|
-
const _text =
|
|
88811
|
-
const _newText =
|
|
90465
|
+
const _text = toLocaleString(text, this.locale);
|
|
90466
|
+
const _newText = toLocaleString(newText, this.locale);
|
|
88812
90467
|
return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
|
|
88813
90468
|
},
|
|
88814
90469
|
isExported: true
|
|
@@ -88819,7 +90474,7 @@ const RIGHT = {
|
|
|
88819
90474
|
compute: function(text, ...args) {
|
|
88820
90475
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88821
90476
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
|
|
88822
|
-
const _text =
|
|
90477
|
+
const _text = toLocaleString(text, this.locale);
|
|
88823
90478
|
const stringLength = _text.length;
|
|
88824
90479
|
return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
|
|
88825
90480
|
},
|
|
@@ -88833,8 +90488,8 @@ const SEARCH = {
|
|
|
88833
90488
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88834
90489
|
],
|
|
88835
90490
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88836
|
-
const _searchFor =
|
|
88837
|
-
const _textToSearch =
|
|
90491
|
+
const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
|
|
90492
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
|
|
88838
90493
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88839
90494
|
if (_textToSearch === "") return {
|
|
88840
90495
|
value: CellErrorType.GenericError,
|
|
@@ -88864,8 +90519,8 @@ const SPLIT = {
|
|
|
88864
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."))
|
|
88865
90520
|
],
|
|
88866
90521
|
computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
|
|
88867
|
-
const _text =
|
|
88868
|
-
const _delimiter = escapeRegExp(
|
|
90522
|
+
const _text = toLocaleString(text, this.locale);
|
|
90523
|
+
const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
|
|
88869
90524
|
const _splitByEach = toBoolean(splitByEach);
|
|
88870
90525
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
88871
90526
|
if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
@@ -88887,10 +90542,10 @@ const SUBSTITUTE = {
|
|
|
88887
90542
|
compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
88888
90543
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
88889
90544
|
if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
88890
|
-
const _textToSearch =
|
|
88891
|
-
const _searchFor =
|
|
90545
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
90546
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
88892
90547
|
if (_searchFor === "") return { value: _textToSearch };
|
|
88893
|
-
const _replaceWith =
|
|
90548
|
+
const _replaceWith = toLocaleString(replaceWith, this.locale);
|
|
88894
90549
|
const reg = new RegExp(escapeRegExp(_searchFor), "g");
|
|
88895
90550
|
if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
|
|
88896
90551
|
let n = 0;
|
|
@@ -88913,10 +90568,10 @@ const TEXTJOIN = {
|
|
|
88913
90568
|
arg("texts (string, range<string>, repeating)", _t("Text item to join."))
|
|
88914
90569
|
],
|
|
88915
90570
|
compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
|
|
88916
|
-
const _delimiter =
|
|
90571
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
88917
90572
|
const _ignoreEmpty = toBoolean(ignoreEmpty);
|
|
88918
90573
|
let n = 0;
|
|
88919
|
-
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, "") };
|
|
88920
90575
|
},
|
|
88921
90576
|
isExported: true
|
|
88922
90577
|
};
|
|
@@ -88969,7 +90624,7 @@ const TRIM = {
|
|
|
88969
90624
|
description: _t("Removes space characters."),
|
|
88970
90625
|
args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
|
|
88971
90626
|
compute: function(text) {
|
|
88972
|
-
return { value: trimContent(
|
|
90627
|
+
return { value: trimContent(toLocaleString(text, this.locale)) };
|
|
88973
90628
|
},
|
|
88974
90629
|
isExported: true
|
|
88975
90630
|
};
|
|
@@ -88977,7 +90632,7 @@ const UPPER = {
|
|
|
88977
90632
|
description: _t("Converts a specified string to uppercase."),
|
|
88978
90633
|
args: [arg("text (string)", _t("The string to convert to uppercase."))],
|
|
88979
90634
|
compute: function(text) {
|
|
88980
|
-
return { value:
|
|
90635
|
+
return { value: toLocaleString(text, this.locale).toUpperCase() };
|
|
88981
90636
|
},
|
|
88982
90637
|
isExported: true
|
|
88983
90638
|
};
|
|
@@ -89074,7 +90729,7 @@ const HYPERLINK = {
|
|
|
89074
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."))],
|
|
89075
90730
|
compute: function(url, linkLabel) {
|
|
89076
90731
|
const processedUrl = toString(url).trim();
|
|
89077
|
-
const processedLabel =
|
|
90732
|
+
const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
|
|
89078
90733
|
if (processedUrl === "") return { value: processedLabel };
|
|
89079
90734
|
return { value: markdownLink(processedLabel, processedUrl) };
|
|
89080
90735
|
},
|
|
@@ -89304,7 +90959,9 @@ const helpers = {
|
|
|
89304
90959
|
computeCachedTextDimension,
|
|
89305
90960
|
createComputeFunction,
|
|
89306
90961
|
isMobileOS,
|
|
89307
|
-
drawHighlight
|
|
90962
|
+
drawHighlight,
|
|
90963
|
+
getBoundingRectWithMargins,
|
|
90964
|
+
hasInteractiveElementInEventTree
|
|
89308
90965
|
};
|
|
89309
90966
|
const links = {
|
|
89310
90967
|
isMarkdownLink,
|
|
@@ -89536,6 +91193,6 @@ exports.stores = stores;
|
|
|
89536
91193
|
exports.tokenColors = tokenColors;
|
|
89537
91194
|
exports.tokenize = tokenize;
|
|
89538
91195
|
|
|
89539
|
-
__info__.version = "19.5.0-alpha.
|
|
89540
|
-
__info__.date = "2026-08-
|
|
89541
|
-
__info__.hash = "
|
|
91196
|
+
__info__.version = "19.5.0-alpha.12";
|
|
91197
|
+
__info__.date = "2026-08-19T09:47:03.774Z";
|
|
91198
|
+
__info__.hash = "68ca68e";
|