@odoo/o-spreadsheet 17.4.0-alpha.4 → 17.4.0-alpha.6
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/o-spreadsheet.cjs.js +1233 -745
- package/dist/o-spreadsheet.d.ts +231 -109
- package/dist/o-spreadsheet.esm.js +1233 -745
- package/dist/o-spreadsheet.iife.js +1233 -745
- package/dist/o-spreadsheet.iife.min.js +383 -350
- package/dist/o_spreadsheet.xml +257 -24
- package/package.json +1 -1
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
5
5
|
* @see https://github.com/odoo/o-spreadsheet
|
|
6
|
-
* @version 17.4.0-alpha.
|
|
7
|
-
* @date 2024-06-
|
|
8
|
-
* @hash
|
|
6
|
+
* @version 17.4.0-alpha.6
|
|
7
|
+
* @date 2024-06-19T13:46:27.157Z
|
|
8
|
+
* @hash a4f22e4
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
|
|
@@ -5282,7 +5282,7 @@ function clipTextWithEllipsis(ctx, text, maxWidth) {
|
|
|
5282
5282
|
return text;
|
|
5283
5283
|
}
|
|
5284
5284
|
const ellipsis = "…";
|
|
5285
|
-
const ellipsisWidth = computeCachedTextWidth(ctx,
|
|
5285
|
+
const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis);
|
|
5286
5286
|
if (width <= ellipsisWidth) {
|
|
5287
5287
|
return text;
|
|
5288
5288
|
}
|
|
@@ -6200,7 +6200,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6200
6200
|
};
|
|
6201
6201
|
}
|
|
6202
6202
|
isPasteAllowed(sheetId, target, content, clipboardOptions) {
|
|
6203
|
-
if (!
|
|
6203
|
+
if (!content.cells) {
|
|
6204
6204
|
return "Success" /* CommandResult.Success */;
|
|
6205
6205
|
}
|
|
6206
6206
|
if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
|
|
@@ -6214,6 +6214,17 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6214
6214
|
return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
|
|
6215
6215
|
}
|
|
6216
6216
|
}
|
|
6217
|
+
const clipboardHeight = content.cells.length;
|
|
6218
|
+
const clipboardWidth = content.cells[0].length;
|
|
6219
|
+
for (const zone of getPasteZones(target, content.cells)) {
|
|
6220
|
+
if (this.getters.doesIntersectMerge(sheetId, zone)) {
|
|
6221
|
+
if (target.length > 1 ||
|
|
6222
|
+
!this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
|
|
6223
|
+
clipboardHeight * clipboardWidth !== 1) {
|
|
6224
|
+
return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6217
6228
|
return "Success" /* CommandResult.Success */;
|
|
6218
6229
|
}
|
|
6219
6230
|
/**
|
|
@@ -6453,13 +6464,22 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6453
6464
|
}
|
|
6454
6465
|
const { rowsIndexes, columnsIndexes } = data;
|
|
6455
6466
|
const sheetId = data.sheetId;
|
|
6456
|
-
const
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6467
|
+
const cfRules = [];
|
|
6468
|
+
for (const row of rowsIndexes) {
|
|
6469
|
+
const cfRuleInRow = [];
|
|
6470
|
+
for (const col of columnsIndexes) {
|
|
6471
|
+
const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
|
|
6472
|
+
cfRuleInRow.push({
|
|
6473
|
+
position: { col, row, sheetId },
|
|
6474
|
+
rules: cfRules,
|
|
6475
|
+
});
|
|
6476
|
+
}
|
|
6477
|
+
cfRules.push(cfRuleInRow);
|
|
6478
|
+
}
|
|
6479
|
+
return { cfRules };
|
|
6460
6480
|
}
|
|
6461
6481
|
paste(target, clippedContent, options) {
|
|
6462
|
-
if (!clippedContent?.
|
|
6482
|
+
if (!clippedContent?.cfRules ||
|
|
6463
6483
|
options?.pasteOption === "asValue" ||
|
|
6464
6484
|
!("zones" in target) ||
|
|
6465
6485
|
!target.zones.length) {
|
|
@@ -6468,7 +6488,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6468
6488
|
const zones = target.zones;
|
|
6469
6489
|
const sheetId = target.sheetId;
|
|
6470
6490
|
if (!options?.isCutOperation) {
|
|
6471
|
-
this.pasteFromCopy(sheetId, zones, clippedContent.
|
|
6491
|
+
this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
|
|
6472
6492
|
}
|
|
6473
6493
|
else {
|
|
6474
6494
|
this.pasteFromCut(sheetId, zones, clippedContent);
|
|
@@ -6476,12 +6496,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6476
6496
|
}
|
|
6477
6497
|
pasteFromCut(sheetId, target, content) {
|
|
6478
6498
|
const selection = target[0];
|
|
6479
|
-
this.pasteZone(sheetId, selection.left, selection.top, content.
|
|
6499
|
+
this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
|
|
6480
6500
|
isCutOperation: true,
|
|
6481
6501
|
});
|
|
6482
6502
|
}
|
|
6483
|
-
pasteZone(sheetId, col, row,
|
|
6484
|
-
for (const [r, rowCells] of
|
|
6503
|
+
pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
|
|
6504
|
+
for (const [r, rowCells] of cfRules.entries()) {
|
|
6485
6505
|
for (const [c, origin] of rowCells.entries()) {
|
|
6486
6506
|
const position = { col: col + c, row: row + r, sheetId };
|
|
6487
6507
|
this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
|
|
@@ -6489,23 +6509,21 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6489
6509
|
}
|
|
6490
6510
|
}
|
|
6491
6511
|
pasteCf(origin, target, isCutOperation) {
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
for (const
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
|
|
6508
|
-
}
|
|
6512
|
+
if (origin?.rules && origin.rules.length > 0) {
|
|
6513
|
+
const zone = positionToZone(target);
|
|
6514
|
+
for (const rule of origin.rules) {
|
|
6515
|
+
const toRemoveZones = [];
|
|
6516
|
+
if (isCutOperation) {
|
|
6517
|
+
//remove from current rule
|
|
6518
|
+
toRemoveZones.push(positionToZone(origin.position));
|
|
6519
|
+
}
|
|
6520
|
+
if (origin.position.sheetId === target.sheetId) {
|
|
6521
|
+
this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
|
|
6522
|
+
}
|
|
6523
|
+
else {
|
|
6524
|
+
this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
|
|
6525
|
+
const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
|
|
6526
|
+
this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
|
|
6509
6527
|
}
|
|
6510
6528
|
}
|
|
6511
6529
|
}
|
|
@@ -6548,13 +6566,20 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6548
6566
|
}
|
|
6549
6567
|
const { rowsIndexes, columnsIndexes } = data;
|
|
6550
6568
|
const sheetId = data.sheetId;
|
|
6551
|
-
const
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6569
|
+
const dvRules = [];
|
|
6570
|
+
for (const row of rowsIndexes) {
|
|
6571
|
+
const dvRuleInRow = [];
|
|
6572
|
+
for (const col of columnsIndexes) {
|
|
6573
|
+
const position = { sheetId, col, row };
|
|
6574
|
+
const rule = this.getters.getValidationRuleForCell(position);
|
|
6575
|
+
dvRuleInRow.push({ position, rule });
|
|
6576
|
+
}
|
|
6577
|
+
dvRules.push(dvRuleInRow);
|
|
6578
|
+
}
|
|
6579
|
+
return { dvRules };
|
|
6555
6580
|
}
|
|
6556
6581
|
paste(target, clippedContent, options) {
|
|
6557
|
-
if (!clippedContent?.
|
|
6582
|
+
if (!clippedContent?.dvRules) {
|
|
6558
6583
|
return;
|
|
6559
6584
|
}
|
|
6560
6585
|
if (options?.pasteOption) {
|
|
@@ -6566,7 +6591,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6566
6591
|
const zones = target.zones;
|
|
6567
6592
|
const sheetId = target.sheetId;
|
|
6568
6593
|
if (!options?.isCutOperation) {
|
|
6569
|
-
this.pasteFromCopy(sheetId, zones, clippedContent.
|
|
6594
|
+
this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
|
|
6570
6595
|
}
|
|
6571
6596
|
else {
|
|
6572
6597
|
this.pasteFromCut(sheetId, zones, clippedContent);
|
|
@@ -6574,12 +6599,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6574
6599
|
}
|
|
6575
6600
|
pasteFromCut(sheetId, target, content) {
|
|
6576
6601
|
const selection = target[0];
|
|
6577
|
-
this.pasteZone(sheetId, selection.left, selection.top, content.
|
|
6602
|
+
this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
|
|
6578
6603
|
isCutOperation: true,
|
|
6579
6604
|
});
|
|
6580
6605
|
}
|
|
6581
|
-
pasteZone(sheetId, col, row,
|
|
6582
|
-
for (const [r, rowCells] of
|
|
6606
|
+
pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
|
|
6607
|
+
for (const [r, rowCells] of dvRules.entries()) {
|
|
6583
6608
|
for (const [c, origin] of rowCells.entries()) {
|
|
6584
6609
|
const position = { col: col + c, row: row + r, sheetId };
|
|
6585
6610
|
this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
|
|
@@ -6587,41 +6612,43 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6587
6612
|
}
|
|
6588
6613
|
}
|
|
6589
6614
|
pasteDataValidation(origin, target, isCutOperation) {
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
const
|
|
6593
|
-
if (
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
}
|
|
6599
|
-
const zone = positionToZone(target);
|
|
6600
|
-
for (const range of rule.ranges) {
|
|
6601
|
-
if (isInside(origin.col, origin.row, range.zone)) {
|
|
6602
|
-
const toRemoveZone = [];
|
|
6603
|
-
if (isCutOperation) {
|
|
6604
|
-
toRemoveZone.push(positionToZone(origin));
|
|
6605
|
-
}
|
|
6606
|
-
if (origin.sheetId === target.sheetId) {
|
|
6607
|
-
this.adaptDataValidationRule(origin.sheetId, rule, [zone], toRemoveZone);
|
|
6615
|
+
if (origin) {
|
|
6616
|
+
const zone = positionToZone(target);
|
|
6617
|
+
const rule = origin.rule;
|
|
6618
|
+
if (!rule) {
|
|
6619
|
+
const targetRule = this.getters.getValidationRuleForCell(target);
|
|
6620
|
+
if (targetRule) {
|
|
6621
|
+
// Remove the data validation rule on the target cell
|
|
6622
|
+
this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
|
|
6608
6623
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6624
|
+
return;
|
|
6625
|
+
}
|
|
6626
|
+
const toRemoveZone = [];
|
|
6627
|
+
if (isCutOperation) {
|
|
6628
|
+
toRemoveZone.push(positionToZone(origin.position));
|
|
6629
|
+
}
|
|
6630
|
+
if (origin.position.sheetId === target.sheetId) {
|
|
6631
|
+
const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
|
|
6632
|
+
this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
|
|
6633
|
+
}
|
|
6634
|
+
else {
|
|
6635
|
+
const originRule = this.getters.getValidationRuleForCell(origin.position);
|
|
6636
|
+
if (originRule) {
|
|
6637
|
+
this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
|
|
6613
6638
|
}
|
|
6639
|
+
const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
|
|
6640
|
+
this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
|
|
6614
6641
|
}
|
|
6615
6642
|
}
|
|
6616
6643
|
}
|
|
6617
|
-
getDataValidationRuleToCopyTo(targetSheetId, originRule) {
|
|
6644
|
+
getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
|
|
6618
6645
|
const ruleInTargetSheet = this.getters
|
|
6619
6646
|
.getDataValidationRules(targetSheetId)
|
|
6620
6647
|
.find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
|
|
6621
6648
|
originRule.isBlocking === rule.isBlocking);
|
|
6622
6649
|
return ruleInTargetSheet
|
|
6623
6650
|
? ruleInTargetSheet
|
|
6624
|
-
: { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
|
|
6651
|
+
: { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
|
|
6625
6652
|
}
|
|
6626
6653
|
/**
|
|
6627
6654
|
* Add or remove XCs to a given data validation rule.
|
|
@@ -6713,69 +6740,63 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
|
|
|
6713
6740
|
}
|
|
6714
6741
|
|
|
6715
6742
|
class MergeClipboardHandler extends AbstractCellClipboardHandler {
|
|
6716
|
-
|
|
6717
|
-
if (!
|
|
6718
|
-
return
|
|
6743
|
+
copy(data) {
|
|
6744
|
+
if (!data.zones.length) {
|
|
6745
|
+
return;
|
|
6719
6746
|
}
|
|
6720
|
-
const
|
|
6721
|
-
const
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
}
|
|
6747
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
6748
|
+
const { rowsIndexes, columnsIndexes } = data;
|
|
6749
|
+
const merges = [];
|
|
6750
|
+
for (const row of rowsIndexes) {
|
|
6751
|
+
const mergesInRow = [];
|
|
6752
|
+
for (const col of columnsIndexes) {
|
|
6753
|
+
const position = { col, row, sheetId };
|
|
6754
|
+
mergesInRow.push(this.getters.getMerge(position));
|
|
6729
6755
|
}
|
|
6756
|
+
merges.push(mergesInRow);
|
|
6730
6757
|
}
|
|
6731
|
-
return
|
|
6758
|
+
return { merges };
|
|
6732
6759
|
}
|
|
6733
6760
|
/**
|
|
6734
6761
|
* Paste the clipboard content in the given target
|
|
6735
6762
|
*/
|
|
6736
6763
|
paste(target, content, options) {
|
|
6737
|
-
if (
|
|
6764
|
+
if (!content.merges ||
|
|
6765
|
+
options?.isCutOperation ||
|
|
6766
|
+
!("zones" in target) ||
|
|
6767
|
+
!target.zones.length) {
|
|
6738
6768
|
return;
|
|
6739
6769
|
}
|
|
6740
|
-
this.pasteFromCopy(target.sheetId, target.zones, content.
|
|
6770
|
+
this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
|
|
6741
6771
|
}
|
|
6742
|
-
pasteZone(sheetId, col, row,
|
|
6743
|
-
for (const [r,
|
|
6744
|
-
for (const [c,
|
|
6745
|
-
if (!origin.position) {
|
|
6746
|
-
continue;
|
|
6747
|
-
}
|
|
6772
|
+
pasteZone(sheetId, col, row, merges) {
|
|
6773
|
+
for (const [r, rowMerges] of merges.entries()) {
|
|
6774
|
+
for (const [c, originMerge] of rowMerges.entries()) {
|
|
6748
6775
|
const position = { col: col + c, row: row + r, sheetId };
|
|
6749
|
-
this.
|
|
6776
|
+
this.pasteMerge(originMerge, position);
|
|
6750
6777
|
}
|
|
6751
6778
|
}
|
|
6752
6779
|
}
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);
|
|
6760
|
-
if (mainCellColOrigin === col && mainCellRowOrigin === row) {
|
|
6761
|
-
const merge = this.getters.getMerge(origin);
|
|
6762
|
-
if (!merge) {
|
|
6763
|
-
return;
|
|
6764
|
-
}
|
|
6765
|
-
({ sheetId, col, row } = target);
|
|
6766
|
-
this.dispatch("ADD_MERGE", {
|
|
6767
|
-
sheetId,
|
|
6768
|
-
force: true,
|
|
6769
|
-
target: [
|
|
6770
|
-
{
|
|
6771
|
-
left: col,
|
|
6772
|
-
top: row,
|
|
6773
|
-
right: col + merge.right - merge.left,
|
|
6774
|
-
bottom: row + merge.bottom - merge.top,
|
|
6775
|
-
},
|
|
6776
|
-
],
|
|
6777
|
-
});
|
|
6780
|
+
pasteMerge(originMerge, target) {
|
|
6781
|
+
if (!originMerge) {
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
if (this.getters.isInMerge(target)) {
|
|
6785
|
+
return;
|
|
6778
6786
|
}
|
|
6787
|
+
const { sheetId, col, row } = target;
|
|
6788
|
+
this.dispatch("ADD_MERGE", {
|
|
6789
|
+
sheetId,
|
|
6790
|
+
force: true,
|
|
6791
|
+
target: [
|
|
6792
|
+
{
|
|
6793
|
+
left: col,
|
|
6794
|
+
top: row,
|
|
6795
|
+
right: col + originMerge.right - originMerge.left,
|
|
6796
|
+
bottom: row + originMerge.bottom - originMerge.top,
|
|
6797
|
+
},
|
|
6798
|
+
],
|
|
6799
|
+
});
|
|
6779
6800
|
}
|
|
6780
6801
|
}
|
|
6781
6802
|
|
|
@@ -7755,6 +7776,486 @@ function errorCell(value, message) {
|
|
|
7755
7776
|
};
|
|
7756
7777
|
}
|
|
7757
7778
|
|
|
7779
|
+
function boolAnd(args) {
|
|
7780
|
+
let foundBoolean = false;
|
|
7781
|
+
let acc = true;
|
|
7782
|
+
conditionalVisitBoolean(args, (arg) => {
|
|
7783
|
+
foundBoolean = true;
|
|
7784
|
+
acc = acc && arg;
|
|
7785
|
+
return acc;
|
|
7786
|
+
});
|
|
7787
|
+
return {
|
|
7788
|
+
foundBoolean,
|
|
7789
|
+
result: acc,
|
|
7790
|
+
};
|
|
7791
|
+
}
|
|
7792
|
+
function boolOr(args) {
|
|
7793
|
+
let foundBoolean = false;
|
|
7794
|
+
let acc = false;
|
|
7795
|
+
conditionalVisitBoolean(args, (arg) => {
|
|
7796
|
+
foundBoolean = true;
|
|
7797
|
+
acc = acc || arg;
|
|
7798
|
+
return !acc;
|
|
7799
|
+
});
|
|
7800
|
+
return {
|
|
7801
|
+
foundBoolean,
|
|
7802
|
+
result: acc,
|
|
7803
|
+
};
|
|
7804
|
+
}
|
|
7805
|
+
|
|
7806
|
+
function sum(values, locale) {
|
|
7807
|
+
return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
|
|
7808
|
+
}
|
|
7809
|
+
function countUnique(args) {
|
|
7810
|
+
return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
|
|
7811
|
+
}
|
|
7812
|
+
|
|
7813
|
+
function assertSameNumberOfElements(...args) {
|
|
7814
|
+
const dims = args[0].length;
|
|
7815
|
+
args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
|
|
7816
|
+
}
|
|
7817
|
+
function average(values, locale) {
|
|
7818
|
+
let count = 0;
|
|
7819
|
+
const sum = reduceNumbers(values, (acc, a) => {
|
|
7820
|
+
count += 1;
|
|
7821
|
+
return acc + a;
|
|
7822
|
+
}, 0, locale);
|
|
7823
|
+
assertNotZero(count);
|
|
7824
|
+
return sum / count;
|
|
7825
|
+
}
|
|
7826
|
+
function countNumbers(values, locale) {
|
|
7827
|
+
let count = 0;
|
|
7828
|
+
for (let n of values) {
|
|
7829
|
+
if (isMatrix(n)) {
|
|
7830
|
+
for (let i of n) {
|
|
7831
|
+
for (let j of i) {
|
|
7832
|
+
if (typeof j.value === "number") {
|
|
7833
|
+
count += 1;
|
|
7834
|
+
}
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
else {
|
|
7839
|
+
const value = n?.value;
|
|
7840
|
+
if (!isEvaluationError(value) &&
|
|
7841
|
+
(typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
|
|
7842
|
+
count += 1;
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
return count;
|
|
7847
|
+
}
|
|
7848
|
+
function countAny(values) {
|
|
7849
|
+
return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
|
|
7850
|
+
}
|
|
7851
|
+
function max(values, locale) {
|
|
7852
|
+
const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
|
|
7853
|
+
return result === -Infinity ? 0 : result;
|
|
7854
|
+
}
|
|
7855
|
+
function min(values, locale) {
|
|
7856
|
+
const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
|
|
7857
|
+
return result === Infinity ? 0 : result;
|
|
7858
|
+
}
|
|
7859
|
+
|
|
7860
|
+
const pivotTimeAdapterRegistry = new Registry();
|
|
7861
|
+
function pivotTimeAdapter(granularity) {
|
|
7862
|
+
return pivotTimeAdapterRegistry.get(granularity);
|
|
7863
|
+
}
|
|
7864
|
+
/**
|
|
7865
|
+
* The Time Adapter: Managing Time Periods for Pivot Functions
|
|
7866
|
+
*
|
|
7867
|
+
* Overview:
|
|
7868
|
+
* A time adapter is responsible for managing time periods associated with pivot functions.
|
|
7869
|
+
* Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
|
|
7870
|
+
* The adapter's primary role is to normalize period values between spreadsheet functions,
|
|
7871
|
+
* and the pivot.
|
|
7872
|
+
* By normalizing the period value, it can be stored consistently in the pivot.
|
|
7873
|
+
*
|
|
7874
|
+
* Normalization Process:
|
|
7875
|
+
* When working with functions in the spreadsheet, the time adapter normalizes
|
|
7876
|
+
* the provided period to facilitate accurate lookup of values in the pivot.
|
|
7877
|
+
* For instance, if the spreadsheet function represents a day period as a number generated
|
|
7878
|
+
* by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
|
|
7879
|
+
*
|
|
7880
|
+
*/
|
|
7881
|
+
/**
|
|
7882
|
+
* Normalized value: "12/25/2023"
|
|
7883
|
+
*
|
|
7884
|
+
* Note: Those two format are equivalent:
|
|
7885
|
+
* - "MM/dd/yyyy" (luxon format)
|
|
7886
|
+
* - "mm/dd/yyyy" (spreadsheet format)
|
|
7887
|
+
**/
|
|
7888
|
+
const dayAdapter = {
|
|
7889
|
+
normalizeFunctionValue(value) {
|
|
7890
|
+
const date = toNumber(value, DEFAULT_LOCALE);
|
|
7891
|
+
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
|
|
7892
|
+
},
|
|
7893
|
+
getFormat(locale) {
|
|
7894
|
+
return (locale ?? DEFAULT_LOCALE).dateFormat;
|
|
7895
|
+
},
|
|
7896
|
+
formatValue(normalizedValue, locale) {
|
|
7897
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7898
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7899
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7900
|
+
},
|
|
7901
|
+
toCellValue(normalizedValue) {
|
|
7902
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7903
|
+
},
|
|
7904
|
+
};
|
|
7905
|
+
/**
|
|
7906
|
+
* normalizes day of month number
|
|
7907
|
+
*/
|
|
7908
|
+
const dayOfMonthAdapter = {
|
|
7909
|
+
normalizeFunctionValue(value) {
|
|
7910
|
+
const day = toNumber(value, DEFAULT_LOCALE);
|
|
7911
|
+
if (day < 1 || day > 31) {
|
|
7912
|
+
throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
|
|
7913
|
+
}
|
|
7914
|
+
return day;
|
|
7915
|
+
},
|
|
7916
|
+
getFormat() {
|
|
7917
|
+
return "0";
|
|
7918
|
+
},
|
|
7919
|
+
formatValue(normalizedValue, locale) {
|
|
7920
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7921
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7922
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7923
|
+
},
|
|
7924
|
+
toCellValue(normalizedValue) {
|
|
7925
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7926
|
+
},
|
|
7927
|
+
};
|
|
7928
|
+
/**
|
|
7929
|
+
* Normalized value: "2/2023" for week 2 of 2023
|
|
7930
|
+
*/
|
|
7931
|
+
const weekAdapter = {
|
|
7932
|
+
normalizeFunctionValue(value) {
|
|
7933
|
+
const [week, year] = value.split("/");
|
|
7934
|
+
return `${Number(week)}/${Number(year)}`;
|
|
7935
|
+
},
|
|
7936
|
+
getFormat() {
|
|
7937
|
+
return undefined;
|
|
7938
|
+
},
|
|
7939
|
+
formatValue(normalizedValue) {
|
|
7940
|
+
const [week, year] = normalizedValue.split("/");
|
|
7941
|
+
return _t("W%(week)s %(year)s", { week, year });
|
|
7942
|
+
},
|
|
7943
|
+
toCellValue(normalizedValue) {
|
|
7944
|
+
return this.formatValue(normalizedValue);
|
|
7945
|
+
},
|
|
7946
|
+
};
|
|
7947
|
+
/**
|
|
7948
|
+
* normalizes iso week number
|
|
7949
|
+
*/
|
|
7950
|
+
const isoWeekNumberAdapter = {
|
|
7951
|
+
normalizeFunctionValue(value) {
|
|
7952
|
+
const isoWeek = toNumber(value, DEFAULT_LOCALE);
|
|
7953
|
+
if (isoWeek < 0 || isoWeek > 53) {
|
|
7954
|
+
throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
|
|
7955
|
+
}
|
|
7956
|
+
return isoWeek;
|
|
7957
|
+
},
|
|
7958
|
+
getFormat() {
|
|
7959
|
+
return "0";
|
|
7960
|
+
},
|
|
7961
|
+
formatValue(normalizedValue, locale) {
|
|
7962
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7963
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7964
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7965
|
+
},
|
|
7966
|
+
toCellValue(normalizedValue) {
|
|
7967
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7968
|
+
},
|
|
7969
|
+
};
|
|
7970
|
+
/**
|
|
7971
|
+
* normalized month value is a string formatted as "MM/yyyy" (luxon format)
|
|
7972
|
+
* e.g. "01/2020" for January 2020
|
|
7973
|
+
*/
|
|
7974
|
+
const monthAdapter = {
|
|
7975
|
+
normalizeFunctionValue(value) {
|
|
7976
|
+
const date = toNumber(value, DEFAULT_LOCALE);
|
|
7977
|
+
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
|
|
7978
|
+
},
|
|
7979
|
+
getFormat() {
|
|
7980
|
+
return "mmmm yyyy";
|
|
7981
|
+
},
|
|
7982
|
+
formatValue(normalizedValue, locale) {
|
|
7983
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7984
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7985
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7986
|
+
},
|
|
7987
|
+
toCellValue(normalizedValue) {
|
|
7988
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7989
|
+
},
|
|
7990
|
+
};
|
|
7991
|
+
/**
|
|
7992
|
+
* normalizes month number
|
|
7993
|
+
*/
|
|
7994
|
+
const monthNumberAdapter = {
|
|
7995
|
+
normalizeFunctionValue(value) {
|
|
7996
|
+
const month = toNumber(value, DEFAULT_LOCALE);
|
|
7997
|
+
if (month < 1 || month > 12) {
|
|
7998
|
+
throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
|
|
7999
|
+
}
|
|
8000
|
+
return month;
|
|
8001
|
+
},
|
|
8002
|
+
getFormat() {
|
|
8003
|
+
return "0";
|
|
8004
|
+
},
|
|
8005
|
+
formatValue(normalizedValue, locale) {
|
|
8006
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
8007
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8008
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
8009
|
+
},
|
|
8010
|
+
toCellValue(normalizedValue) {
|
|
8011
|
+
return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
|
|
8012
|
+
},
|
|
8013
|
+
};
|
|
8014
|
+
/**
|
|
8015
|
+
* normalized quarter value is "quarter/year"
|
|
8016
|
+
* e.g. "1/2020" for Q1 2020
|
|
8017
|
+
*/
|
|
8018
|
+
const quarterAdapter = {
|
|
8019
|
+
normalizeFunctionValue(value) {
|
|
8020
|
+
const [quarter, year] = value.split("/");
|
|
8021
|
+
return `${quarter}/${year}`;
|
|
8022
|
+
},
|
|
8023
|
+
getFormat() {
|
|
8024
|
+
return undefined;
|
|
8025
|
+
},
|
|
8026
|
+
formatValue(normalizedValue) {
|
|
8027
|
+
const [quarter, year] = normalizedValue.split("/");
|
|
8028
|
+
return _t("Q%(quarter)s %(year)s", { quarter, year });
|
|
8029
|
+
},
|
|
8030
|
+
toCellValue(normalizedValue) {
|
|
8031
|
+
return this.formatValue(normalizedValue);
|
|
8032
|
+
},
|
|
8033
|
+
};
|
|
8034
|
+
/**
|
|
8035
|
+
* normalizes quarter number
|
|
8036
|
+
*/
|
|
8037
|
+
const quarterNumberAdapter = {
|
|
8038
|
+
normalizeFunctionValue(value) {
|
|
8039
|
+
const quarter = toNumber(value, DEFAULT_LOCALE);
|
|
8040
|
+
if (quarter < 1 || quarter > 4) {
|
|
8041
|
+
throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
|
|
8042
|
+
}
|
|
8043
|
+
return quarter;
|
|
8044
|
+
},
|
|
8045
|
+
getFormat() {
|
|
8046
|
+
return "0";
|
|
8047
|
+
},
|
|
8048
|
+
formatValue(normalizedValue, locale) {
|
|
8049
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
8050
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8051
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
8052
|
+
},
|
|
8053
|
+
toCellValue(normalizedValue) {
|
|
8054
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8055
|
+
},
|
|
8056
|
+
};
|
|
8057
|
+
const yearAdapter = {
|
|
8058
|
+
normalizeFunctionValue(value) {
|
|
8059
|
+
return toNumber(value, DEFAULT_LOCALE);
|
|
8060
|
+
},
|
|
8061
|
+
getFormat() {
|
|
8062
|
+
return "0";
|
|
8063
|
+
},
|
|
8064
|
+
formatValue(normalizedValue, locale) {
|
|
8065
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
8066
|
+
return formatValue(normalizedValue, { locale, format: "0" });
|
|
8067
|
+
},
|
|
8068
|
+
toCellValue(normalizedValue) {
|
|
8069
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8070
|
+
},
|
|
8071
|
+
};
|
|
8072
|
+
pivotTimeAdapterRegistry
|
|
8073
|
+
.add("day", dayAdapter)
|
|
8074
|
+
.add("week", weekAdapter)
|
|
8075
|
+
.add("month", monthAdapter)
|
|
8076
|
+
.add("quarter", quarterAdapter)
|
|
8077
|
+
.add("year", yearAdapter)
|
|
8078
|
+
.add("day_of_month", dayOfMonthAdapter)
|
|
8079
|
+
.add("iso_week_number", isoWeekNumberAdapter)
|
|
8080
|
+
.add("month_number", monthNumberAdapter)
|
|
8081
|
+
.add("quarter_number", quarterNumberAdapter)
|
|
8082
|
+
.add("year_number", yearAdapter);
|
|
8083
|
+
|
|
8084
|
+
const AGGREGATOR_NAMES = {
|
|
8085
|
+
count: _t("Count"),
|
|
8086
|
+
count_distinct: _t("Count Distinct"),
|
|
8087
|
+
bool_and: _t("Boolean And"),
|
|
8088
|
+
bool_or: _t("Boolean Or"),
|
|
8089
|
+
max: _t("Maximum"),
|
|
8090
|
+
min: _t("Minimum"),
|
|
8091
|
+
avg: _t("Average"),
|
|
8092
|
+
sum: _t("Sum"),
|
|
8093
|
+
};
|
|
8094
|
+
const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
|
|
8095
|
+
const AGGREGATORS_BY_FIELD_TYPE = {
|
|
8096
|
+
integer: NUMBER_CHAR_AGGREGATORS,
|
|
8097
|
+
char: NUMBER_CHAR_AGGREGATORS,
|
|
8098
|
+
boolean: ["count_distinct", "count", "bool_and", "bool_or"],
|
|
8099
|
+
};
|
|
8100
|
+
const AGGREGATORS = {};
|
|
8101
|
+
for (const type in AGGREGATORS_BY_FIELD_TYPE) {
|
|
8102
|
+
AGGREGATORS[type] = {};
|
|
8103
|
+
for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
|
|
8104
|
+
AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
const AGGREGATORS_FN = {
|
|
8108
|
+
count: {
|
|
8109
|
+
fn: (args) => countAny([args]),
|
|
8110
|
+
format: () => "0",
|
|
8111
|
+
},
|
|
8112
|
+
count_distinct: {
|
|
8113
|
+
fn: (args) => countUnique([args]),
|
|
8114
|
+
format: () => "0",
|
|
8115
|
+
},
|
|
8116
|
+
bool_and: {
|
|
8117
|
+
fn: (args) => boolAnd([args]).result,
|
|
8118
|
+
format: () => undefined,
|
|
8119
|
+
},
|
|
8120
|
+
bool_or: {
|
|
8121
|
+
fn: (args) => boolOr([args]).result,
|
|
8122
|
+
format: () => undefined,
|
|
8123
|
+
},
|
|
8124
|
+
max: {
|
|
8125
|
+
fn: (args, locale) => max([args], locale),
|
|
8126
|
+
format: inferFormat,
|
|
8127
|
+
},
|
|
8128
|
+
min: {
|
|
8129
|
+
fn: (args, locale) => min([args], locale),
|
|
8130
|
+
format: inferFormat,
|
|
8131
|
+
},
|
|
8132
|
+
avg: {
|
|
8133
|
+
fn: (args, locale) => average([args], locale),
|
|
8134
|
+
format: inferFormat,
|
|
8135
|
+
},
|
|
8136
|
+
sum: {
|
|
8137
|
+
fn: (args, locale) => sum([args], locale),
|
|
8138
|
+
format: inferFormat,
|
|
8139
|
+
},
|
|
8140
|
+
};
|
|
8141
|
+
function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
|
|
8142
|
+
switch (pivotCell.type) {
|
|
8143
|
+
case "HEADER":
|
|
8144
|
+
return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
8145
|
+
case "MEASURE_HEADER":
|
|
8146
|
+
return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
|
|
8147
|
+
case "VALUE":
|
|
8148
|
+
return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
8149
|
+
case "EMPTY":
|
|
8150
|
+
return "";
|
|
8151
|
+
}
|
|
8152
|
+
}
|
|
8153
|
+
/**
|
|
8154
|
+
* Build a pivot formula expression
|
|
8155
|
+
*/
|
|
8156
|
+
function makePivotFormula(formula, args) {
|
|
8157
|
+
return `=${formula}(${args
|
|
8158
|
+
.map((arg) => {
|
|
8159
|
+
const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
|
|
8160
|
+
const convertToNumber = typeof arg == "number" || stringIsNumber;
|
|
8161
|
+
return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
|
|
8162
|
+
})
|
|
8163
|
+
.join(",")})`;
|
|
8164
|
+
}
|
|
8165
|
+
/**
|
|
8166
|
+
* Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
|
|
8167
|
+
* in this object
|
|
8168
|
+
* If the object has no keys, return 0
|
|
8169
|
+
*
|
|
8170
|
+
*/
|
|
8171
|
+
function getMaxObjectId(o) {
|
|
8172
|
+
const keys = Object.keys(o);
|
|
8173
|
+
if (!keys.length) {
|
|
8174
|
+
return 0;
|
|
8175
|
+
}
|
|
8176
|
+
const nums = keys.map((id) => parseInt(id, 10));
|
|
8177
|
+
const max = Math.max(...nums);
|
|
8178
|
+
return max;
|
|
8179
|
+
}
|
|
8180
|
+
const ALL_PERIODS = {
|
|
8181
|
+
year: _t("Year"),
|
|
8182
|
+
quarter: _t("Quarter"),
|
|
8183
|
+
month: _t("Month"),
|
|
8184
|
+
week: _t("Week"),
|
|
8185
|
+
day: _t("Day"),
|
|
8186
|
+
year_number: _t("Year"),
|
|
8187
|
+
quarter_number: _t("Quarter"),
|
|
8188
|
+
month_number: _t("Month"),
|
|
8189
|
+
iso_week_number: _t("Week"),
|
|
8190
|
+
day_of_month: _t("Day of Month"),
|
|
8191
|
+
};
|
|
8192
|
+
const DATE_FIELDS = ["date", "datetime"];
|
|
8193
|
+
/**
|
|
8194
|
+
* Parse a dimension string into a pivot dimension definition.
|
|
8195
|
+
* e.g "create_date:month" => { name: "create_date", granularity: "month" }
|
|
8196
|
+
*/
|
|
8197
|
+
function parseDimension(dimension) {
|
|
8198
|
+
const [name, granularity] = dimension.split(":");
|
|
8199
|
+
if (granularity) {
|
|
8200
|
+
return { name, granularity };
|
|
8201
|
+
}
|
|
8202
|
+
return { name };
|
|
8203
|
+
}
|
|
8204
|
+
function isDateField(field) {
|
|
8205
|
+
return DATE_FIELDS.includes(field.type);
|
|
8206
|
+
}
|
|
8207
|
+
function toPivotDomain(domainStr) {
|
|
8208
|
+
if (domainStr.length % 2 !== 0) {
|
|
8209
|
+
throw new Error("Invalid domain: odd number of elements");
|
|
8210
|
+
}
|
|
8211
|
+
const domain = [];
|
|
8212
|
+
for (let i = 0; i < domainStr.length - 1; i += 2) {
|
|
8213
|
+
domain.push({ field: domainStr[i], value: domainStr[i + 1] });
|
|
8214
|
+
}
|
|
8215
|
+
return domain;
|
|
8216
|
+
}
|
|
8217
|
+
function flatPivotDomain(domain) {
|
|
8218
|
+
return domain.flatMap((arg) => [arg.field, arg.value]);
|
|
8219
|
+
}
|
|
8220
|
+
/**
|
|
8221
|
+
* Parses the value defining a pivot group in a PIVOT formula
|
|
8222
|
+
* e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
|
|
8223
|
+
* the two group values are "42" and "won".
|
|
8224
|
+
*/
|
|
8225
|
+
function toNormalizedPivotValue(dimension, groupValue) {
|
|
8226
|
+
if (groupValue === null || groupValue === "null") {
|
|
8227
|
+
return null;
|
|
8228
|
+
}
|
|
8229
|
+
const groupValueString = typeof groupValue === "boolean"
|
|
8230
|
+
? toString(groupValue).toLocaleLowerCase()
|
|
8231
|
+
: toString(groupValue);
|
|
8232
|
+
if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
|
|
8233
|
+
throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
|
|
8234
|
+
field: dimension.displayName,
|
|
8235
|
+
type: dimension.type,
|
|
8236
|
+
}));
|
|
8237
|
+
}
|
|
8238
|
+
// represents a field which is not set (=False server side)
|
|
8239
|
+
if (groupValueString === "false") {
|
|
8240
|
+
return false;
|
|
8241
|
+
}
|
|
8242
|
+
const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
|
|
8243
|
+
return normalizer(groupValueString, dimension.granularity);
|
|
8244
|
+
}
|
|
8245
|
+
function normalizeDateTime(value, granularity) {
|
|
8246
|
+
if (!granularity) {
|
|
8247
|
+
throw "";
|
|
8248
|
+
}
|
|
8249
|
+
return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
|
|
8250
|
+
}
|
|
8251
|
+
const pivotNormalizationValueRegistry = new Registry();
|
|
8252
|
+
pivotNormalizationValueRegistry
|
|
8253
|
+
.add("date", normalizeDateTime)
|
|
8254
|
+
.add("datetime", normalizeDateTime)
|
|
8255
|
+
.add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
|
|
8256
|
+
.add("boolean", (value) => toBoolean(value))
|
|
8257
|
+
.add("char", (value) => toString(value));
|
|
8258
|
+
|
|
7758
8259
|
/**
|
|
7759
8260
|
* Change the reference types inside the given token, if the token represent a range or a cell
|
|
7760
8261
|
*
|
|
@@ -7953,6 +8454,11 @@ const CellIsOperators = {
|
|
|
7953
8454
|
const ChartTerms = {
|
|
7954
8455
|
Series: _t("Series"),
|
|
7955
8456
|
BackgroundColor: _t("Background color"),
|
|
8457
|
+
StackedBarChart: _t("Stacked bar chart"),
|
|
8458
|
+
StackedLineChart: _t("Stacked line chart"),
|
|
8459
|
+
CumulativeData: _t("Cumulative data"),
|
|
8460
|
+
TreatLabelsAsText: _t("Treat labels as text"),
|
|
8461
|
+
AggregatedChart: _t("Aggregate"),
|
|
7956
8462
|
Errors: {
|
|
7957
8463
|
Unexpected: _t("The chart definition is invalid for an unknown reason"),
|
|
7958
8464
|
// BASIC CHART ERRORS (LINE | BAR | PIE)
|
|
@@ -8885,6 +9391,7 @@ function drawHighlight(renderingContext, highlight, rect) {
|
|
|
8885
9391
|
}
|
|
8886
9392
|
const color = highlight.color || HIGHLIGHT_COLOR;
|
|
8887
9393
|
const { ctx } = renderingContext;
|
|
9394
|
+
ctx.save();
|
|
8888
9395
|
if (!highlight.noBorder) {
|
|
8889
9396
|
if (highlight.dashed) {
|
|
8890
9397
|
ctx.setLineDash([5, 3]);
|
|
@@ -8904,6 +9411,7 @@ function drawHighlight(renderingContext, highlight, rect) {
|
|
|
8904
9411
|
ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
|
|
8905
9412
|
ctx.fillRect(x, y, width, height);
|
|
8906
9413
|
}
|
|
9414
|
+
ctx.restore();
|
|
8907
9415
|
}
|
|
8908
9416
|
|
|
8909
9417
|
class HighlightStore extends SpreadsheetStore {
|
|
@@ -9434,8 +9942,20 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9434
9942
|
replaceSelectedRange(zone) {
|
|
9435
9943
|
const ref = this.getZoneReference(zone);
|
|
9436
9944
|
const currentToken = this.tokenAtCursor;
|
|
9437
|
-
|
|
9438
|
-
|
|
9945
|
+
let replaceStart = this.selectionStart;
|
|
9946
|
+
if (currentToken?.type === "REFERENCE") {
|
|
9947
|
+
replaceStart = currentToken.start;
|
|
9948
|
+
}
|
|
9949
|
+
else if (currentToken?.type === "RIGHT_PAREN") {
|
|
9950
|
+
// match left parenthesis
|
|
9951
|
+
const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
|
|
9952
|
+
const functionToken = this.currentTokens[leftParenthesisIndex - 1];
|
|
9953
|
+
if (functionToken === undefined) {
|
|
9954
|
+
return;
|
|
9955
|
+
}
|
|
9956
|
+
replaceStart = functionToken.start;
|
|
9957
|
+
}
|
|
9958
|
+
this.replaceText(ref, replaceStart, this.selectionEnd);
|
|
9439
9959
|
}
|
|
9440
9960
|
/**
|
|
9441
9961
|
* Replace the reference of the old zone by the new one.
|
|
@@ -9455,10 +9975,8 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9455
9975
|
const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
|
|
9456
9976
|
return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
|
|
9457
9977
|
});
|
|
9458
|
-
// this function assumes that the previous range is always found because
|
|
9459
|
-
// it's called when changing a highlight, which exists by definition
|
|
9460
9978
|
if (!previousRefToken) {
|
|
9461
|
-
|
|
9979
|
+
return;
|
|
9462
9980
|
}
|
|
9463
9981
|
const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
|
|
9464
9982
|
this.selectionStart = previousRefToken.start;
|
|
@@ -9470,6 +9988,17 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9470
9988
|
getZoneReference(zone) {
|
|
9471
9989
|
const inputSheetId = this.currentEditedCell.sheetId;
|
|
9472
9990
|
const sheetId = this.getters.getActiveSheetId();
|
|
9991
|
+
if (zone.top === zone.bottom && zone.left === zone.right) {
|
|
9992
|
+
const position = { sheetId, col: zone.left, row: zone.top };
|
|
9993
|
+
const pivotId = this.getters.getPivotIdFromPosition(position);
|
|
9994
|
+
const pivotCell = this.getters.getPivotCellFromPosition(position);
|
|
9995
|
+
const cell = this.getters.getCell(position);
|
|
9996
|
+
if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
|
|
9997
|
+
const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
|
|
9998
|
+
const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
|
|
9999
|
+
return formula.slice(1); // strip leading =
|
|
10000
|
+
}
|
|
10001
|
+
}
|
|
9473
10002
|
const range = this.getters.getRangeFromZone(sheetId, zone);
|
|
9474
10003
|
return this.getters.getSelectionRangeString(range, inputSheetId);
|
|
9475
10004
|
}
|
|
@@ -9572,19 +10101,35 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9572
10101
|
const colorIndex = this.colorIndexByRange[rangeString];
|
|
9573
10102
|
return colors$1[colorIndex % colors$1.length];
|
|
9574
10103
|
};
|
|
9575
|
-
|
|
10104
|
+
const highlights = [];
|
|
10105
|
+
for (const range of this.getReferencedRanges()) {
|
|
9576
10106
|
const rangeString = this.getters.getRangeString(range, editionSheetId);
|
|
9577
10107
|
const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
|
|
9578
10108
|
const zone = numberOfRows * numberOfCols === 1
|
|
9579
10109
|
? this.getters.expandZone(range.sheetId, range.zone)
|
|
9580
10110
|
: range.zone;
|
|
9581
|
-
|
|
10111
|
+
highlights.push({
|
|
9582
10112
|
zone,
|
|
9583
10113
|
color: rangeColor(rangeString),
|
|
9584
10114
|
sheetId: range.sheetId,
|
|
9585
10115
|
interactive: true,
|
|
9586
|
-
};
|
|
9587
|
-
}
|
|
10116
|
+
});
|
|
10117
|
+
}
|
|
10118
|
+
const activeSheetId = this.getters.getActiveSheetId();
|
|
10119
|
+
const selectionZone = this.model.selection.getAnchor().zone;
|
|
10120
|
+
const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
|
|
10121
|
+
if (this.editionMode === "selecting" && !isSelectionHightlighted) {
|
|
10122
|
+
highlights.push({
|
|
10123
|
+
zone: selectionZone,
|
|
10124
|
+
color: "#445566",
|
|
10125
|
+
sheetId: activeSheetId,
|
|
10126
|
+
dashed: true,
|
|
10127
|
+
interactive: false,
|
|
10128
|
+
noFill: true,
|
|
10129
|
+
thinLine: true,
|
|
10130
|
+
});
|
|
10131
|
+
}
|
|
10132
|
+
return highlights;
|
|
9588
10133
|
}
|
|
9589
10134
|
/**
|
|
9590
10135
|
* Return ranges currently referenced in the composer
|
|
@@ -9843,8 +10388,14 @@ class ChartJsComponent extends Component {
|
|
|
9843
10388
|
useEffect(() => {
|
|
9844
10389
|
const runtime = this.chartRuntime;
|
|
9845
10390
|
if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
|
|
10391
|
+
if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
|
|
10392
|
+
this.chart?.destroy();
|
|
10393
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
10394
|
+
}
|
|
10395
|
+
else {
|
|
10396
|
+
this.updateChartJs(deepCopy(runtime));
|
|
10397
|
+
}
|
|
9846
10398
|
this.currentRuntime = runtime;
|
|
9847
|
-
this.updateChartJs(deepCopy(runtime));
|
|
9848
10399
|
}
|
|
9849
10400
|
});
|
|
9850
10401
|
}
|
|
@@ -10212,6 +10763,9 @@ function getChartAxisTitleRuntime(design) {
|
|
|
10212
10763
|
}
|
|
10213
10764
|
function getDefinedAxis(definition) {
|
|
10214
10765
|
let useLeftAxis = false, useRightAxis = false;
|
|
10766
|
+
if ("horizontal" in definition && definition.horizontal) {
|
|
10767
|
+
return { useLeftAxis: true, useRightAxis: false };
|
|
10768
|
+
}
|
|
10215
10769
|
for (const design of definition.dataSets || []) {
|
|
10216
10770
|
if (design.yAxisId === "y1") {
|
|
10217
10771
|
useRightAxis = true;
|
|
@@ -10422,6 +10976,7 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
|
|
|
10422
10976
|
function drawScoreChart(structure, canvas) {
|
|
10423
10977
|
const ctx = canvas.getContext("2d");
|
|
10424
10978
|
canvas.width = structure.canvas.width;
|
|
10979
|
+
const availableWidth = canvas.width - DEFAULT_CHART_PADDING;
|
|
10425
10980
|
canvas.height = structure.canvas.height;
|
|
10426
10981
|
ctx.fillStyle = structure.canvas.backgroundColor;
|
|
10427
10982
|
ctx.fillRect(0, 0, structure.canvas.width, structure.canvas.height);
|
|
@@ -10430,7 +10985,7 @@ function drawScoreChart(structure, canvas) {
|
|
|
10430
10985
|
ctx.fillStyle = structure.title.style.color;
|
|
10431
10986
|
const baseline = ctx.textBaseline;
|
|
10432
10987
|
ctx.textBaseline = "middle";
|
|
10433
|
-
ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text,
|
|
10988
|
+
ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, availableWidth - structure.title.position.x), structure.title.position.x, structure.title.position.y);
|
|
10434
10989
|
ctx.textBaseline = baseline;
|
|
10435
10990
|
}
|
|
10436
10991
|
if (structure.baseline) {
|
|
@@ -10520,13 +11075,16 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
10520
11075
|
return {
|
|
10521
11076
|
title: {
|
|
10522
11077
|
...chart.title,
|
|
11078
|
+
// chart titles are extracted from .json files and they are translated at runtime here
|
|
10523
11079
|
text: _t(chart.title.text ?? ""),
|
|
10524
11080
|
},
|
|
10525
11081
|
keyValue: formattedKeyValue,
|
|
10526
11082
|
baselineDisplay,
|
|
10527
11083
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
10528
11084
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
10529
|
-
baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr
|
|
11085
|
+
baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr
|
|
11086
|
+
? _t(chart.baselineDescr) // descriptions are extracted from .json files and they are translated at runtime here
|
|
11087
|
+
: "",
|
|
10530
11088
|
fontColor,
|
|
10531
11089
|
background,
|
|
10532
11090
|
baselineStyle: chart.baselineMode !== "percentage" && chart.baselineMode !== "progress" && baseline
|
|
@@ -10555,7 +11113,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
10555
11113
|
/* Sizes of boxes containing the texts, in percentage of the Chart size */
|
|
10556
11114
|
const KEY_BOX_HEIGHT_RATIO = 0.8;
|
|
10557
11115
|
/* Padding at the border of the chart */
|
|
10558
|
-
const CHART_PADDING =
|
|
11116
|
+
const CHART_PADDING = 10;
|
|
10559
11117
|
const BOTTOM_PADDING_RATIO = 0.05;
|
|
10560
11118
|
/**
|
|
10561
11119
|
* Line height (in em)
|
|
@@ -10705,6 +11263,7 @@ class ScorecardChartConfigBuilder {
|
|
|
10705
11263
|
position: {
|
|
10706
11264
|
x: (this.width - keyWidth) / 2,
|
|
10707
11265
|
y: this.height * (0.5 - BOTTOM_PADDING_RATIO * 2) +
|
|
11266
|
+
CHART_PADDING / 2 +
|
|
10708
11267
|
(titleHeight + keyHeight / (this.baseline || this.baselineDescr ? 2 : 1.2)) / 2,
|
|
10709
11268
|
},
|
|
10710
11269
|
};
|
|
@@ -10764,7 +11323,8 @@ class ScorecardChartConfigBuilder {
|
|
|
10764
11323
|
const remainingWidth = maxLineWidth - baselineValueWidth;
|
|
10765
11324
|
let baselineDescrFontSize = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => computeTextWidth(this.context, this.baselineDescr, { fontSize }));
|
|
10766
11325
|
let isBaselineSplit = false;
|
|
10767
|
-
if (baselineDescrFontSize < baselineValueFontSize / 2.5
|
|
11326
|
+
if (baselineDescrFontSize < baselineValueFontSize / 2.5 &&
|
|
11327
|
+
this.baselineDescr.trim().includes(" ")) {
|
|
10768
11328
|
isBaselineSplit = true;
|
|
10769
11329
|
baselineDescrFontSize = Math.floor(baselineValueFontSize / 2.5);
|
|
10770
11330
|
for (const line of splitTextInTwoLines(this.baselineDescr)) {
|
|
@@ -10813,7 +11373,7 @@ class ScorecardChartConfigBuilder {
|
|
|
10813
11373
|
/** Get the height of the chart minus all the vertical paddings */
|
|
10814
11374
|
getDrawableHeight() {
|
|
10815
11375
|
const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
|
|
10816
|
-
let availableHeight = this.height -
|
|
11376
|
+
let availableHeight = this.height - verticalPadding;
|
|
10817
11377
|
availableHeight -= this.title ? DEFAULT_CHART_FONT_SIZE * LINE_HEIGHT : 0;
|
|
10818
11378
|
return availableHeight;
|
|
10819
11379
|
}
|
|
@@ -10855,6 +11415,11 @@ class ScorecardChart extends Component {
|
|
|
10855
11415
|
get runtime() {
|
|
10856
11416
|
return this.env.model.getters.getChartRuntime(this.props.figure.id);
|
|
10857
11417
|
}
|
|
11418
|
+
get title() {
|
|
11419
|
+
const title = this.env.model.getters.getChartDefinition(this.props.figure.id).title.text ?? "";
|
|
11420
|
+
// chart titles are extracted from .json files and they are translated at runtime here
|
|
11421
|
+
return _t(title);
|
|
11422
|
+
}
|
|
10858
11423
|
setup() {
|
|
10859
11424
|
useEffect(this.createChart.bind(this), () => {
|
|
10860
11425
|
const canvas = this.canvas.el;
|
|
@@ -11768,13 +12333,6 @@ var misc = /*#__PURE__*/Object.freeze({
|
|
|
11768
12333
|
FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
|
|
11769
12334
|
});
|
|
11770
12335
|
|
|
11771
|
-
function sum(values, locale) {
|
|
11772
|
-
return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
|
|
11773
|
-
}
|
|
11774
|
-
function countUnique(args) {
|
|
11775
|
-
return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
|
|
11776
|
-
}
|
|
11777
|
-
|
|
11778
12336
|
const DEFAULT_FACTOR = 1;
|
|
11779
12337
|
const DEFAULT_MODE = 0;
|
|
11780
12338
|
const DEFAULT_PLACES = 0;
|
|
@@ -12888,53 +13446,6 @@ var math = /*#__PURE__*/Object.freeze({
|
|
|
12888
13446
|
TRUNC: TRUNC
|
|
12889
13447
|
});
|
|
12890
13448
|
|
|
12891
|
-
function assertSameNumberOfElements(...args) {
|
|
12892
|
-
const dims = args[0].length;
|
|
12893
|
-
args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
|
|
12894
|
-
}
|
|
12895
|
-
function average(values, locale) {
|
|
12896
|
-
let count = 0;
|
|
12897
|
-
const sum = reduceNumbers(values, (acc, a) => {
|
|
12898
|
-
count += 1;
|
|
12899
|
-
return acc + a;
|
|
12900
|
-
}, 0, locale);
|
|
12901
|
-
assertNotZero(count);
|
|
12902
|
-
return sum / count;
|
|
12903
|
-
}
|
|
12904
|
-
function countNumbers(values, locale) {
|
|
12905
|
-
let count = 0;
|
|
12906
|
-
for (let n of values) {
|
|
12907
|
-
if (isMatrix(n)) {
|
|
12908
|
-
for (let i of n) {
|
|
12909
|
-
for (let j of i) {
|
|
12910
|
-
if (typeof j.value === "number") {
|
|
12911
|
-
count += 1;
|
|
12912
|
-
}
|
|
12913
|
-
}
|
|
12914
|
-
}
|
|
12915
|
-
}
|
|
12916
|
-
else {
|
|
12917
|
-
const value = n?.value;
|
|
12918
|
-
if (!isEvaluationError(value) &&
|
|
12919
|
-
(typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
|
|
12920
|
-
count += 1;
|
|
12921
|
-
}
|
|
12922
|
-
}
|
|
12923
|
-
}
|
|
12924
|
-
return count;
|
|
12925
|
-
}
|
|
12926
|
-
function countAny(values) {
|
|
12927
|
-
return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
|
|
12928
|
-
}
|
|
12929
|
-
function max(values, locale) {
|
|
12930
|
-
const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
|
|
12931
|
-
return result === -Infinity ? 0 : result;
|
|
12932
|
-
}
|
|
12933
|
-
function min(values, locale) {
|
|
12934
|
-
const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
|
|
12935
|
-
return result === Infinity ? 0 : result;
|
|
12936
|
-
}
|
|
12937
|
-
|
|
12938
13449
|
function filterAndFlatData(dataY, dataX) {
|
|
12939
13450
|
const _flatDataY = [];
|
|
12940
13451
|
const _flatDataX = [];
|
|
@@ -13490,7 +14001,7 @@ const LARGE = {
|
|
|
13490
14001
|
// LINEST
|
|
13491
14002
|
// -----------------------------------------------------------------------------
|
|
13492
14003
|
const LINEST = {
|
|
13493
|
-
description: _t("
|
|
14004
|
+
description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
|
|
13494
14005
|
args: [
|
|
13495
14006
|
arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
|
|
13496
14007
|
arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
|
|
@@ -13506,7 +14017,7 @@ const LINEST = {
|
|
|
13506
14017
|
// LOGEST
|
|
13507
14018
|
// -----------------------------------------------------------------------------
|
|
13508
14019
|
const LOGEST = {
|
|
13509
|
-
description: _t("
|
|
14020
|
+
description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
|
|
13510
14021
|
args: [
|
|
13511
14022
|
arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
|
|
13512
14023
|
arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
|
|
@@ -17909,33 +18420,6 @@ var info = /*#__PURE__*/Object.freeze({
|
|
|
17909
18420
|
NA: NA
|
|
17910
18421
|
});
|
|
17911
18422
|
|
|
17912
|
-
function boolAnd(args) {
|
|
17913
|
-
let foundBoolean = false;
|
|
17914
|
-
let acc = true;
|
|
17915
|
-
conditionalVisitBoolean(args, (arg) => {
|
|
17916
|
-
foundBoolean = true;
|
|
17917
|
-
acc = acc && arg;
|
|
17918
|
-
return acc;
|
|
17919
|
-
});
|
|
17920
|
-
return {
|
|
17921
|
-
foundBoolean,
|
|
17922
|
-
result: acc,
|
|
17923
|
-
};
|
|
17924
|
-
}
|
|
17925
|
-
function boolOr(args) {
|
|
17926
|
-
let foundBoolean = false;
|
|
17927
|
-
let acc = false;
|
|
17928
|
-
conditionalVisitBoolean(args, (arg) => {
|
|
17929
|
-
foundBoolean = true;
|
|
17930
|
-
acc = acc || arg;
|
|
17931
|
-
return !acc;
|
|
17932
|
-
});
|
|
17933
|
-
return {
|
|
17934
|
-
foundBoolean,
|
|
17935
|
-
result: acc,
|
|
17936
|
-
};
|
|
17937
|
-
}
|
|
17938
|
-
|
|
17939
18423
|
// -----------------------------------------------------------------------------
|
|
17940
18424
|
// AND
|
|
17941
18425
|
// -----------------------------------------------------------------------------
|
|
@@ -18133,393 +18617,6 @@ var logical = /*#__PURE__*/Object.freeze({
|
|
|
18133
18617
|
XOR: XOR
|
|
18134
18618
|
});
|
|
18135
18619
|
|
|
18136
|
-
const pivotTimeAdapterRegistry = new Registry();
|
|
18137
|
-
function pivotTimeAdapter(granularity) {
|
|
18138
|
-
return pivotTimeAdapterRegistry.get(granularity);
|
|
18139
|
-
}
|
|
18140
|
-
/**
|
|
18141
|
-
* The Time Adapter: Managing Time Periods for Pivot Functions
|
|
18142
|
-
*
|
|
18143
|
-
* Overview:
|
|
18144
|
-
* A time adapter is responsible for managing time periods associated with pivot functions.
|
|
18145
|
-
* Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
|
|
18146
|
-
* The adapter's primary role is to normalize period values between spreadsheet functions,
|
|
18147
|
-
* and the pivot.
|
|
18148
|
-
* By normalizing the period value, it can be stored consistently in the pivot.
|
|
18149
|
-
*
|
|
18150
|
-
* Normalization Process:
|
|
18151
|
-
* When working with functions in the spreadsheet, the time adapter normalizes
|
|
18152
|
-
* the provided period to facilitate accurate lookup of values in the pivot.
|
|
18153
|
-
* For instance, if the spreadsheet function represents a day period as a number generated
|
|
18154
|
-
* by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
|
|
18155
|
-
*
|
|
18156
|
-
*/
|
|
18157
|
-
/**
|
|
18158
|
-
* Normalized value: "12/25/2023"
|
|
18159
|
-
*
|
|
18160
|
-
* Note: Those two format are equivalent:
|
|
18161
|
-
* - "MM/dd/yyyy" (luxon format)
|
|
18162
|
-
* - "mm/dd/yyyy" (spreadsheet format)
|
|
18163
|
-
**/
|
|
18164
|
-
const dayAdapter = {
|
|
18165
|
-
normalizeFunctionValue(value) {
|
|
18166
|
-
const date = toNumber(value, DEFAULT_LOCALE);
|
|
18167
|
-
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
|
|
18168
|
-
},
|
|
18169
|
-
getFormat(locale) {
|
|
18170
|
-
return (locale ?? DEFAULT_LOCALE).dateFormat;
|
|
18171
|
-
},
|
|
18172
|
-
formatValue(normalizedValue, locale) {
|
|
18173
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18174
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18175
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18176
|
-
},
|
|
18177
|
-
toCellValue(normalizedValue) {
|
|
18178
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18179
|
-
},
|
|
18180
|
-
};
|
|
18181
|
-
/**
|
|
18182
|
-
* normalizes day of month number
|
|
18183
|
-
*/
|
|
18184
|
-
const dayOfMonthAdapter = {
|
|
18185
|
-
normalizeFunctionValue(value) {
|
|
18186
|
-
const day = toNumber(value, DEFAULT_LOCALE);
|
|
18187
|
-
if (day < 1 || day > 31) {
|
|
18188
|
-
throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
|
|
18189
|
-
}
|
|
18190
|
-
return day;
|
|
18191
|
-
},
|
|
18192
|
-
getFormat() {
|
|
18193
|
-
return "0";
|
|
18194
|
-
},
|
|
18195
|
-
formatValue(normalizedValue, locale) {
|
|
18196
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18197
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18198
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18199
|
-
},
|
|
18200
|
-
toCellValue(normalizedValue) {
|
|
18201
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18202
|
-
},
|
|
18203
|
-
};
|
|
18204
|
-
/**
|
|
18205
|
-
* Normalized value: "2/2023" for week 2 of 2023
|
|
18206
|
-
*/
|
|
18207
|
-
const weekAdapter = {
|
|
18208
|
-
normalizeFunctionValue(value) {
|
|
18209
|
-
const [week, year] = value.split("/");
|
|
18210
|
-
return `${Number(week)}/${Number(year)}`;
|
|
18211
|
-
},
|
|
18212
|
-
getFormat() {
|
|
18213
|
-
return undefined;
|
|
18214
|
-
},
|
|
18215
|
-
formatValue(normalizedValue) {
|
|
18216
|
-
const [week, year] = normalizedValue.split("/");
|
|
18217
|
-
return _t("W%(week)s %(year)s", { week, year });
|
|
18218
|
-
},
|
|
18219
|
-
toCellValue(normalizedValue) {
|
|
18220
|
-
return this.formatValue(normalizedValue);
|
|
18221
|
-
},
|
|
18222
|
-
};
|
|
18223
|
-
/**
|
|
18224
|
-
* normalizes iso week number
|
|
18225
|
-
*/
|
|
18226
|
-
const isoWeekNumberAdapter = {
|
|
18227
|
-
normalizeFunctionValue(value) {
|
|
18228
|
-
const isoWeek = toNumber(value, DEFAULT_LOCALE);
|
|
18229
|
-
if (isoWeek < 0 || isoWeek > 53) {
|
|
18230
|
-
throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
|
|
18231
|
-
}
|
|
18232
|
-
return isoWeek;
|
|
18233
|
-
},
|
|
18234
|
-
getFormat() {
|
|
18235
|
-
return "0";
|
|
18236
|
-
},
|
|
18237
|
-
formatValue(normalizedValue, locale) {
|
|
18238
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18239
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18240
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18241
|
-
},
|
|
18242
|
-
toCellValue(normalizedValue) {
|
|
18243
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18244
|
-
},
|
|
18245
|
-
};
|
|
18246
|
-
/**
|
|
18247
|
-
* normalized month value is a string formatted as "MM/yyyy" (luxon format)
|
|
18248
|
-
* e.g. "01/2020" for January 2020
|
|
18249
|
-
*/
|
|
18250
|
-
const monthAdapter = {
|
|
18251
|
-
normalizeFunctionValue(value) {
|
|
18252
|
-
const date = toNumber(value, DEFAULT_LOCALE);
|
|
18253
|
-
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
|
|
18254
|
-
},
|
|
18255
|
-
getFormat() {
|
|
18256
|
-
return "mmmm yyyy";
|
|
18257
|
-
},
|
|
18258
|
-
formatValue(normalizedValue, locale) {
|
|
18259
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18260
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18261
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18262
|
-
},
|
|
18263
|
-
toCellValue(normalizedValue) {
|
|
18264
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18265
|
-
},
|
|
18266
|
-
};
|
|
18267
|
-
/**
|
|
18268
|
-
* normalizes month number
|
|
18269
|
-
*/
|
|
18270
|
-
const monthNumberAdapter = {
|
|
18271
|
-
normalizeFunctionValue(value) {
|
|
18272
|
-
const month = toNumber(value, DEFAULT_LOCALE);
|
|
18273
|
-
if (month < 1 || month > 12) {
|
|
18274
|
-
throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
|
|
18275
|
-
}
|
|
18276
|
-
return month;
|
|
18277
|
-
},
|
|
18278
|
-
getFormat() {
|
|
18279
|
-
return "0";
|
|
18280
|
-
},
|
|
18281
|
-
formatValue(normalizedValue, locale) {
|
|
18282
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18283
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18284
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18285
|
-
},
|
|
18286
|
-
toCellValue(normalizedValue) {
|
|
18287
|
-
return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
|
|
18288
|
-
},
|
|
18289
|
-
};
|
|
18290
|
-
/**
|
|
18291
|
-
* normalized quarter value is "quarter/year"
|
|
18292
|
-
* e.g. "1/2020" for Q1 2020
|
|
18293
|
-
*/
|
|
18294
|
-
const quarterAdapter = {
|
|
18295
|
-
normalizeFunctionValue(value) {
|
|
18296
|
-
const [quarter, year] = value.split("/");
|
|
18297
|
-
return `${quarter}/${year}`;
|
|
18298
|
-
},
|
|
18299
|
-
getFormat() {
|
|
18300
|
-
return undefined;
|
|
18301
|
-
},
|
|
18302
|
-
formatValue(normalizedValue) {
|
|
18303
|
-
const [quarter, year] = normalizedValue.split("/");
|
|
18304
|
-
return _t("Q%(quarter)s %(year)s", { quarter, year });
|
|
18305
|
-
},
|
|
18306
|
-
toCellValue(normalizedValue) {
|
|
18307
|
-
return this.formatValue(normalizedValue);
|
|
18308
|
-
},
|
|
18309
|
-
};
|
|
18310
|
-
/**
|
|
18311
|
-
* normalizes quarter number
|
|
18312
|
-
*/
|
|
18313
|
-
const quarterNumberAdapter = {
|
|
18314
|
-
normalizeFunctionValue(value) {
|
|
18315
|
-
const quarter = toNumber(value, DEFAULT_LOCALE);
|
|
18316
|
-
if (quarter < 1 || quarter > 4) {
|
|
18317
|
-
throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
|
|
18318
|
-
}
|
|
18319
|
-
return quarter;
|
|
18320
|
-
},
|
|
18321
|
-
getFormat() {
|
|
18322
|
-
return "0";
|
|
18323
|
-
},
|
|
18324
|
-
formatValue(normalizedValue, locale) {
|
|
18325
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18326
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18327
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18328
|
-
},
|
|
18329
|
-
toCellValue(normalizedValue) {
|
|
18330
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18331
|
-
},
|
|
18332
|
-
};
|
|
18333
|
-
const yearAdapter = {
|
|
18334
|
-
normalizeFunctionValue(value) {
|
|
18335
|
-
return toNumber(value, DEFAULT_LOCALE);
|
|
18336
|
-
},
|
|
18337
|
-
getFormat() {
|
|
18338
|
-
return "0";
|
|
18339
|
-
},
|
|
18340
|
-
formatValue(normalizedValue, locale) {
|
|
18341
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18342
|
-
return formatValue(normalizedValue, { locale, format: "0" });
|
|
18343
|
-
},
|
|
18344
|
-
toCellValue(normalizedValue) {
|
|
18345
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18346
|
-
},
|
|
18347
|
-
};
|
|
18348
|
-
pivotTimeAdapterRegistry
|
|
18349
|
-
.add("day", dayAdapter)
|
|
18350
|
-
.add("week", weekAdapter)
|
|
18351
|
-
.add("month", monthAdapter)
|
|
18352
|
-
.add("quarter", quarterAdapter)
|
|
18353
|
-
.add("year", yearAdapter)
|
|
18354
|
-
.add("day_of_month", dayOfMonthAdapter)
|
|
18355
|
-
.add("iso_week_number", isoWeekNumberAdapter)
|
|
18356
|
-
.add("month_number", monthNumberAdapter)
|
|
18357
|
-
.add("quarter_number", quarterNumberAdapter)
|
|
18358
|
-
.add("year_number", yearAdapter);
|
|
18359
|
-
|
|
18360
|
-
const AGGREGATOR_NAMES = {
|
|
18361
|
-
count: _t("Count"),
|
|
18362
|
-
count_distinct: _t("Count Distinct"),
|
|
18363
|
-
bool_and: _t("Boolean And"),
|
|
18364
|
-
bool_or: _t("Boolean Or"),
|
|
18365
|
-
max: _t("Maximum"),
|
|
18366
|
-
min: _t("Minimum"),
|
|
18367
|
-
avg: _t("Average"),
|
|
18368
|
-
sum: _t("Sum"),
|
|
18369
|
-
};
|
|
18370
|
-
const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
|
|
18371
|
-
const AGGREGATORS_BY_FIELD_TYPE = {
|
|
18372
|
-
integer: NUMBER_CHAR_AGGREGATORS,
|
|
18373
|
-
char: NUMBER_CHAR_AGGREGATORS,
|
|
18374
|
-
boolean: ["count_distinct", "count", "bool_and", "bool_or"],
|
|
18375
|
-
};
|
|
18376
|
-
const AGGREGATORS = {};
|
|
18377
|
-
for (const type in AGGREGATORS_BY_FIELD_TYPE) {
|
|
18378
|
-
AGGREGATORS[type] = {};
|
|
18379
|
-
for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
|
|
18380
|
-
AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
|
|
18381
|
-
}
|
|
18382
|
-
}
|
|
18383
|
-
const AGGREGATORS_FN = {
|
|
18384
|
-
count: {
|
|
18385
|
-
fn: (args) => countAny([args]),
|
|
18386
|
-
format: () => "0",
|
|
18387
|
-
},
|
|
18388
|
-
count_distinct: {
|
|
18389
|
-
fn: (args) => countUnique([args]),
|
|
18390
|
-
format: () => "0",
|
|
18391
|
-
},
|
|
18392
|
-
bool_and: {
|
|
18393
|
-
fn: (args) => boolAnd([args]).result,
|
|
18394
|
-
format: () => undefined,
|
|
18395
|
-
},
|
|
18396
|
-
bool_or: {
|
|
18397
|
-
fn: (args) => boolOr([args]).result,
|
|
18398
|
-
format: () => undefined,
|
|
18399
|
-
},
|
|
18400
|
-
max: {
|
|
18401
|
-
fn: (args, locale) => max([args], locale),
|
|
18402
|
-
format: inferFormat,
|
|
18403
|
-
},
|
|
18404
|
-
min: {
|
|
18405
|
-
fn: (args, locale) => min([args], locale),
|
|
18406
|
-
format: inferFormat,
|
|
18407
|
-
},
|
|
18408
|
-
avg: {
|
|
18409
|
-
fn: (args, locale) => average([args], locale),
|
|
18410
|
-
format: inferFormat,
|
|
18411
|
-
},
|
|
18412
|
-
sum: {
|
|
18413
|
-
fn: (args, locale) => sum([args], locale),
|
|
18414
|
-
format: inferFormat,
|
|
18415
|
-
},
|
|
18416
|
-
};
|
|
18417
|
-
/**
|
|
18418
|
-
* Build a pivot formula expression
|
|
18419
|
-
*/
|
|
18420
|
-
function makePivotFormula(formula, args) {
|
|
18421
|
-
return `=${formula}(${args
|
|
18422
|
-
.map((arg) => {
|
|
18423
|
-
const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
|
|
18424
|
-
const convertToNumber = typeof arg == "number" || stringIsNumber;
|
|
18425
|
-
return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
|
|
18426
|
-
})
|
|
18427
|
-
.join(",")})`;
|
|
18428
|
-
}
|
|
18429
|
-
/**
|
|
18430
|
-
* Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
|
|
18431
|
-
* in this object
|
|
18432
|
-
* If the object has no keys, return 0
|
|
18433
|
-
*
|
|
18434
|
-
*/
|
|
18435
|
-
function getMaxObjectId(o) {
|
|
18436
|
-
const keys = Object.keys(o);
|
|
18437
|
-
if (!keys.length) {
|
|
18438
|
-
return 0;
|
|
18439
|
-
}
|
|
18440
|
-
const nums = keys.map((id) => parseInt(id, 10));
|
|
18441
|
-
const max = Math.max(...nums);
|
|
18442
|
-
return max;
|
|
18443
|
-
}
|
|
18444
|
-
const ALL_PERIODS = {
|
|
18445
|
-
year: _t("Year"),
|
|
18446
|
-
quarter: _t("Quarter"),
|
|
18447
|
-
month: _t("Month"),
|
|
18448
|
-
week: _t("Week"),
|
|
18449
|
-
day: _t("Day"),
|
|
18450
|
-
year_number: _t("Year"),
|
|
18451
|
-
quarter_number: _t("Quarter"),
|
|
18452
|
-
month_number: _t("Month"),
|
|
18453
|
-
iso_week_number: _t("Week"),
|
|
18454
|
-
day_of_month: _t("Day of Month"),
|
|
18455
|
-
};
|
|
18456
|
-
const DATE_FIELDS = ["date", "datetime"];
|
|
18457
|
-
/**
|
|
18458
|
-
* Parse a dimension string into a pivot dimension definition.
|
|
18459
|
-
* e.g "create_date:month" => { name: "create_date", granularity: "month" }
|
|
18460
|
-
*/
|
|
18461
|
-
function parseDimension(dimension) {
|
|
18462
|
-
const [name, granularity] = dimension.split(":");
|
|
18463
|
-
if (granularity) {
|
|
18464
|
-
return { name, granularity };
|
|
18465
|
-
}
|
|
18466
|
-
return { name };
|
|
18467
|
-
}
|
|
18468
|
-
function isDateField(field) {
|
|
18469
|
-
return DATE_FIELDS.includes(field.type);
|
|
18470
|
-
}
|
|
18471
|
-
function toPivotDomain(domainStr) {
|
|
18472
|
-
if (domainStr.length % 2 !== 0) {
|
|
18473
|
-
throw new Error("Invalid domain: odd number of elements");
|
|
18474
|
-
}
|
|
18475
|
-
const domain = [];
|
|
18476
|
-
for (let i = 0; i < domainStr.length - 1; i += 2) {
|
|
18477
|
-
domain.push({ field: domainStr[i], value: domainStr[i + 1] });
|
|
18478
|
-
}
|
|
18479
|
-
return domain;
|
|
18480
|
-
}
|
|
18481
|
-
function flatPivotDomain(domain) {
|
|
18482
|
-
return domain.flatMap((arg) => [arg.field, arg.value]);
|
|
18483
|
-
}
|
|
18484
|
-
/**
|
|
18485
|
-
* Parses the value defining a pivot group in a PIVOT formula
|
|
18486
|
-
* e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
|
|
18487
|
-
* the two group values are "42" and "won".
|
|
18488
|
-
*/
|
|
18489
|
-
function toNormalizedPivotValue(dimension, groupValue) {
|
|
18490
|
-
if (groupValue === null || groupValue === "null") {
|
|
18491
|
-
return null;
|
|
18492
|
-
}
|
|
18493
|
-
const groupValueString = typeof groupValue === "boolean"
|
|
18494
|
-
? toString(groupValue).toLocaleLowerCase()
|
|
18495
|
-
: toString(groupValue);
|
|
18496
|
-
if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
|
|
18497
|
-
throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
|
|
18498
|
-
field: dimension.displayName,
|
|
18499
|
-
type: dimension.type,
|
|
18500
|
-
}));
|
|
18501
|
-
}
|
|
18502
|
-
// represents a field which is not set (=False server side)
|
|
18503
|
-
if (groupValueString === "false") {
|
|
18504
|
-
return false;
|
|
18505
|
-
}
|
|
18506
|
-
const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
|
|
18507
|
-
return normalizer(groupValueString, dimension.granularity);
|
|
18508
|
-
}
|
|
18509
|
-
function normalizeDateTime(value, granularity) {
|
|
18510
|
-
if (!granularity) {
|
|
18511
|
-
throw "";
|
|
18512
|
-
}
|
|
18513
|
-
return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
|
|
18514
|
-
}
|
|
18515
|
-
const pivotNormalizationValueRegistry = new Registry();
|
|
18516
|
-
pivotNormalizationValueRegistry
|
|
18517
|
-
.add("date", normalizeDateTime)
|
|
18518
|
-
.add("datetime", normalizeDateTime)
|
|
18519
|
-
.add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
|
|
18520
|
-
.add("boolean", (value) => toBoolean(value))
|
|
18521
|
-
.add("char", (value) => toString(value));
|
|
18522
|
-
|
|
18523
18620
|
/**
|
|
18524
18621
|
* Get the pivot ID from the formula pivot ID.
|
|
18525
18622
|
*/
|
|
@@ -19096,14 +19193,10 @@ const PIVOT = {
|
|
|
19096
19193
|
result[col].push({ value: "" });
|
|
19097
19194
|
break;
|
|
19098
19195
|
case "HEADER":
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
}
|
|
19104
|
-
else {
|
|
19105
|
-
result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
|
|
19106
|
-
}
|
|
19196
|
+
result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
|
|
19197
|
+
break;
|
|
19198
|
+
case "MEASURE_HEADER":
|
|
19199
|
+
result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
|
|
19107
19200
|
break;
|
|
19108
19201
|
case "VALUE":
|
|
19109
19202
|
result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
|
|
@@ -20142,6 +20235,8 @@ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
|
|
|
20142
20235
|
* a child element.
|
|
20143
20236
|
*/
|
|
20144
20237
|
function isChildEvent(parent, ev) {
|
|
20238
|
+
if (!parent)
|
|
20239
|
+
return false;
|
|
20145
20240
|
return !!ev.target && parent.contains(ev.target);
|
|
20146
20241
|
}
|
|
20147
20242
|
function gridOverlayPosition() {
|
|
@@ -22681,7 +22776,8 @@ function truncateLabel(label) {
|
|
|
22681
22776
|
/**
|
|
22682
22777
|
* Get a default chart js configuration
|
|
22683
22778
|
*/
|
|
22684
|
-
function getDefaultChartJsRuntime(chart, labels, fontColor,
|
|
22779
|
+
function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
|
|
22780
|
+
const { format, locale, truncateLabels, horizontalChart } = args;
|
|
22685
22781
|
const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
|
|
22686
22782
|
const options = {
|
|
22687
22783
|
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
@@ -22725,8 +22821,11 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
|
|
|
22725
22821
|
callbacks: {
|
|
22726
22822
|
label: function (tooltipItem) {
|
|
22727
22823
|
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
22728
|
-
// tooltipItem.parsed
|
|
22729
|
-
|
|
22824
|
+
// tooltipItem.parsed can be an object or a number for pie charts
|
|
22825
|
+
let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
|
|
22826
|
+
if (!yLabel) {
|
|
22827
|
+
yLabel = tooltipItem.parsed;
|
|
22828
|
+
}
|
|
22730
22829
|
const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
|
|
22731
22830
|
const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
|
|
22732
22831
|
return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
|
|
@@ -22900,6 +22999,7 @@ class BarChart extends AbstractChart {
|
|
|
22900
22999
|
dataSetsHaveTitle;
|
|
22901
23000
|
dataSetDesign;
|
|
22902
23001
|
axesDesign;
|
|
23002
|
+
horizontal;
|
|
22903
23003
|
constructor(definition, sheetId, getters) {
|
|
22904
23004
|
super(definition, sheetId, getters);
|
|
22905
23005
|
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
@@ -22911,6 +23011,7 @@ class BarChart extends AbstractChart {
|
|
|
22911
23011
|
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
22912
23012
|
this.dataSetDesign = definition.dataSets;
|
|
22913
23013
|
this.axesDesign = definition.axesDesign;
|
|
23014
|
+
this.horizontal = definition.horizontal;
|
|
22914
23015
|
}
|
|
22915
23016
|
static transformDefinition(definition, executed) {
|
|
22916
23017
|
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
@@ -22982,6 +23083,7 @@ class BarChart extends AbstractChart {
|
|
|
22982
23083
|
stacked: this.stacked,
|
|
22983
23084
|
aggregated: this.aggregated,
|
|
22984
23085
|
axesDesign: this.axesDesign,
|
|
23086
|
+
horizontal: this.horizontal,
|
|
22985
23087
|
};
|
|
22986
23088
|
}
|
|
22987
23089
|
getDefinitionForExcel() {
|
|
@@ -23013,7 +23115,10 @@ class BarChart extends AbstractChart {
|
|
|
23013
23115
|
}
|
|
23014
23116
|
function getBarConfiguration(chart, labels, localeFormat) {
|
|
23015
23117
|
const fontColor = chartFontColor(chart.background);
|
|
23016
|
-
const config = getDefaultChartJsRuntime(chart, labels, fontColor,
|
|
23118
|
+
const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
|
|
23119
|
+
...localeFormat,
|
|
23120
|
+
horizontalChart: chart.horizontal,
|
|
23121
|
+
});
|
|
23017
23122
|
const legend = {
|
|
23018
23123
|
labels: { color: fontColor },
|
|
23019
23124
|
};
|
|
@@ -23027,16 +23132,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
23027
23132
|
config.options.layout = {
|
|
23028
23133
|
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
|
23029
23134
|
};
|
|
23030
|
-
config.options.
|
|
23031
|
-
|
|
23032
|
-
|
|
23033
|
-
|
|
23034
|
-
color: fontColor,
|
|
23035
|
-
},
|
|
23036
|
-
title: getChartAxisTitleRuntime(chart.axesDesign?.x),
|
|
23037
|
-
},
|
|
23038
|
-
};
|
|
23039
|
-
const yAxis = {
|
|
23135
|
+
config.options.indexAxis = chart.horizontal ? "y" : "x";
|
|
23136
|
+
config.options.scales = {};
|
|
23137
|
+
const labelsAxis = { ticks: { padding: 5, color: fontColor } };
|
|
23138
|
+
const valuesAxis = {
|
|
23040
23139
|
beginAtZero: true, // the origin of the y axis is always zero
|
|
23041
23140
|
ticks: {
|
|
23042
23141
|
color: fontColor,
|
|
@@ -23052,7 +23151,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
23052
23151
|
},
|
|
23053
23152
|
},
|
|
23054
23153
|
};
|
|
23154
|
+
const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
|
|
23155
|
+
const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
|
|
23055
23156
|
const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
|
|
23157
|
+
config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
|
|
23056
23158
|
if (useLeftAxis) {
|
|
23057
23159
|
config.options.scales.y = {
|
|
23058
23160
|
...yAxis,
|
|
@@ -23119,7 +23221,7 @@ function createBarChartRuntime(chart, getters) {
|
|
|
23119
23221
|
const label = definition.dataSets[index].label;
|
|
23120
23222
|
dataset.label = label;
|
|
23121
23223
|
}
|
|
23122
|
-
if (definition.dataSets?.[index]?.yAxisId) {
|
|
23224
|
+
if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
|
|
23123
23225
|
dataset["yAxisID"] = definition.dataSets[index].yAxisId;
|
|
23124
23226
|
}
|
|
23125
23227
|
}
|
|
@@ -24110,6 +24212,7 @@ class PieChart extends AbstractChart {
|
|
|
24110
24212
|
type = "pie";
|
|
24111
24213
|
aggregated;
|
|
24112
24214
|
dataSetsHaveTitle;
|
|
24215
|
+
isDoughnut;
|
|
24113
24216
|
constructor(definition, sheetId, getters) {
|
|
24114
24217
|
super(definition, sheetId, getters);
|
|
24115
24218
|
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
@@ -24118,6 +24221,7 @@ class PieChart extends AbstractChart {
|
|
|
24118
24221
|
this.legendPosition = definition.legendPosition;
|
|
24119
24222
|
this.aggregated = definition.aggregated;
|
|
24120
24223
|
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
24224
|
+
this.isDoughnut = definition.isDoughnut;
|
|
24121
24225
|
}
|
|
24122
24226
|
static transformDefinition(definition, executed) {
|
|
24123
24227
|
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
@@ -24135,6 +24239,7 @@ class PieChart extends AbstractChart {
|
|
|
24135
24239
|
type: "pie",
|
|
24136
24240
|
labelRange: context.auxiliaryRange || undefined,
|
|
24137
24241
|
aggregated: context.aggregated ?? false,
|
|
24242
|
+
isDoughnut: false,
|
|
24138
24243
|
};
|
|
24139
24244
|
}
|
|
24140
24245
|
getDefinition() {
|
|
@@ -24165,6 +24270,7 @@ class PieChart extends AbstractChart {
|
|
|
24165
24270
|
: undefined,
|
|
24166
24271
|
title: this.title,
|
|
24167
24272
|
aggregated: this.aggregated,
|
|
24273
|
+
isDoughnut: this.isDoughnut,
|
|
24168
24274
|
};
|
|
24169
24275
|
}
|
|
24170
24276
|
copyForSheetId(sheetId) {
|
|
@@ -24299,6 +24405,141 @@ function createPieChartRuntime(chart, getters) {
|
|
|
24299
24405
|
};
|
|
24300
24406
|
config.data.datasets.push(dataset);
|
|
24301
24407
|
}
|
|
24408
|
+
if (chart.isDoughnut) {
|
|
24409
|
+
config.type = "doughnut";
|
|
24410
|
+
}
|
|
24411
|
+
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
24412
|
+
}
|
|
24413
|
+
|
|
24414
|
+
class PyramidChart extends AbstractChart {
|
|
24415
|
+
dataSets;
|
|
24416
|
+
labelRange;
|
|
24417
|
+
background;
|
|
24418
|
+
legendPosition;
|
|
24419
|
+
aggregated;
|
|
24420
|
+
type = "pyramid";
|
|
24421
|
+
dataSetsHaveTitle;
|
|
24422
|
+
dataSetDesign;
|
|
24423
|
+
axesDesign;
|
|
24424
|
+
horizontal = true;
|
|
24425
|
+
stacked = true;
|
|
24426
|
+
constructor(definition, sheetId, getters) {
|
|
24427
|
+
super(definition, sheetId, getters);
|
|
24428
|
+
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
|
|
24429
|
+
this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
|
|
24430
|
+
this.background = definition.background;
|
|
24431
|
+
this.legendPosition = definition.legendPosition;
|
|
24432
|
+
this.aggregated = definition.aggregated;
|
|
24433
|
+
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
24434
|
+
this.dataSetDesign = definition.dataSets;
|
|
24435
|
+
this.axesDesign = definition.axesDesign;
|
|
24436
|
+
}
|
|
24437
|
+
static transformDefinition(definition, executed) {
|
|
24438
|
+
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
24439
|
+
}
|
|
24440
|
+
static validateChartDefinition(validator, definition) {
|
|
24441
|
+
return validator.checkValidations(definition, checkDataset, checkLabelRange);
|
|
24442
|
+
}
|
|
24443
|
+
static getDefinitionFromContextCreation(context) {
|
|
24444
|
+
return {
|
|
24445
|
+
background: context.background,
|
|
24446
|
+
dataSets: context.range ?? [],
|
|
24447
|
+
dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
|
|
24448
|
+
aggregated: context.aggregated ?? false,
|
|
24449
|
+
legendPosition: context.legendPosition ?? "top",
|
|
24450
|
+
title: context.title || { text: "" },
|
|
24451
|
+
type: "pyramid",
|
|
24452
|
+
labelRange: context.auxiliaryRange || undefined,
|
|
24453
|
+
axesDesign: context.axesDesign,
|
|
24454
|
+
horizontal: true,
|
|
24455
|
+
stacked: true,
|
|
24456
|
+
};
|
|
24457
|
+
}
|
|
24458
|
+
getContextCreation() {
|
|
24459
|
+
const range = [];
|
|
24460
|
+
for (const [i, dataSet] of this.dataSets.entries()) {
|
|
24461
|
+
range.push({
|
|
24462
|
+
...this.dataSetDesign?.[i],
|
|
24463
|
+
dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
|
|
24464
|
+
});
|
|
24465
|
+
}
|
|
24466
|
+
return {
|
|
24467
|
+
...this,
|
|
24468
|
+
range,
|
|
24469
|
+
auxiliaryRange: this.labelRange
|
|
24470
|
+
? this.getters.getRangeString(this.labelRange, this.sheetId)
|
|
24471
|
+
: undefined,
|
|
24472
|
+
};
|
|
24473
|
+
}
|
|
24474
|
+
copyForSheetId(sheetId) {
|
|
24475
|
+
const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
|
|
24476
|
+
const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
|
|
24477
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
|
|
24478
|
+
return new PyramidChart(definition, sheetId, this.getters);
|
|
24479
|
+
}
|
|
24480
|
+
copyInSheetId(sheetId) {
|
|
24481
|
+
const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
|
|
24482
|
+
return new PyramidChart(definition, sheetId, this.getters);
|
|
24483
|
+
}
|
|
24484
|
+
getDefinition() {
|
|
24485
|
+
return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
|
|
24486
|
+
}
|
|
24487
|
+
getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
|
|
24488
|
+
const ranges = [];
|
|
24489
|
+
for (const [i, dataSet] of dataSets.entries()) {
|
|
24490
|
+
ranges.push({
|
|
24491
|
+
...this.dataSetDesign?.[i],
|
|
24492
|
+
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
|
|
24493
|
+
});
|
|
24494
|
+
}
|
|
24495
|
+
return {
|
|
24496
|
+
type: "pyramid",
|
|
24497
|
+
dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
|
|
24498
|
+
background: this.background,
|
|
24499
|
+
dataSets: ranges,
|
|
24500
|
+
legendPosition: this.legendPosition,
|
|
24501
|
+
labelRange: labelRange
|
|
24502
|
+
? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
|
|
24503
|
+
: undefined,
|
|
24504
|
+
title: this.title,
|
|
24505
|
+
aggregated: this.aggregated,
|
|
24506
|
+
axesDesign: this.axesDesign,
|
|
24507
|
+
horizontal: true,
|
|
24508
|
+
stacked: true,
|
|
24509
|
+
};
|
|
24510
|
+
}
|
|
24511
|
+
getDefinitionForExcel() {
|
|
24512
|
+
return undefined;
|
|
24513
|
+
}
|
|
24514
|
+
updateRanges(applyChange) {
|
|
24515
|
+
const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
|
|
24516
|
+
if (!isStale) {
|
|
24517
|
+
return this;
|
|
24518
|
+
}
|
|
24519
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
|
|
24520
|
+
return new PyramidChart(definition, this.sheetId, this.getters);
|
|
24521
|
+
}
|
|
24522
|
+
}
|
|
24523
|
+
function createPyramidChartRuntime(chart, getters) {
|
|
24524
|
+
const barDef = { ...chart.getDefinition(), type: "bar" };
|
|
24525
|
+
const barChart = new BarChart(barDef, chart.sheetId, getters);
|
|
24526
|
+
const barRuntime = createBarChartRuntime(barChart, getters);
|
|
24527
|
+
const config = barRuntime.chartJsConfig;
|
|
24528
|
+
let datasets = config.data?.datasets;
|
|
24529
|
+
if (datasets && datasets[0]) {
|
|
24530
|
+
datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
|
|
24531
|
+
}
|
|
24532
|
+
if (datasets && datasets[1]) {
|
|
24533
|
+
datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
|
|
24534
|
+
}
|
|
24535
|
+
const scales = config.options.scales;
|
|
24536
|
+
const scalesXCallback = scales.x.ticks.callback;
|
|
24537
|
+
scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
|
|
24538
|
+
const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
|
|
24539
|
+
config.options.plugins.tooltip.callbacks.label = (item) => {
|
|
24540
|
+
const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
|
|
24541
|
+
return tooltipLabelCallback(tooltipItem);
|
|
24542
|
+
};
|
|
24302
24543
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
24303
24544
|
}
|
|
24304
24545
|
|
|
@@ -24741,7 +24982,6 @@ chartRegistry.add("bar", {
|
|
|
24741
24982
|
validateChartDefinition: BarChart.validateChartDefinition,
|
|
24742
24983
|
transformDefinition: BarChart.transformDefinition,
|
|
24743
24984
|
getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
|
|
24744
|
-
name: _t("Bar"),
|
|
24745
24985
|
sequence: 10,
|
|
24746
24986
|
});
|
|
24747
24987
|
chartRegistry.add("combo", {
|
|
@@ -24751,7 +24991,6 @@ chartRegistry.add("combo", {
|
|
|
24751
24991
|
validateChartDefinition: ComboChart.validateChartDefinition,
|
|
24752
24992
|
transformDefinition: ComboChart.transformDefinition,
|
|
24753
24993
|
getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
|
|
24754
|
-
name: _t("Combo"),
|
|
24755
24994
|
sequence: 15,
|
|
24756
24995
|
});
|
|
24757
24996
|
chartRegistry.add("line", {
|
|
@@ -24761,7 +25000,6 @@ chartRegistry.add("line", {
|
|
|
24761
25000
|
validateChartDefinition: LineChart.validateChartDefinition,
|
|
24762
25001
|
transformDefinition: LineChart.transformDefinition,
|
|
24763
25002
|
getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
|
|
24764
|
-
name: _t("Line"),
|
|
24765
25003
|
sequence: 20,
|
|
24766
25004
|
});
|
|
24767
25005
|
chartRegistry.add("pie", {
|
|
@@ -24771,7 +25009,6 @@ chartRegistry.add("pie", {
|
|
|
24771
25009
|
validateChartDefinition: PieChart.validateChartDefinition,
|
|
24772
25010
|
transformDefinition: PieChart.transformDefinition,
|
|
24773
25011
|
getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
|
|
24774
|
-
name: _t("Pie"),
|
|
24775
25012
|
sequence: 30,
|
|
24776
25013
|
});
|
|
24777
25014
|
chartRegistry.add("scorecard", {
|
|
@@ -24781,7 +25018,6 @@ chartRegistry.add("scorecard", {
|
|
|
24781
25018
|
validateChartDefinition: ScorecardChart$1.validateChartDefinition,
|
|
24782
25019
|
transformDefinition: ScorecardChart$1.transformDefinition,
|
|
24783
25020
|
getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
|
|
24784
|
-
name: _t("Scorecard"),
|
|
24785
25021
|
sequence: 40,
|
|
24786
25022
|
});
|
|
24787
25023
|
chartRegistry.add("gauge", {
|
|
@@ -24791,7 +25027,6 @@ chartRegistry.add("gauge", {
|
|
|
24791
25027
|
validateChartDefinition: GaugeChart.validateChartDefinition,
|
|
24792
25028
|
transformDefinition: GaugeChart.transformDefinition,
|
|
24793
25029
|
getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
|
|
24794
|
-
name: _t("Gauge"),
|
|
24795
25030
|
sequence: 50,
|
|
24796
25031
|
});
|
|
24797
25032
|
chartRegistry.add("scatter", {
|
|
@@ -24801,7 +25036,6 @@ chartRegistry.add("scatter", {
|
|
|
24801
25036
|
validateChartDefinition: ScatterChart.validateChartDefinition,
|
|
24802
25037
|
transformDefinition: ScatterChart.transformDefinition,
|
|
24803
25038
|
getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
|
|
24804
|
-
name: _t("Scatter"),
|
|
24805
25039
|
sequence: 60,
|
|
24806
25040
|
});
|
|
24807
25041
|
chartRegistry.add("waterfall", {
|
|
@@ -24811,9 +25045,17 @@ chartRegistry.add("waterfall", {
|
|
|
24811
25045
|
validateChartDefinition: WaterfallChart.validateChartDefinition,
|
|
24812
25046
|
transformDefinition: WaterfallChart.transformDefinition,
|
|
24813
25047
|
getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
|
|
24814
|
-
name: _t("Waterfall"),
|
|
24815
25048
|
sequence: 70,
|
|
24816
25049
|
});
|
|
25050
|
+
chartRegistry.add("pyramid", {
|
|
25051
|
+
match: (type) => type === "pyramid",
|
|
25052
|
+
createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
|
|
25053
|
+
getChartRuntime: createPyramidChartRuntime,
|
|
25054
|
+
validateChartDefinition: PyramidChart.validateChartDefinition,
|
|
25055
|
+
transformDefinition: PyramidChart.transformDefinition,
|
|
25056
|
+
getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
|
|
25057
|
+
sequence: 80,
|
|
25058
|
+
});
|
|
24817
25059
|
const chartComponentRegistry = new Registry();
|
|
24818
25060
|
chartComponentRegistry.add("line", ChartJsComponent);
|
|
24819
25061
|
chartComponentRegistry.add("bar", ChartJsComponent);
|
|
@@ -24823,6 +25065,130 @@ chartComponentRegistry.add("gauge", GaugeChartComponent);
|
|
|
24823
25065
|
chartComponentRegistry.add("scatter", ChartJsComponent);
|
|
24824
25066
|
chartComponentRegistry.add("scorecard", ScorecardChart);
|
|
24825
25067
|
chartComponentRegistry.add("waterfall", ChartJsComponent);
|
|
25068
|
+
chartComponentRegistry.add("pyramid", ChartJsComponent);
|
|
25069
|
+
const chartCategories = {
|
|
25070
|
+
line: _t("Line"),
|
|
25071
|
+
column: _t("Column"),
|
|
25072
|
+
bar: _t("Bar"),
|
|
25073
|
+
pie: _t("Pie"),
|
|
25074
|
+
misc: _t("Miscellaneous"),
|
|
25075
|
+
};
|
|
25076
|
+
const chartSubtypeRegistry = new Registry();
|
|
25077
|
+
chartSubtypeRegistry
|
|
25078
|
+
.add("line", {
|
|
25079
|
+
matcher: (definition) => definition.type === "line" && !definition.stacked,
|
|
25080
|
+
displayName: _t("Line"),
|
|
25081
|
+
chartType: "line",
|
|
25082
|
+
chartSubtype: "line",
|
|
25083
|
+
subtypeDefinition: { stacked: false },
|
|
25084
|
+
category: "line",
|
|
25085
|
+
preview: "o-spreadsheet-ChartPreview.LINE_CHART",
|
|
25086
|
+
})
|
|
25087
|
+
.add("stacked_line", {
|
|
25088
|
+
matcher: (definition) => definition.type === "line" && definition.stacked,
|
|
25089
|
+
displayName: _t("Stacked Line"),
|
|
25090
|
+
chartType: "line",
|
|
25091
|
+
chartSubtype: "stacked_line",
|
|
25092
|
+
subtypeDefinition: { stacked: true },
|
|
25093
|
+
category: "line",
|
|
25094
|
+
preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
|
|
25095
|
+
})
|
|
25096
|
+
.add("scatter", {
|
|
25097
|
+
displayName: _t("Scatter"),
|
|
25098
|
+
chartType: "scatter",
|
|
25099
|
+
chartSubtype: "scatter",
|
|
25100
|
+
category: "misc",
|
|
25101
|
+
preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
|
|
25102
|
+
})
|
|
25103
|
+
.add("column", {
|
|
25104
|
+
matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
|
|
25105
|
+
displayName: _t("Column"),
|
|
25106
|
+
chartType: "bar",
|
|
25107
|
+
chartSubtype: "column",
|
|
25108
|
+
subtypeDefinition: { stacked: false, horizontal: false },
|
|
25109
|
+
category: "column",
|
|
25110
|
+
preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
|
|
25111
|
+
})
|
|
25112
|
+
.add("stacked_column", {
|
|
25113
|
+
matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
|
|
25114
|
+
displayName: _t("Stacked Column"),
|
|
25115
|
+
chartType: "bar",
|
|
25116
|
+
chartSubtype: "stacked_column",
|
|
25117
|
+
subtypeDefinition: { stacked: true, horizontal: false },
|
|
25118
|
+
category: "column",
|
|
25119
|
+
preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
|
|
25120
|
+
})
|
|
25121
|
+
.add("bar", {
|
|
25122
|
+
matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
|
|
25123
|
+
displayName: _t("Bar"),
|
|
25124
|
+
chartType: "bar",
|
|
25125
|
+
chartSubtype: "bar",
|
|
25126
|
+
subtypeDefinition: { horizontal: true, stacked: false },
|
|
25127
|
+
category: "bar",
|
|
25128
|
+
preview: "o-spreadsheet-ChartPreview.BAR_CHART",
|
|
25129
|
+
})
|
|
25130
|
+
.add("stacked_bar", {
|
|
25131
|
+
matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
|
|
25132
|
+
displayName: _t("Stacked Bar"),
|
|
25133
|
+
chartType: "bar",
|
|
25134
|
+
chartSubtype: "stacked_bar",
|
|
25135
|
+
subtypeDefinition: { horizontal: true, stacked: true },
|
|
25136
|
+
category: "bar",
|
|
25137
|
+
preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
|
|
25138
|
+
})
|
|
25139
|
+
.add("combo", {
|
|
25140
|
+
displayName: _t("Combo"),
|
|
25141
|
+
chartSubtype: "combo",
|
|
25142
|
+
chartType: "combo",
|
|
25143
|
+
category: "line",
|
|
25144
|
+
preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
|
|
25145
|
+
})
|
|
25146
|
+
.add("pie", {
|
|
25147
|
+
matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
|
|
25148
|
+
displayName: _t("Pie"),
|
|
25149
|
+
chartSubtype: "pie",
|
|
25150
|
+
chartType: "pie",
|
|
25151
|
+
subtypeDefinition: { isDoughnut: false },
|
|
25152
|
+
category: "pie",
|
|
25153
|
+
preview: "o-spreadsheet-ChartPreview.PIE_CHART",
|
|
25154
|
+
})
|
|
25155
|
+
.add("doughnut", {
|
|
25156
|
+
matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
|
|
25157
|
+
displayName: _t("Doughnut"),
|
|
25158
|
+
chartSubtype: "doughnut",
|
|
25159
|
+
chartType: "pie",
|
|
25160
|
+
subtypeDefinition: { isDoughnut: true },
|
|
25161
|
+
category: "pie",
|
|
25162
|
+
preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
|
|
25163
|
+
})
|
|
25164
|
+
.add("gauge", {
|
|
25165
|
+
displayName: _t("Gauge"),
|
|
25166
|
+
chartSubtype: "gauge",
|
|
25167
|
+
chartType: "gauge",
|
|
25168
|
+
category: "misc",
|
|
25169
|
+
preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
|
|
25170
|
+
})
|
|
25171
|
+
.add("scorecard", {
|
|
25172
|
+
displayName: _t("Scorecard"),
|
|
25173
|
+
chartSubtype: "scorecard",
|
|
25174
|
+
chartType: "scorecard",
|
|
25175
|
+
category: "misc",
|
|
25176
|
+
preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
|
|
25177
|
+
})
|
|
25178
|
+
.add("waterfall", {
|
|
25179
|
+
displayName: _t("Waterfall"),
|
|
25180
|
+
chartSubtype: "waterfall",
|
|
25181
|
+
chartType: "waterfall",
|
|
25182
|
+
category: "misc",
|
|
25183
|
+
preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
|
|
25184
|
+
})
|
|
25185
|
+
.add("pyramid", {
|
|
25186
|
+
displayName: _t("Population Pyramid"),
|
|
25187
|
+
chartSubtype: "pyramid",
|
|
25188
|
+
chartType: "pyramid",
|
|
25189
|
+
category: "misc",
|
|
25190
|
+
preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
|
|
25191
|
+
});
|
|
24826
25192
|
|
|
24827
25193
|
/**
|
|
24828
25194
|
* Registry intended to support usual currencies. It is mainly used to create
|
|
@@ -26679,20 +27045,6 @@ function transformDefinition(definition, executed) {
|
|
|
26679
27045
|
}
|
|
26680
27046
|
return transformation.transformDefinition(definition, executed);
|
|
26681
27047
|
}
|
|
26682
|
-
/**
|
|
26683
|
-
* Get an empty definition based on the given context and the given type
|
|
26684
|
-
*/
|
|
26685
|
-
function getChartDefinitionFromContextCreation(context, type) {
|
|
26686
|
-
const chartClass = chartRegistry.get(type);
|
|
26687
|
-
return chartClass.getChartDefinitionFromContextCreation(context);
|
|
26688
|
-
}
|
|
26689
|
-
function getChartTypes() {
|
|
26690
|
-
const result = {};
|
|
26691
|
-
for (const key of chartRegistry.getKeys()) {
|
|
26692
|
-
result[key] = chartRegistry.get(key).name;
|
|
26693
|
-
}
|
|
26694
|
-
return result;
|
|
26695
|
-
}
|
|
26696
27048
|
/**
|
|
26697
27049
|
* Return a "smart" chart definition in the given zone. The definition is "smart" because it will
|
|
26698
27050
|
* use the best type of chart to display the data of the zone.
|
|
@@ -28152,6 +28504,40 @@ const pivotProperties = {
|
|
|
28152
28504
|
},
|
|
28153
28505
|
icon: "o-spreadsheet-Icon.PIVOT",
|
|
28154
28506
|
};
|
|
28507
|
+
const FIX_FORMULAS = {
|
|
28508
|
+
name: _t("Convert to individual formulas"),
|
|
28509
|
+
execute(env) {
|
|
28510
|
+
const position = env.model.getters.getActivePosition();
|
|
28511
|
+
const cell = env.model.getters.getCorrespondingFormulaCell(position);
|
|
28512
|
+
const pivotId = env.model.getters.getPivotIdFromPosition(position);
|
|
28513
|
+
if (!cell || !pivotId) {
|
|
28514
|
+
return;
|
|
28515
|
+
}
|
|
28516
|
+
const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
|
|
28517
|
+
const pivot = env.model.getters.getPivot(pivotId);
|
|
28518
|
+
pivot.init();
|
|
28519
|
+
if (!pivot.isValid()) {
|
|
28520
|
+
return;
|
|
28521
|
+
}
|
|
28522
|
+
env.model.dispatch("INSERT_PIVOT", {
|
|
28523
|
+
sheetId,
|
|
28524
|
+
col,
|
|
28525
|
+
row,
|
|
28526
|
+
pivotId,
|
|
28527
|
+
table: pivot.getTableStructure().export(),
|
|
28528
|
+
});
|
|
28529
|
+
},
|
|
28530
|
+
isVisible: (env) => {
|
|
28531
|
+
const position = env.model.getters.getActivePosition();
|
|
28532
|
+
const pivotId = env.model.getters.getPivotIdFromPosition(position);
|
|
28533
|
+
if (!pivotId) {
|
|
28534
|
+
return false;
|
|
28535
|
+
}
|
|
28536
|
+
const pivot = env.model.getters.getPivot(pivotId);
|
|
28537
|
+
return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
|
|
28538
|
+
},
|
|
28539
|
+
icon: "o-spreadsheet-Icon.PIVOT",
|
|
28540
|
+
};
|
|
28155
28541
|
|
|
28156
28542
|
//------------------------------------------------------------------------------
|
|
28157
28543
|
// Context Menu Registry
|
|
@@ -28250,6 +28636,10 @@ cellMenuRegistry
|
|
|
28250
28636
|
name: INSERT_LINK_NAME,
|
|
28251
28637
|
sequence: 150,
|
|
28252
28638
|
separator: true,
|
|
28639
|
+
})
|
|
28640
|
+
.add("pivot_fix_formulas", {
|
|
28641
|
+
...FIX_FORMULAS,
|
|
28642
|
+
sequence: 155,
|
|
28253
28643
|
})
|
|
28254
28644
|
.add("pivot_properties", {
|
|
28255
28645
|
...pivotProperties,
|
|
@@ -30472,6 +30862,7 @@ class GenericChartConfigPanel extends Component {
|
|
|
30472
30862
|
});
|
|
30473
30863
|
dataSeriesRanges = [];
|
|
30474
30864
|
labelRange;
|
|
30865
|
+
chartTerms = ChartTerms;
|
|
30475
30866
|
setup() {
|
|
30476
30867
|
this.dataSeriesRanges = this.props.definition.dataSets;
|
|
30477
30868
|
this.labelRange = this.props.definition.labelRange;
|
|
@@ -30496,7 +30887,7 @@ class GenericChartConfigPanel extends Component {
|
|
|
30496
30887
|
return [
|
|
30497
30888
|
{
|
|
30498
30889
|
name: "aggregated",
|
|
30499
|
-
label:
|
|
30890
|
+
label: this.chartTerms.AggregatedChart,
|
|
30500
30891
|
value: this.props.definition.aggregated ?? false,
|
|
30501
30892
|
onChange: this.onUpdateAggregated.bind(this),
|
|
30502
30893
|
},
|
|
@@ -30572,9 +30963,6 @@ class GenericChartConfigPanel extends Component {
|
|
|
30572
30963
|
|
|
30573
30964
|
class BarConfigPanel extends GenericChartConfigPanel {
|
|
30574
30965
|
static template = "o-spreadsheet-BarConfigPanel";
|
|
30575
|
-
get stackedLabel() {
|
|
30576
|
-
return _t("Stacked barchart");
|
|
30577
|
-
}
|
|
30578
30966
|
onUpdateStacked(stacked) {
|
|
30579
30967
|
this.props.updateChart(this.props.figureId, {
|
|
30580
30968
|
stacked,
|
|
@@ -31564,6 +31952,9 @@ class ChartWithAxisDesignPanel extends Component {
|
|
|
31564
31952
|
return "left";
|
|
31565
31953
|
return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
|
|
31566
31954
|
}
|
|
31955
|
+
get canHaveTwoVerticalAxis() {
|
|
31956
|
+
return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
|
|
31957
|
+
}
|
|
31567
31958
|
updateDataSeriesLabel(ev) {
|
|
31568
31959
|
const label = ev.target.value;
|
|
31569
31960
|
const dataSets = this.props.definition.dataSets;
|
|
@@ -31678,14 +32069,6 @@ class GaugeChartDesignPanel extends Component {
|
|
|
31678
32069
|
const cancelledReasons = [...(this.state.sectionRuleDispatchResult?.reasons || [])];
|
|
31679
32070
|
return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
|
|
31680
32071
|
}
|
|
31681
|
-
updateBackgroundColor(color) {
|
|
31682
|
-
this.props.updateChart(this.props.figureId, {
|
|
31683
|
-
background: color,
|
|
31684
|
-
});
|
|
31685
|
-
}
|
|
31686
|
-
updateTitle(content) {
|
|
31687
|
-
this.props.updateChart(this.props.figureId, { title: { text: content } });
|
|
31688
|
-
}
|
|
31689
32072
|
isRangeMinInvalid() {
|
|
31690
32073
|
return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause("EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */) ||
|
|
31691
32074
|
this.state.sectionRuleDispatchResult?.isCancelledBecause("GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */) ||
|
|
@@ -31725,9 +32108,6 @@ class GaugeChartDesignPanel extends Component {
|
|
|
31725
32108
|
sectionRule,
|
|
31726
32109
|
});
|
|
31727
32110
|
}
|
|
31728
|
-
get backgroundColorTitle() {
|
|
31729
|
-
return ChartTerms.BackgroundColor;
|
|
31730
|
-
}
|
|
31731
32111
|
}
|
|
31732
32112
|
|
|
31733
32113
|
class LineConfigPanel extends GenericChartConfigPanel {
|
|
@@ -31739,19 +32119,13 @@ class LineConfigPanel extends GenericChartConfigPanel {
|
|
|
31739
32119
|
}
|
|
31740
32120
|
return false;
|
|
31741
32121
|
}
|
|
31742
|
-
get stackedLabel() {
|
|
31743
|
-
return _t("Stacked linechart");
|
|
31744
|
-
}
|
|
31745
|
-
get cumulativeLabel() {
|
|
31746
|
-
return _t("Cumulative data");
|
|
31747
|
-
}
|
|
31748
32122
|
getLabelRangeOptions() {
|
|
31749
32123
|
const options = super.getLabelRangeOptions();
|
|
31750
32124
|
if (this.canTreatLabelsAsText) {
|
|
31751
32125
|
options.push({
|
|
31752
32126
|
name: "labelsAsText",
|
|
31753
32127
|
value: this.props.definition.labelsAsText,
|
|
31754
|
-
label:
|
|
32128
|
+
label: this.chartTerms.TreatLabelsAsText,
|
|
31755
32129
|
onChange: this.onUpdateLabelsAsText.bind(this),
|
|
31756
32130
|
});
|
|
31757
32131
|
}
|
|
@@ -31818,7 +32192,7 @@ class ScatterConfigPanel extends GenericChartConfigPanel {
|
|
|
31818
32192
|
options.push({
|
|
31819
32193
|
name: "labelsAsText",
|
|
31820
32194
|
value: this.props.definition.labelsAsText,
|
|
31821
|
-
label:
|
|
32195
|
+
label: this.chartTerms.TreatLabelsAsText,
|
|
31822
32196
|
onChange: this.onUpdateLabelsAsText.bind(this),
|
|
31823
32197
|
});
|
|
31824
32198
|
}
|
|
@@ -31910,9 +32284,6 @@ class ScorecardChartDesignPanel extends Component {
|
|
|
31910
32284
|
get humanizeNumbersLabel() {
|
|
31911
32285
|
return _t("Humanize numbers");
|
|
31912
32286
|
}
|
|
31913
|
-
updateTitle(content) {
|
|
31914
|
-
this.props.updateChart(this.props.figureId, { title: { text: content } });
|
|
31915
|
-
}
|
|
31916
32287
|
updateHumanizeNumbers(humanize) {
|
|
31917
32288
|
this.props.updateChart(this.props.figureId, { humanize });
|
|
31918
32289
|
}
|
|
@@ -31935,9 +32306,6 @@ class ScorecardChartDesignPanel extends Component {
|
|
|
31935
32306
|
break;
|
|
31936
32307
|
}
|
|
31937
32308
|
}
|
|
31938
|
-
get backgroundColorTitle() {
|
|
31939
|
-
return ChartTerms.BackgroundColor;
|
|
31940
|
-
}
|
|
31941
32309
|
}
|
|
31942
32310
|
|
|
31943
32311
|
class WaterfallChartDesignPanel extends Component {
|
|
@@ -32031,8 +32399,106 @@ chartSidePanelComponentRegistry
|
|
|
32031
32399
|
.add("waterfall", {
|
|
32032
32400
|
configuration: GenericChartConfigPanel,
|
|
32033
32401
|
design: WaterfallChartDesignPanel,
|
|
32402
|
+
})
|
|
32403
|
+
.add("pyramid", {
|
|
32404
|
+
configuration: GenericChartConfigPanel,
|
|
32405
|
+
design: ChartWithAxisDesignPanel,
|
|
32034
32406
|
});
|
|
32035
32407
|
|
|
32408
|
+
css /* scss */ `
|
|
32409
|
+
.o-section .o-type-selector {
|
|
32410
|
+
height: 30px;
|
|
32411
|
+
padding-left: 30px;
|
|
32412
|
+
}
|
|
32413
|
+
.o-type-selector-preview {
|
|
32414
|
+
left: 5px;
|
|
32415
|
+
top: 3px;
|
|
32416
|
+
.o-chart-preview {
|
|
32417
|
+
width: 24px;
|
|
32418
|
+
height: 24px;
|
|
32419
|
+
}
|
|
32420
|
+
}
|
|
32421
|
+
|
|
32422
|
+
.o-popover .o-chart-select-popover {
|
|
32423
|
+
box-sizing: border-box;
|
|
32424
|
+
background: #fff;
|
|
32425
|
+
.o-chart-type-item {
|
|
32426
|
+
cursor: pointer;
|
|
32427
|
+
padding: 3px 6px;
|
|
32428
|
+
margin: 1px 2px;
|
|
32429
|
+
&.selected,
|
|
32430
|
+
&:hover {
|
|
32431
|
+
background: #f5f5f5;
|
|
32432
|
+
border: 1px solid #ccc;
|
|
32433
|
+
padding: 2px 5px;
|
|
32434
|
+
}
|
|
32435
|
+
.o-chart-preview {
|
|
32436
|
+
width: 48px;
|
|
32437
|
+
height: 48px;
|
|
32438
|
+
}
|
|
32439
|
+
}
|
|
32440
|
+
}
|
|
32441
|
+
`;
|
|
32442
|
+
class ChartTypePicker extends Component {
|
|
32443
|
+
static template = "o-spreadsheet-ChartTypePicker";
|
|
32444
|
+
static components = { Section, Popover };
|
|
32445
|
+
static props = { figureId: String, chartPanelStore: Object };
|
|
32446
|
+
categories = chartCategories;
|
|
32447
|
+
chartTypeByCategories = {};
|
|
32448
|
+
popoverRef = useRef("popoverRef");
|
|
32449
|
+
selectRef = useRef("selectRef");
|
|
32450
|
+
state = useState({ popoverProps: undefined, popoverStyle: "" });
|
|
32451
|
+
setup() {
|
|
32452
|
+
useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
|
|
32453
|
+
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
32454
|
+
if (this.chartTypeByCategories[subtypeProperties.category]) {
|
|
32455
|
+
this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
|
|
32456
|
+
}
|
|
32457
|
+
else {
|
|
32458
|
+
this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
|
|
32459
|
+
}
|
|
32460
|
+
}
|
|
32461
|
+
}
|
|
32462
|
+
onExternalClick(ev) {
|
|
32463
|
+
if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
|
|
32464
|
+
isChildEvent(this.selectRef.el, ev)) {
|
|
32465
|
+
return;
|
|
32466
|
+
}
|
|
32467
|
+
this.closePopover();
|
|
32468
|
+
}
|
|
32469
|
+
onTypeChange(type) {
|
|
32470
|
+
this.props.chartPanelStore.changeChartType(this.props.figureId, type);
|
|
32471
|
+
this.closePopover();
|
|
32472
|
+
}
|
|
32473
|
+
getChartDefinition(figureId) {
|
|
32474
|
+
return this.env.model.getters.getChartDefinition(figureId);
|
|
32475
|
+
}
|
|
32476
|
+
getSelectedChartSubtypeProperties() {
|
|
32477
|
+
const definition = this.getChartDefinition(this.props.figureId);
|
|
32478
|
+
const matchedChart = chartSubtypeRegistry
|
|
32479
|
+
.getAll()
|
|
32480
|
+
.find((c) => c.matcher?.(definition) || false);
|
|
32481
|
+
return matchedChart || chartSubtypeRegistry.get(definition.type);
|
|
32482
|
+
}
|
|
32483
|
+
onPointerDown(ev) {
|
|
32484
|
+
if (this.state.popoverProps) {
|
|
32485
|
+
this.closePopover();
|
|
32486
|
+
return;
|
|
32487
|
+
}
|
|
32488
|
+
const target = ev.currentTarget;
|
|
32489
|
+
const { bottom, right, width } = target.getBoundingClientRect();
|
|
32490
|
+
this.state.popoverProps = {
|
|
32491
|
+
anchorRect: { x: right, y: bottom, width: 0, height: 0 },
|
|
32492
|
+
positioning: "TopRight",
|
|
32493
|
+
verticalOffset: 0,
|
|
32494
|
+
};
|
|
32495
|
+
this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
|
|
32496
|
+
}
|
|
32497
|
+
closePopover() {
|
|
32498
|
+
this.state.popoverProps = undefined;
|
|
32499
|
+
}
|
|
32500
|
+
}
|
|
32501
|
+
|
|
32036
32502
|
class MainChartPanelStore extends SpreadsheetStore {
|
|
32037
32503
|
mutators = ["activatePanel", "changeChartType"];
|
|
32038
32504
|
panel = "configuration";
|
|
@@ -32040,7 +32506,7 @@ class MainChartPanelStore extends SpreadsheetStore {
|
|
|
32040
32506
|
activatePanel(panel) {
|
|
32041
32507
|
this.panel = panel;
|
|
32042
32508
|
}
|
|
32043
|
-
changeChartType(figureId,
|
|
32509
|
+
changeChartType(figureId, newDisplayType) {
|
|
32044
32510
|
this.creationContext = {
|
|
32045
32511
|
...this.creationContext,
|
|
32046
32512
|
...this.getters.getContextCreationChart(figureId),
|
|
@@ -32049,13 +32515,25 @@ class MainChartPanelStore extends SpreadsheetStore {
|
|
|
32049
32515
|
if (!sheetId) {
|
|
32050
32516
|
return;
|
|
32051
32517
|
}
|
|
32052
|
-
const definition = getChartDefinitionFromContextCreation(
|
|
32518
|
+
const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
|
|
32053
32519
|
this.model.dispatch("UPDATE_CHART", {
|
|
32054
32520
|
definition,
|
|
32055
32521
|
id: figureId,
|
|
32056
32522
|
sheetId,
|
|
32057
32523
|
});
|
|
32058
32524
|
}
|
|
32525
|
+
getChartDefinitionFromContextCreation(figureId, newDisplayType) {
|
|
32526
|
+
const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
|
|
32527
|
+
const ChartClass = chartRegistry.get(newChartInfo.chartType);
|
|
32528
|
+
const contextCreation = {
|
|
32529
|
+
...this.creationContext,
|
|
32530
|
+
...this.getters.getContextCreationChart(figureId),
|
|
32531
|
+
};
|
|
32532
|
+
return {
|
|
32533
|
+
...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
|
|
32534
|
+
...newChartInfo.subtypeDefinition,
|
|
32535
|
+
};
|
|
32536
|
+
}
|
|
32059
32537
|
}
|
|
32060
32538
|
|
|
32061
32539
|
css /* scss */ `
|
|
@@ -32084,7 +32562,7 @@ css /* scss */ `
|
|
|
32084
32562
|
`;
|
|
32085
32563
|
class ChartPanel extends Component {
|
|
32086
32564
|
static template = "o-spreadsheet-ChartPanel";
|
|
32087
|
-
static components = { Section };
|
|
32565
|
+
static components = { Section, ChartTypePicker };
|
|
32088
32566
|
static props = { onCloseSidePanel: Function, figureId: String };
|
|
32089
32567
|
store;
|
|
32090
32568
|
get figureId() {
|
|
@@ -32144,9 +32622,6 @@ class ChartPanel extends Component {
|
|
|
32144
32622
|
getChartDefinition(figureId) {
|
|
32145
32623
|
return this.env.model.getters.getChartDefinition(figureId);
|
|
32146
32624
|
}
|
|
32147
|
-
get chartTypes() {
|
|
32148
|
-
return getChartTypes();
|
|
32149
|
-
}
|
|
32150
32625
|
}
|
|
32151
32626
|
|
|
32152
32627
|
css /* scss */ `
|
|
@@ -35220,9 +35695,17 @@ class SpreadsheetPivotTable {
|
|
|
35220
35695
|
}
|
|
35221
35696
|
getPivotCell(col, row, includeTotal = true) {
|
|
35222
35697
|
const colHeadersHeight = this.columns.length;
|
|
35223
|
-
if (row
|
|
35698
|
+
if (col > 0 && row === colHeadersHeight - 1) {
|
|
35224
35699
|
const domain = this.getColHeaderDomain(col, row);
|
|
35225
|
-
|
|
35700
|
+
if (!domain) {
|
|
35701
|
+
return EMPTY_PIVOT_CELL;
|
|
35702
|
+
}
|
|
35703
|
+
const measure = domain.at(-1)?.value.toString() || "";
|
|
35704
|
+
return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
|
|
35705
|
+
}
|
|
35706
|
+
else if (row <= colHeadersHeight - 1) {
|
|
35707
|
+
const domain = this.getColHeaderDomain(col, row);
|
|
35708
|
+
return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
|
|
35226
35709
|
}
|
|
35227
35710
|
else if (col === 0) {
|
|
35228
35711
|
const rowIndex = row - colHeadersHeight;
|
|
@@ -35232,7 +35715,7 @@ class SpreadsheetPivotTable {
|
|
|
35232
35715
|
else {
|
|
35233
35716
|
const rowIndex = row - colHeadersHeight;
|
|
35234
35717
|
if (!includeTotal && this.isTotalRow(rowIndex)) {
|
|
35235
|
-
return
|
|
35718
|
+
return EMPTY_PIVOT_CELL;
|
|
35236
35719
|
}
|
|
35237
35720
|
const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
|
|
35238
35721
|
const measure = this.getColMeasure(col);
|
|
@@ -35286,6 +35769,7 @@ class SpreadsheetPivotTable {
|
|
|
35286
35769
|
};
|
|
35287
35770
|
}
|
|
35288
35771
|
}
|
|
35772
|
+
const EMPTY_PIVOT_CELL = { type: "EMPTY" };
|
|
35289
35773
|
|
|
35290
35774
|
/**
|
|
35291
35775
|
* This function converts a list of data entry into a spreadsheet pivot table.
|
|
@@ -35483,6 +35967,9 @@ function createDate(dimension, value, locale) {
|
|
|
35483
35967
|
if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
|
|
35484
35968
|
throw new Error(`Unknown date granularity: ${granularity}`);
|
|
35485
35969
|
}
|
|
35970
|
+
if (value === null) {
|
|
35971
|
+
return null;
|
|
35972
|
+
}
|
|
35486
35973
|
if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
|
|
35487
35974
|
MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
|
|
35488
35975
|
const date = toJsDate(value, locale);
|
|
@@ -35713,14 +36200,17 @@ class SpreadsheetPivot {
|
|
|
35713
36200
|
if (dimension.type === "date") {
|
|
35714
36201
|
const adapter = pivotTimeAdapter(dimension.granularity);
|
|
35715
36202
|
return {
|
|
35716
|
-
value:
|
|
36203
|
+
value: lastNode.value !== "null"
|
|
36204
|
+
? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
|
|
36205
|
+
: _t("(Undefined)"),
|
|
35717
36206
|
format: adapter.getFormat(this.getters.getLocale()),
|
|
35718
36207
|
};
|
|
35719
36208
|
}
|
|
35720
36209
|
if (!finalCell) {
|
|
35721
36210
|
return { value: "" };
|
|
35722
36211
|
}
|
|
35723
|
-
|
|
36212
|
+
// Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
|
|
36213
|
+
if (finalCell.value === null || finalCell.value === `${null}`) {
|
|
35724
36214
|
return { value: _t("(Undefined)") };
|
|
35725
36215
|
}
|
|
35726
36216
|
return {
|
|
@@ -35742,10 +36232,15 @@ class SpreadsheetPivot {
|
|
|
35742
36232
|
if (!operator) {
|
|
35743
36233
|
throw new Error(`Aggregator ${aggregator} does not exist`);
|
|
35744
36234
|
}
|
|
35745
|
-
|
|
35746
|
-
|
|
35747
|
-
|
|
35748
|
-
|
|
36235
|
+
try {
|
|
36236
|
+
return {
|
|
36237
|
+
value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
|
|
36238
|
+
format: operator.format(values[0]),
|
|
36239
|
+
};
|
|
36240
|
+
}
|
|
36241
|
+
catch (e) {
|
|
36242
|
+
return handleError(e, aggregator.toUpperCase());
|
|
36243
|
+
}
|
|
35749
36244
|
}
|
|
35750
36245
|
getPossibleFieldValues(dimension) {
|
|
35751
36246
|
const values = [];
|
|
@@ -38619,7 +39114,7 @@ class FiguresContainer extends Component {
|
|
|
38619
39114
|
});
|
|
38620
39115
|
}
|
|
38621
39116
|
getContainerRect(container) {
|
|
38622
|
-
const { width: viewWidth, height: viewHeight } = this.env.model.getters.
|
|
39117
|
+
const { width: viewWidth, height: viewHeight } = this.env.model.getters.getSheetViewDimension();
|
|
38623
39118
|
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
|
|
38624
39119
|
const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
|
|
38625
39120
|
const width = viewWidth - x;
|
|
@@ -51112,30 +51607,14 @@ class PivotCorePlugin extends CorePlugin {
|
|
|
51112
51607
|
for (let col = 0; col < pivotCells.length; col++) {
|
|
51113
51608
|
for (let row = 0; row < pivotCells[col].length; row++) {
|
|
51114
51609
|
const pivotCell = pivotCells[col][row];
|
|
51115
|
-
|
|
51610
|
+
this.dispatch("UPDATE_CELL", {
|
|
51116
51611
|
sheetId: position.sheetId,
|
|
51117
51612
|
col: position.col + col,
|
|
51118
51613
|
row: position.row + row,
|
|
51119
|
-
|
|
51120
|
-
|
|
51614
|
+
content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
|
|
51615
|
+
});
|
|
51121
51616
|
}
|
|
51122
51617
|
}
|
|
51123
|
-
const pivotZone = {
|
|
51124
|
-
top: position.row,
|
|
51125
|
-
bottom: position.row + pivotCells[0].length - 1,
|
|
51126
|
-
left: position.col,
|
|
51127
|
-
right: position.col + pivotCells.length - 1,
|
|
51128
|
-
};
|
|
51129
|
-
const numberOfHeaders = table.columns.length - 1;
|
|
51130
|
-
const cmdContent = {
|
|
51131
|
-
sheetId: position.sheetId,
|
|
51132
|
-
ranges: [this.getters.getRangeDataFromZone(position.sheetId, pivotZone)],
|
|
51133
|
-
config: { ...PIVOT_TABLE_CONFIG, numberOfHeaders },
|
|
51134
|
-
tableType: "static",
|
|
51135
|
-
};
|
|
51136
|
-
if (this.canDispatch("CREATE_TABLE", cmdContent).isSuccessful) {
|
|
51137
|
-
this.dispatch("CREATE_TABLE", cmdContent);
|
|
51138
|
-
}
|
|
51139
51618
|
}
|
|
51140
51619
|
resizeSheet(sheetId, { col, row }, table) {
|
|
51141
51620
|
const colLimit = table.getNumberOfDataColumns() + 1; // +1 for the Top-Left
|
|
@@ -51163,21 +51642,6 @@ class PivotCorePlugin extends CorePlugin {
|
|
|
51163
51642
|
});
|
|
51164
51643
|
}
|
|
51165
51644
|
}
|
|
51166
|
-
addPivotFormula(position, formulaId, pivotCell) {
|
|
51167
|
-
let content = undefined;
|
|
51168
|
-
switch (pivotCell.type) {
|
|
51169
|
-
case "HEADER":
|
|
51170
|
-
content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
51171
|
-
break;
|
|
51172
|
-
case "VALUE":
|
|
51173
|
-
content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
51174
|
-
break;
|
|
51175
|
-
}
|
|
51176
|
-
this.dispatch("UPDATE_CELL", {
|
|
51177
|
-
...position,
|
|
51178
|
-
content,
|
|
51179
|
-
});
|
|
51180
|
-
}
|
|
51181
51645
|
getPivotCore(pivotId) {
|
|
51182
51646
|
const pivot = this.pivots[pivotId];
|
|
51183
51647
|
if (!pivot) {
|
|
@@ -52842,7 +53306,6 @@ class Evaluator {
|
|
|
52842
53306
|
if (!this.blockedArrayFormulas.has(position)) {
|
|
52843
53307
|
this.invalidateSpreading(position);
|
|
52844
53308
|
}
|
|
52845
|
-
this.spreadingRelations.removeNode(position);
|
|
52846
53309
|
const cell = this.getters.getCell(position);
|
|
52847
53310
|
if (cell === undefined) {
|
|
52848
53311
|
return EMPTY_CELL;
|
|
@@ -52884,6 +53347,7 @@ class Evaluator {
|
|
|
52884
53347
|
this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
|
|
52885
53348
|
const nbColumns = formulaReturn.length;
|
|
52886
53349
|
const nbRows = formulaReturn[0].length;
|
|
53350
|
+
this.spreadingRelations.removeNode(formulaPosition);
|
|
52887
53351
|
forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
|
|
52888
53352
|
this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
|
|
52889
53353
|
forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
|
|
@@ -54411,9 +54875,10 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54411
54875
|
"getPivot",
|
|
54412
54876
|
"getFirstPivotFunction",
|
|
54413
54877
|
"getPivotIdFromPosition",
|
|
54414
|
-
"
|
|
54878
|
+
"getPivotCellFromPosition",
|
|
54415
54879
|
"isPivotUnused",
|
|
54416
54880
|
"areDomainArgsFieldsValid",
|
|
54881
|
+
"isSpillPivotFormula",
|
|
54417
54882
|
];
|
|
54418
54883
|
pivots = {};
|
|
54419
54884
|
unusedPivots;
|
|
@@ -54492,6 +54957,14 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54492
54957
|
}
|
|
54493
54958
|
return undefined;
|
|
54494
54959
|
}
|
|
54960
|
+
isSpillPivotFormula(position) {
|
|
54961
|
+
const cell = this.getters.getCorrespondingFormulaCell(position);
|
|
54962
|
+
if (cell && cell.isFormula) {
|
|
54963
|
+
const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
54964
|
+
return pivotFunction?.functionName === "PIVOT";
|
|
54965
|
+
}
|
|
54966
|
+
return false;
|
|
54967
|
+
}
|
|
54495
54968
|
getFirstPivotFunction(tokens) {
|
|
54496
54969
|
const pivotFunction = getFirstPivotFunction(tokens);
|
|
54497
54970
|
if (!pivotFunction) {
|
|
@@ -54525,29 +54998,29 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54525
54998
|
* If the cell is the result of PIVOT, the result is the domain of the cell
|
|
54526
54999
|
* as if it was the individual pivot formula
|
|
54527
55000
|
*/
|
|
54528
|
-
|
|
55001
|
+
getPivotCellFromPosition(position) {
|
|
54529
55002
|
const cell = this.getters.getCorrespondingFormulaCell(position);
|
|
54530
55003
|
if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
|
|
54531
|
-
return
|
|
55004
|
+
return EMPTY_PIVOT_CELL;
|
|
54532
55005
|
}
|
|
54533
55006
|
const mainPosition = this.getters.getCellPosition(cell.id);
|
|
54534
55007
|
const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
54535
55008
|
if (!result) {
|
|
54536
|
-
return
|
|
55009
|
+
return EMPTY_PIVOT_CELL;
|
|
54537
55010
|
}
|
|
54538
55011
|
const { functionName, args } = result;
|
|
54539
55012
|
if (functionName === "PIVOT") {
|
|
54540
55013
|
const formulaId = args[0];
|
|
54541
55014
|
if (!formulaId) {
|
|
54542
|
-
return
|
|
55015
|
+
return EMPTY_PIVOT_CELL;
|
|
54543
55016
|
}
|
|
54544
55017
|
const pivotId = this.getters.getPivotId(formulaId.toString());
|
|
54545
55018
|
if (!pivotId) {
|
|
54546
|
-
return
|
|
55019
|
+
return EMPTY_PIVOT_CELL;
|
|
54547
55020
|
}
|
|
54548
55021
|
const pivot = this.getPivot(pivotId);
|
|
54549
55022
|
if (!pivot.isValid()) {
|
|
54550
|
-
return
|
|
55023
|
+
return EMPTY_PIVOT_CELL;
|
|
54551
55024
|
}
|
|
54552
55025
|
const includeTotal = args[2] === false ? false : undefined;
|
|
54553
55026
|
const includeColumnHeaders = args[3] === false ? false : undefined;
|
|
@@ -54556,22 +55029,28 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54556
55029
|
.getPivotCells(includeTotal, includeColumnHeaders);
|
|
54557
55030
|
const pivotCol = position.col - mainPosition.col;
|
|
54558
55031
|
const pivotRow = position.row - mainPosition.row;
|
|
54559
|
-
|
|
54560
|
-
if (pivotCell.type === "EMPTY") {
|
|
54561
|
-
return undefined;
|
|
54562
|
-
}
|
|
54563
|
-
let domain = pivotCell.domain;
|
|
54564
|
-
if (domain.at(-1)?.field === "measure") {
|
|
54565
|
-
domain = domain.slice(0, -1);
|
|
54566
|
-
}
|
|
54567
|
-
return { domainArgs: domain, isHeader: pivotCell.type === "HEADER" };
|
|
55032
|
+
return pivotCells[pivotCol][pivotRow];
|
|
54568
55033
|
}
|
|
54569
|
-
|
|
54570
|
-
|
|
54571
|
-
|
|
55034
|
+
if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
|
|
55035
|
+
const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
|
|
55036
|
+
return {
|
|
55037
|
+
type: "MEASURE_HEADER",
|
|
55038
|
+
domain,
|
|
55039
|
+
measure: args.at(-1)?.toString() || "",
|
|
55040
|
+
};
|
|
54572
55041
|
}
|
|
54573
|
-
|
|
54574
|
-
|
|
55042
|
+
else if (functionName === "PIVOT.HEADER") {
|
|
55043
|
+
return {
|
|
55044
|
+
type: "HEADER",
|
|
55045
|
+
domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
|
|
55046
|
+
};
|
|
55047
|
+
}
|
|
55048
|
+
const [measure, ...domainArgs] = args.slice(1);
|
|
55049
|
+
return {
|
|
55050
|
+
type: "VALUE",
|
|
55051
|
+
domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
|
|
55052
|
+
measure: measure?.toString() || "",
|
|
55053
|
+
};
|
|
54575
55054
|
}
|
|
54576
55055
|
getPivot(pivotId) {
|
|
54577
55056
|
return this.pivots[pivotId];
|
|
@@ -55907,6 +56386,9 @@ class Session extends EventBus {
|
|
|
55907
56386
|
* Send a snapshot of the spreadsheet to the collaboration server
|
|
55908
56387
|
*/
|
|
55909
56388
|
snapshot(data) {
|
|
56389
|
+
if (this.pendingMessages.length !== 0) {
|
|
56390
|
+
return;
|
|
56391
|
+
}
|
|
55910
56392
|
const snapshotId = this.uuidGenerator.uuidv4();
|
|
55911
56393
|
this.transportService.sendMessage({
|
|
55912
56394
|
type: "SNAPSHOT",
|
|
@@ -63851,6 +64333,9 @@ class SelectionStreamProcessorImpl {
|
|
|
63851
64333
|
getBackToDefault() {
|
|
63852
64334
|
this.stream.getBackToDefault();
|
|
63853
64335
|
}
|
|
64336
|
+
getAnchor() {
|
|
64337
|
+
return this.anchor;
|
|
64338
|
+
}
|
|
63854
64339
|
modifyAnchor(anchor, mode, options) {
|
|
63855
64340
|
const sheetId = this.getters.getActiveSheetId();
|
|
63856
64341
|
anchor = {
|
|
@@ -66814,6 +67299,7 @@ const registries = {
|
|
|
66814
67299
|
chartSidePanelComponentRegistry,
|
|
66815
67300
|
chartComponentRegistry,
|
|
66816
67301
|
chartRegistry,
|
|
67302
|
+
chartSubtypeRegistry,
|
|
66817
67303
|
topbarMenuRegistry,
|
|
66818
67304
|
topbarComponentRegistry,
|
|
66819
67305
|
clickableCellRegistry,
|
|
@@ -66922,6 +67408,7 @@ const components = {
|
|
|
66922
67408
|
GaugeChartDesignPanel,
|
|
66923
67409
|
ScorecardChartConfigPanel,
|
|
66924
67410
|
ScorecardChartDesignPanel,
|
|
67411
|
+
ChartTypePicker,
|
|
66925
67412
|
FigureComponent,
|
|
66926
67413
|
Menu,
|
|
66927
67414
|
Popover,
|
|
@@ -66971,11 +67458,12 @@ const constants = {
|
|
|
66971
67458
|
DEFAULT_LOCALE,
|
|
66972
67459
|
HIGHLIGHT_COLOR,
|
|
66973
67460
|
PIVOT_TABLE_CONFIG,
|
|
67461
|
+
ChartTerms,
|
|
66974
67462
|
};
|
|
66975
67463
|
|
|
66976
67464
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
|
|
66977
67465
|
|
|
66978
67466
|
|
|
66979
|
-
__info__.version = "17.4.0-alpha.
|
|
66980
|
-
__info__.date = "2024-06-
|
|
66981
|
-
__info__.hash = "
|
|
67467
|
+
__info__.version = "17.4.0-alpha.6";
|
|
67468
|
+
__info__.date = "2024-06-19T13:46:27.157Z";
|
|
67469
|
+
__info__.hash = "a4f22e4";
|