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