@dhis2/analytics 26.2.0-alpha.2 → 26.2.0-cumulative-values-alpha.1

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.
Files changed (49) hide show
  1. package/build/cjs/__demo__/PivotTable.stories.js +69 -29
  2. package/build/cjs/api/analytics/Analytics.js +0 -7
  3. package/build/cjs/api/analytics/AnalyticsBase.js +6 -24
  4. package/build/cjs/api/analytics/AnalyticsRequest.js +10 -33
  5. package/build/cjs/api/analytics/AnalyticsRequestBase.js +1 -3
  6. package/build/cjs/api/analytics/AnalyticsRequestPropertiesMixin.js +0 -19
  7. package/build/cjs/api/analytics/__tests__/AnalyticsTrackedEntities.spec.js +44 -0
  8. package/build/cjs/api/analytics/__tests__/__snapshots__/AnalyticsTrackedEntities.spec.js.snap +3 -0
  9. package/build/cjs/api/analytics/utils.js +2 -23
  10. package/build/cjs/components/Options/VisualizationOptions.js +1 -1
  11. package/build/cjs/components/Options/styles/VisualizationOptions.style.js +8 -1
  12. package/build/cjs/components/RichText/Editor.bk/Editor.js +40 -0
  13. package/build/cjs/components/RichText/Editor.bk/__tests__/Editor.spec.js +29 -0
  14. package/build/cjs/components/RichText/Editor.bk/__tests__/convertCtrlKey.spec.js +205 -0
  15. package/build/cjs/components/RichText/Editor.bk/convertCtrlKey.js +87 -0
  16. package/build/cjs/components/RichText/Parser.bk/MdParser.js +107 -0
  17. package/build/cjs/components/RichText/Parser.bk/Parser.js +34 -0
  18. package/build/cjs/components/RichText/Parser.bk/__tests__/MdParser.spec.js +34 -0
  19. package/build/cjs/components/RichText/Parser.bk/__tests__/Parser.spec.js +41 -0
  20. package/build/cjs/modules/layout/dimension.js +2 -9
  21. package/build/cjs/modules/layout/dimensionCreate.js +0 -3
  22. package/build/cjs/modules/pivotTable/PivotTableEngine.js +119 -57
  23. package/build/cjs/visualizations/config/generators/dhis/singleValue.js.xp1 +478 -0
  24. package/build/es/__demo__/PivotTable.stories.js +69 -29
  25. package/build/es/api/analytics/Analytics.js +0 -7
  26. package/build/es/api/analytics/AnalyticsBase.js +6 -24
  27. package/build/es/api/analytics/AnalyticsRequest.js +10 -33
  28. package/build/es/api/analytics/AnalyticsRequestBase.js +1 -3
  29. package/build/es/api/analytics/AnalyticsRequestPropertiesMixin.js +0 -19
  30. package/build/es/api/analytics/__tests__/AnalyticsTrackedEntities.spec.js +41 -0
  31. package/build/es/api/analytics/__tests__/__snapshots__/AnalyticsTrackedEntities.spec.js.snap +3 -0
  32. package/build/es/api/analytics/utils.js +1 -20
  33. package/build/es/components/Options/VisualizationOptions.js +2 -2
  34. package/build/es/components/Options/styles/VisualizationOptions.style.js +6 -0
  35. package/build/es/components/RichText/Editor.bk/Editor.js +30 -0
  36. package/build/es/components/RichText/Editor.bk/__tests__/Editor.spec.js +26 -0
  37. package/build/es/components/RichText/Editor.bk/__tests__/convertCtrlKey.spec.js +202 -0
  38. package/build/es/components/RichText/Editor.bk/convertCtrlKey.js +80 -0
  39. package/build/es/components/RichText/Parser.bk/MdParser.js +99 -0
  40. package/build/es/components/RichText/Parser.bk/Parser.js +24 -0
  41. package/build/es/components/RichText/Parser.bk/__tests__/MdParser.spec.js +31 -0
  42. package/build/es/components/RichText/Parser.bk/__tests__/Parser.spec.js +38 -0
  43. package/build/es/modules/layout/dimension.js +1 -7
  44. package/build/es/modules/layout/dimensionCreate.js +1 -4
  45. package/build/es/modules/pivotTable/PivotTableEngine.js +119 -57
  46. package/build/es/visualizations/config/generators/dhis/singleValue.js.xp1 +478 -0
  47. package/package.json +1 -1
  48. package/build/cjs/api/analytics/AnalyticsTrackedEntities.js +0 -31
  49. package/build/es/api/analytics/AnalyticsTrackedEntities.js +0 -24
@@ -0,0 +1,80 @@
1
+ const state = {
2
+ boldMode: false,
3
+ italicMode: false,
4
+ element: null
5
+ };
6
+ const markerMap = {
7
+ italic: '_',
8
+ bold: '*'
9
+ };
10
+ const trim = str => {
11
+ const leftSpaces = /^\s+/;
12
+ const rightSpaces = /\s+$/;
13
+ return str.replace(leftSpaces, '').replace(rightSpaces, '');
14
+ };
15
+ const toggleMode = mode => {
16
+ const prop = `${mode}Mode`;
17
+ state[prop] = !state[prop];
18
+ };
19
+ const insertMarkers = (mode, cb) => {
20
+ const {
21
+ selectionStart: start,
22
+ selectionEnd: end,
23
+ value
24
+ } = state.element;
25
+ const marker = markerMap[mode] || null;
26
+ if (!marker || !cb || start < 0) {
27
+ return;
28
+ }
29
+ toggleMode(mode);
30
+ let newValue;
31
+ let caretPos = end + 1;
32
+ const padMarkers = text => {
33
+ // is caret between two markers (i.e., "**" or "__")? Then do not add padding
34
+ if (start === end && value.length && start > 0) {
35
+ if (value[start - 1] === markerMap.bold && value[start] === markerMap.bold || value[start - 1] === markerMap.italic && value[start] === markerMap.italic) {
36
+ return text;
37
+ }
38
+ }
39
+ if (value.length && start > 0 && value[start - 1] !== ' ') {
40
+ text = ` ${text}`;
41
+ ++caretPos;
42
+ }
43
+ if (value.length && end !== value.length && value[end] !== ' ') {
44
+ text = `${text} `;
45
+ }
46
+ return text;
47
+ };
48
+ if (start === end) {
49
+ //no text
50
+ const valueArr = value.split('');
51
+ valueArr.splice(start, 0, padMarkers(`${marker}${marker}`));
52
+ newValue = valueArr.join('');
53
+ } else {
54
+ const text = value.slice(start, end);
55
+ const trimmedText = trim(text);
56
+
57
+ // adjust caretPos based on trimmed text selection
58
+ caretPos = caretPos - (text.length - trimmedText.length) + 1;
59
+ newValue = [value.slice(0, start), padMarkers(`${marker}${trimmedText}${marker}`), value.slice(end)].join('');
60
+ toggleMode(mode);
61
+ }
62
+ cb(newValue, caretPos);
63
+ };
64
+ const convertCtrlKey = (event, cb) => {
65
+ const {
66
+ key,
67
+ ctrlKey,
68
+ metaKey
69
+ } = event;
70
+ const element = event.target;
71
+ state.element = element;
72
+ if (key === 'b' && (ctrlKey || metaKey)) {
73
+ event.preventDefault();
74
+ insertMarkers('bold', cb);
75
+ } else if (key === 'i' && (ctrlKey || metaKey)) {
76
+ event.preventDefault();
77
+ insertMarkers('italic', cb);
78
+ }
79
+ };
80
+ export default convertCtrlKey;
@@ -0,0 +1,99 @@
1
+ import MarkdownIt from 'markdown-it';
2
+ const emojiDb = {
3
+ ':-)': '\u{1F642}',
4
+ ':)': '\u{1F642}',
5
+ ':-(': '\u{1F641}',
6
+ ':(': '\u{1F641}',
7
+ ':+1': '\u{1F44D}',
8
+ ':-1': '\u{1F44E}'
9
+ };
10
+ const codes = {
11
+ bold: {
12
+ name: 'bold',
13
+ char: '*',
14
+ domEl: 'strong',
15
+ encodedChar: 0x2a,
16
+ // see https://regex101.com/r/evswdV/8 for explanation of regexp
17
+ regexString: '\\B\\*((?!\\s)[^*]+(?:\\b|[^*\\s]))\\*\\B',
18
+ contentFn: val => val
19
+ },
20
+ italic: {
21
+ name: 'italic',
22
+ char: '_',
23
+ domEl: 'em',
24
+ encodedChar: 0x5f,
25
+ // see https://regex101.com/r/p6LpjK/6 for explanation of regexp
26
+ regexString: '\\b_((?!\\s)[^_]+(?:\\B|[^_\\s]))_\\b',
27
+ contentFn: val => val
28
+ },
29
+ emoji: {
30
+ name: 'emoji',
31
+ char: ':',
32
+ domEl: 'span',
33
+ encodedChar: 0x3a,
34
+ regexString: '^(:-\\)|:\\)|:\\(|:-\\(|:\\+1|:-1)',
35
+ contentFn: val => emojiDb[val]
36
+ }
37
+ };
38
+ let md;
39
+ let linksInText;
40
+ const markerIsInLinkText = pos => linksInText.some(link => pos >= link.index && pos <= link.lastIndex);
41
+ const parse = code => (state, silent) => {
42
+ if (silent) {
43
+ return false;
44
+ }
45
+ const start = state.pos;
46
+
47
+ // skip parsing emphasis if marker is within a link
48
+ if (markerIsInLinkText(start)) {
49
+ return false;
50
+ }
51
+ const marker = state.src.charCodeAt(start);
52
+
53
+ // marker character: "_", "*", ":"
54
+ if (marker !== codes[code].encodedChar) {
55
+ return false;
56
+ }
57
+ const MARKER_REGEX = new RegExp(codes[code].regexString);
58
+ const token = state.src.slice(start);
59
+ if (MARKER_REGEX.test(token)) {
60
+ const markerMatch = token.match(MARKER_REGEX);
61
+
62
+ // skip parsing sections where the marker is not at the start of the token
63
+ if (markerMatch.index !== 0) {
64
+ return false;
65
+ }
66
+ const text = markerMatch[1];
67
+ state.push(`${codes[code].domEl}_open`, codes[code].domEl, 1);
68
+ const t = state.push('text', '', 0);
69
+ t.content = codes[code].contentFn(text);
70
+ state.push(`${codes.bold.domEl}_close`, codes[code].domEl, -1);
71
+ state.pos += markerMatch[0].length;
72
+ return true;
73
+ }
74
+ return false;
75
+ };
76
+ class MdParser {
77
+ constructor() {
78
+ // disable all rules, enable autolink for URLs and email addresses
79
+ md = new MarkdownIt('zero', {
80
+ linkify: true
81
+ });
82
+
83
+ // *bold* -> <strong>bold</strong>
84
+ md.inline.ruler.push('strong', parse(codes.bold.name));
85
+
86
+ // _italic_ -> <em>italic</em>
87
+ md.inline.ruler.push('italic', parse(codes.italic.name));
88
+
89
+ // :-) :) :-( :( :+1 :-1 -> <span>[unicode]</span>
90
+ md.inline.ruler.push('emoji', parse(codes.emoji.name));
91
+ md.enable(['link', 'linkify', 'strong', 'italic', 'emoji']);
92
+ return this;
93
+ }
94
+ render(text) {
95
+ linksInText = md.linkify.match(text) || [];
96
+ return md.renderInline(text);
97
+ }
98
+ }
99
+ export default MdParser;
@@ -0,0 +1,24 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useMemo } from 'react';
3
+ import MdParserClass from './MdParser.js';
4
+ const Parser = _ref => {
5
+ let {
6
+ children,
7
+ style
8
+ } = _ref;
9
+ const MdParser = useMemo(() => new MdParserClass(), []);
10
+ return children ? /*#__PURE__*/React.createElement("p", {
11
+ style: style,
12
+ dangerouslySetInnerHTML: {
13
+ __html: MdParser.render(children)
14
+ }
15
+ }) : null;
16
+ };
17
+ Parser.defaultProps = {
18
+ style: null
19
+ };
20
+ Parser.propTypes = {
21
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
22
+ style: PropTypes.object
23
+ };
24
+ export default Parser;
@@ -0,0 +1,31 @@
1
+ import MdParser from '../MdParser.js';
2
+ const Parser = new MdParser();
3
+ describe('MdParser class', () => {
4
+ it('converts text into HTML', () => {
5
+ const tests = [['_italic_', '<em>italic</em>'], ['*bold*', '<strong>bold</strong>'], ['* not bold because there is a space *', '* not bold because there is a space *'], ['_ not italic because there is a space _', '_ not italic because there is a space _'], [':-)', '<span>\u{1F642}</span>'], [':)', '<span>\u{1F642}</span>'], [':-(', '<span>\u{1F641}</span>'], [':(', '<span>\u{1F641}</span>'], [':+1', '<span>\u{1F44D}</span>'], [':-1', '<span>\u{1F44E}</span>'], ['mixed _italic_ *bold* and :+1', 'mixed <em>italic</em> <strong>bold</strong> and <span>\u{1F44D}</span>'], ['_italic with * inside_', '<em>italic with * inside</em>'], ['*bold with _ inside*', '<strong>bold with _ inside</strong>'],
6
+ // italic marker followed by : should work
7
+ ['_italic_:', '<em>italic</em>:'], ['_italic_: some text, *bold*: some other text', '<em>italic</em>: some text, <strong>bold</strong>: some other text'],
8
+ // bold marker followed by : should work
9
+ ['*bold*:', '<strong>bold</strong>:'], ['*bold*: some text, _italic_: some other text', '<strong>bold</strong>: some text, <em>italic</em>: some other text'],
10
+ // italic marker inside an italic string not allowed
11
+ ['_italic with _ inside_', '_italic with _ inside_'],
12
+ // bold marker inside a bold string not allowed
13
+ ['*bold with * inside*', '*bold with * inside*'], ['_multiple_ italic in the _same line_', '<em>multiple</em> italic in the <em>same line</em>'],
14
+ // nested italic/bold combinations not allowed
15
+ ['_italic with *bold* inside_', '<em>italic with *bold* inside</em>'], ['*bold with _italic_ inside*', '<strong>bold with _italic_ inside</strong>'], ['text with : and :)', 'text with : and <span>\u{1F642}</span>'], ['(parenthesis and :))', '(parenthesis and <span>\u{1F642}</span>)'], [':((parenthesis:))', '<span>\u{1F641}</span>(parenthesis<span>\u{1F642}</span>)'], [':+1+1', '<span>\u{1F44D}</span>+1'], ['-1:-1', '-1<span>\u{1F44E}</span>'],
16
+ // links
17
+ ['example.com/path', '<a href="http://example.com/path">example.com/path</a>'],
18
+ // not recognized links with italic marker inside not converted
19
+ ['example_with_underscore.com/path', 'example_with_underscore.com/path'], ['example_with_underscore.com/path_with_underscore', 'example_with_underscore.com/path_with_underscore'],
20
+ // markers around non-recognized links
21
+ ['link example_with_underscore.com/path should _not_ be converted', 'link example_with_underscore.com/path should <em>not</em> be converted'], ['link example_with_underscore.com/path should *not* be converted', 'link example_with_underscore.com/path should <strong>not</strong> be converted'],
22
+ // italic marker inside links not converted
23
+ ['example.com/path_with_underscore', '<a href="http://example.com/path_with_underscore">example.com/path_with_underscore</a>'], ['_italic_ and *bold* with a example.com/link_with_underscore', '<em>italic</em> and <strong>bold</strong> with a <a href="http://example.com/link_with_underscore">example.com/link_with_underscore</a>'], ['example.com/path with *bold* after :)', '<a href="http://example.com/path">example.com/path</a> with <strong>bold</strong> after <span>\u{1F642}</span>'], ['_before_ example.com/path_with_underscore *after* :)', '<em>before</em> <a href="http://example.com/path_with_underscore">example.com/path_with_underscore</a> <strong>after</strong> <span>\u{1F642}</span>'],
24
+ // italic/bold markers right after non-word characters
25
+ ['_If % of ART retention rate after 12 months >90(%)_: Sustain the efforts.', '<em>If % of ART retention rate after 12 months &gt;90(%)</em>: Sustain the efforts.'], ['*If % of ART retention rate after 12 months >90(%)*: Sustain the efforts.', '<strong>If % of ART retention rate after 12 months &gt;90(%)</strong>: Sustain the efforts.']];
26
+ tests.forEach(test => {
27
+ const renderedText = Parser.render(test[0]);
28
+ expect(renderedText).toEqual(test[1]);
29
+ });
30
+ });
31
+ });
@@ -0,0 +1,38 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+ import Parser from '../Parser.js';
4
+ import '../MdParser.js';
5
+ jest.mock('../MdParser', () => {
6
+ return jest.fn().mockImplementation(() => {
7
+ return {
8
+ render: () => 'converted text'
9
+ };
10
+ });
11
+ });
12
+ describe('RichText: Parser component', () => {
13
+ let richTextParser;
14
+ const defaultProps = {
15
+ style: {
16
+ color: 'blue'
17
+ }
18
+ };
19
+ const renderComponent = (props, text) => {
20
+ return shallow( /*#__PURE__*/React.createElement(Parser, props, text));
21
+ };
22
+ it('should have rendered a result', () => {
23
+ richTextParser = renderComponent({}, 'test');
24
+ expect(richTextParser).toHaveLength(1);
25
+ });
26
+ it('should have rendered a result with the style prop', () => {
27
+ richTextParser = renderComponent(defaultProps, 'test prop');
28
+ expect(richTextParser.props().style).toEqual(defaultProps.style);
29
+ });
30
+ it('should have rendered content', () => {
31
+ richTextParser = renderComponent({}, 'plain text');
32
+ expect(richTextParser.html()).toEqual('<p>converted text</p>');
33
+ });
34
+ it('should return null if no children is passed', () => {
35
+ richTextParser = renderComponent({}, undefined);
36
+ expect(richTextParser.html()).toBe(null);
37
+ });
38
+ });
@@ -33,12 +33,6 @@ export const DIMENSION_PROP_LEGEND_SET = {
33
33
  required: false,
34
34
  isValid: prop => isString(prop)
35
35
  };
36
- export const DIMENSION_PROP_PROGRAM = {
37
- name: 'program',
38
- defaultValue: {},
39
- required: false,
40
- isValid: prop => isObject(prop)
41
- };
42
36
  export const DIMENSION_PROP_PROGRAM_STAGE = {
43
37
  name: 'programStage',
44
38
  defaultValue: {},
@@ -51,4 +45,4 @@ export const DIMENSION_PROP_REPETITION = {
51
45
  required: false,
52
46
  isValid: prop => Array.isArray(prop)
53
47
  };
54
- export const DIMENSION_PROPS = [DIMENSION_PROP_ID, DIMENSION_PROP_ITEMS, DIMENSION_PROP_FILTER, DIMENSION_PROP_LEGEND_SET, DIMENSION_PROP_PROGRAM, DIMENSION_PROP_PROGRAM_STAGE, DIMENSION_PROP_REPETITION];
48
+ export const DIMENSION_PROPS = [DIMENSION_PROP_ID, DIMENSION_PROP_ITEMS, DIMENSION_PROP_FILTER, DIMENSION_PROP_LEGEND_SET, DIMENSION_PROP_PROGRAM_STAGE, DIMENSION_PROP_REPETITION];
@@ -1,4 +1,4 @@
1
- import { DIMENSION_PROP_ID, DIMENSION_PROP_ITEMS, DIMENSION_PROP_FILTER, DIMENSION_PROP_LEGEND_SET, DIMENSION_PROP_PROGRAM, DIMENSION_PROP_PROGRAM_STAGE, DIMENSION_PROP_REPETITION } from './dimension.js';
1
+ import { DIMENSION_PROP_ID, DIMENSION_PROP_ITEMS, DIMENSION_PROP_FILTER, DIMENSION_PROP_LEGEND_SET, DIMENSION_PROP_PROGRAM_STAGE, DIMENSION_PROP_REPETITION } from './dimension.js';
2
2
  export const dimensionCreate = function (dimensionId) {
3
3
  let itemIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
4
4
  let args = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
@@ -15,9 +15,6 @@ export const dimensionCreate = function (dimensionId) {
15
15
  ...(args.legendSet && {
16
16
  [DIMENSION_PROP_LEGEND_SET.name]: args.legendSet
17
17
  }),
18
- ...(args.program && {
19
- [DIMENSION_PROP_PROGRAM.name]: args.program
20
- }),
21
18
  ...(args.programStage && {
22
19
  [DIMENSION_PROP_PROGRAM_STAGE.name]: args.programStage
23
20
  }),
@@ -20,7 +20,8 @@ const defaultOptions = {
20
20
  showRowSubtotals: false,
21
21
  showColumnSubtotals: false,
22
22
  fixColumnHeaders: false,
23
- fixRowHeaders: false
23
+ fixRowHeaders: false,
24
+ cumulativeValues: false
24
25
  };
25
26
  const defaultVisualizationProps = {
26
27
  fontSize: FONT_SIZE_OPTION_NORMAL,
@@ -175,6 +176,9 @@ export class PivotTableEngine {
175
176
  _defineProperty(this, "data", []);
176
177
  _defineProperty(this, "rowMap", []);
177
178
  _defineProperty(this, "columnMap", []);
179
+ _defineProperty(this, "accumulators", {
180
+ rows: {}
181
+ });
178
182
  this.visualization = Object.assign({}, defaultVisualizationProps, visualization);
179
183
  this.legendSets = (legendSets || []).reduce((sets, set) => {
180
184
  sets[set.id] = set;
@@ -194,7 +198,8 @@ export class PivotTableEngine {
194
198
  subtitle: visualization.hideSubtitle ? undefined : visualization.subtitle,
195
199
  // turn on fixed headers only when there are dimensions
196
200
  fixColumnHeaders: this.dimensionLookup.columns.length ? visualization.fixColumnHeaders : false,
197
- fixRowHeaders: this.dimensionLookup.rows.length ? visualization.fixRowHeaders : false
201
+ fixRowHeaders: this.dimensionLookup.rows.length ? visualization.fixRowHeaders : false,
202
+ cumulativeValues: visualization.cumulativeValues
198
203
  };
199
204
  this.adaptiveClippingController = new AdaptiveClippingController(this);
200
205
  const doColumnSubtotals = this.options.showColumnSubtotals && this.dimensionLookup.rows.length > 1;
@@ -218,51 +223,68 @@ export class PivotTableEngine {
218
223
  row,
219
224
  column
220
225
  });
226
+ const valueType = (dxDimension === null || dxDimension === void 0 ? void 0 : dxDimension.valueType) || VALUE_TYPE_TEXT;
221
227
  const headers = [...this.getRawRowHeader(row), ...this.getRawColumnHeader(column)];
222
228
  const peId = (_headers$find = headers.find(header => (header === null || header === void 0 ? void 0 : header.dimensionItemType) === DIMENSION_TYPE_PERIOD)) === null || _headers$find === void 0 ? void 0 : _headers$find.uid;
223
229
  const ouId = (_headers$find2 = headers.find(header => (header === null || header === void 0 ? void 0 : header.dimensionItemType) === DIMENSION_TYPE_ORGANISATION_UNIT)) === null || _headers$find2 === void 0 ? void 0 : _headers$find2.uid;
224
- if (!this.data[row] || !this.data[row][column]) {
225
- return {
226
- cellType,
227
- empty: true,
228
- ouId,
229
- peId
230
- };
231
- }
232
- const dataRow = this.data[row][column];
233
- let rawValue = cellType === CELL_TYPE_VALUE ? dataRow[this.dimensionLookup.dataHeaders.value] : dataRow.value;
234
- let renderedValue = rawValue;
235
- const valueType = (dxDimension === null || dxDimension === void 0 ? void 0 : dxDimension.valueType) || VALUE_TYPE_TEXT;
236
- if (valueType === VALUE_TYPE_NUMBER) {
237
- rawValue = parseValue(rawValue);
238
- switch (this.visualization.numberType) {
239
- case NUMBER_TYPE_ROW_PERCENTAGE:
240
- renderedValue = rawValue / this.percentageTotals[row].value;
241
- break;
242
- case NUMBER_TYPE_COLUMN_PERCENTAGE:
243
- renderedValue = rawValue / this.percentageTotals[column].value;
244
- break;
245
- default:
246
- break;
247
- }
248
- }
249
- renderedValue = renderValue(renderedValue, valueType, this.visualization);
250
- return {
230
+ const rawCell = {
251
231
  cellType,
252
- empty: false,
253
232
  valueType,
254
- rawValue,
255
- renderedValue,
256
- dxDimension,
257
233
  ouId,
258
234
  peId
259
235
  };
236
+ if (!this.data[row] || !this.data[row][column]) {
237
+ rawCell.empty = true;
238
+ } else {
239
+ const dataRow = this.data[row][column];
240
+ let rawValue = cellType === CELL_TYPE_VALUE ? dataRow[this.dimensionLookup.dataHeaders.value] : dataRow.value;
241
+ let renderedValue = rawValue;
242
+ if (valueType === VALUE_TYPE_NUMBER) {
243
+ rawValue = parseValue(rawValue);
244
+ switch (this.visualization.numberType) {
245
+ case NUMBER_TYPE_ROW_PERCENTAGE:
246
+ renderedValue = rawValue / this.percentageTotals[row].value;
247
+ break;
248
+ case NUMBER_TYPE_COLUMN_PERCENTAGE:
249
+ renderedValue = rawValue / this.percentageTotals[column].value;
250
+ break;
251
+ default:
252
+ break;
253
+ }
254
+ }
255
+ renderedValue = renderValue(renderedValue, valueType, this.visualization);
256
+ rawCell.dxDimension = dxDimension;
257
+ rawCell.empty = false;
258
+ rawCell.rawValue = rawValue;
259
+ rawCell.renderedValue = renderedValue;
260
+ }
261
+ if (this.options.cumulativeValues) {
262
+ const cumulativeValue = this.getCumulative({
263
+ row,
264
+ column
265
+ });
266
+ if (cumulativeValue !== undefined && cumulativeValue !== null) {
267
+ // force to NUMBER for accumulated values
268
+ rawCell.valueType = valueType === undefined || valueType === null ? VALUE_TYPE_NUMBER : valueType;
269
+ rawCell.empty = false;
270
+ rawCell.rawValue = cumulativeValue;
271
+ rawCell.renderedValue = renderValue(cumulativeValue, valueType, this.visualization);
272
+ }
273
+ }
274
+ return rawCell;
260
275
  }
261
- get(_ref4) {
276
+ getCumulative(_ref4) {
262
277
  let {
263
278
  row,
264
279
  column
265
280
  } = _ref4;
281
+ return this.accumulators.rows[row][column];
282
+ }
283
+ get(_ref5) {
284
+ let {
285
+ row,
286
+ column
287
+ } = _ref5;
266
288
  const mappedRow = this.rowMap[row],
267
289
  mappedColumn = this.columnMap[column];
268
290
  if (!mappedRow && mappedRow !== 0 || !mappedColumn && mappedColumn !== 0) {
@@ -273,11 +295,11 @@ export class PivotTableEngine {
273
295
  column: mappedColumn
274
296
  });
275
297
  }
276
- getRawCellType(_ref5) {
298
+ getRawCellType(_ref6) {
277
299
  let {
278
300
  row,
279
301
  column
280
- } = _ref5;
302
+ } = _ref6;
281
303
  const isRowTotal = this.doRowTotals && column === this.dataWidth - 1;
282
304
  const isColumnTotal = this.doColumnTotals && row === this.dataHeight - 1;
283
305
  if (isRowTotal || isColumnTotal) {
@@ -290,11 +312,11 @@ export class PivotTableEngine {
290
312
  }
291
313
  return CELL_TYPE_VALUE;
292
314
  }
293
- getCellType(_ref6) {
315
+ getCellType(_ref7) {
294
316
  let {
295
317
  row,
296
318
  column
297
- } = _ref6;
319
+ } = _ref7;
298
320
  row = this.rowMap[row];
299
321
  column = this.columnMap[column];
300
322
  return this.getRawCellType({
@@ -324,29 +346,26 @@ export class PivotTableEngine {
324
346
  return i18n.t(this.dimensionLookup.rows[rowLevel].meta.name);
325
347
  }
326
348
  }
327
- getCellDxDimension(_ref7) {
349
+ getCellDxDimension(_ref8) {
328
350
  let {
329
351
  row,
330
352
  column
331
- } = _ref7;
353
+ } = _ref8;
332
354
  return this.getRawCellDxDimension({
333
355
  row: this.rowMap[row],
334
356
  column: this.columnMap[column]
335
357
  });
336
358
  }
337
- getRawCellDxDimension(_ref8) {
359
+ getRawCellDxDimension(_ref9) {
338
360
  let {
339
361
  row,
340
362
  column
341
- } = _ref8;
363
+ } = _ref9;
342
364
  if (!this.data[row]) {
343
365
  return undefined;
344
366
  }
345
367
  const cellValue = this.data[row][column];
346
- if (!cellValue) {
347
- return undefined;
348
- }
349
- if (!Array.isArray(cellValue)) {
368
+ if (cellValue && !Array.isArray(cellValue)) {
350
369
  // This is a total cell
351
370
  return {
352
371
  valueType: cellValue.valueType,
@@ -373,6 +392,11 @@ export class PivotTableEngine {
373
392
  };
374
393
  }
375
394
 
395
+ // Empty cell
396
+ // The cell still needs to get the valueType to render correctly 0 and cumulative values
397
+ //
398
+ // OR
399
+ //
376
400
  // Data is in Filter
377
401
  // TODO : This assumes the server ignores text types, we should confirm this is the case
378
402
  return {
@@ -384,7 +408,7 @@ export class PivotTableEngine {
384
408
  return !this.data[row] || this.data[row].length === 0;
385
409
  }
386
410
  columnIsEmpty(column) {
387
- return !this.adaptiveClippingController.columns.sizes[column];
411
+ return !this.rowMap.some(row => this.data[row][column]);
388
412
  }
389
413
  getRawColumnHeader(column) {
390
414
  if (this.doRowTotals && column === this.dataWidth - 1) {
@@ -430,12 +454,12 @@ export class PivotTableEngine {
430
454
  getRowHeader(row) {
431
455
  return this.getRawRowHeader(this.rowMap[row]);
432
456
  }
433
- getDependantTotalCells(_ref9) {
457
+ getDependantTotalCells(_ref10) {
434
458
  var _this$dimensionLookup, _this$dimensionLookup2;
435
459
  let {
436
460
  row,
437
461
  column
438
- } = _ref9;
462
+ } = _ref10;
439
463
  const rowSubtotalSize = ((_this$dimensionLookup = this.dimensionLookup.columns[0]) === null || _this$dimensionLookup === void 0 ? void 0 : _this$dimensionLookup.size) + 1;
440
464
  const rowSubtotal = rowSubtotalSize && this.doRowSubtotals && {
441
465
  row,
@@ -610,11 +634,11 @@ export class PivotTableEngine {
610
634
  }
611
635
  }
612
636
  }
613
- finalizeTotal(_ref10) {
637
+ finalizeTotal(_ref11) {
614
638
  let {
615
639
  row,
616
640
  column
617
- } = _ref10;
641
+ } = _ref11;
618
642
  if (!this.data[row]) {
619
643
  return;
620
644
  }
@@ -710,6 +734,40 @@ export class PivotTableEngine {
710
734
  resetColumnMap() {
711
735
  this.columnMap = this.options.hideEmptyColumns ? times(this.dataWidth, n => n).filter(idx => !this.columnIsEmpty(idx)) : times(this.dataWidth, n => n);
712
736
  }
737
+ resetAccumulators() {
738
+ if (this.options.cumulativeValues) {
739
+ this.rowMap.forEach(row => {
740
+ this.accumulators.rows[row] = {};
741
+ this.columnMap.reduce((acc, column) => {
742
+ const cellType = this.getRawCellType({
743
+ row,
744
+ column
745
+ });
746
+ const dxDimension = this.getRawCellDxDimension({
747
+ row,
748
+ column
749
+ });
750
+ const valueType = (dxDimension === null || dxDimension === void 0 ? void 0 : dxDimension.valueType) || VALUE_TYPE_TEXT;
751
+
752
+ // only accumulate numeric values
753
+ // accumulating text values does not make sense
754
+ if (valueType === VALUE_TYPE_NUMBER) {
755
+ if (this.data[row] && this.data[row][column]) {
756
+ const dataRow = this.data[row][column];
757
+ const rawValue = cellType === CELL_TYPE_VALUE ? dataRow[this.dimensionLookup.dataHeaders.value] : dataRow.value;
758
+ acc += parseValue(rawValue);
759
+ }
760
+ this.accumulators.rows[row][column] = acc;
761
+ }
762
+ return acc;
763
+ }, 0);
764
+ });
765
+ } else {
766
+ this.accumulators = {
767
+ rows: {}
768
+ };
769
+ }
770
+ }
713
771
  get cellPadding() {
714
772
  switch (this.visualization.displayDensity) {
715
773
  case DISPLAY_DENSITY_OPTION_COMPACT:
@@ -780,14 +838,18 @@ export class PivotTableEngine {
780
838
  }
781
839
  });
782
840
  this.finalizeTotals();
783
- this.rawData.rows.forEach(dataRow => {
784
- const pos = lookup(dataRow, this.dimensionLookup, this);
785
- if (pos) {
786
- this.adaptiveClippingController.add(pos, this.getRaw(pos).renderedValue);
787
- }
788
- });
789
841
  this.resetRowMap();
790
842
  this.resetColumnMap();
843
+ this.resetAccumulators();
844
+ this.rowMap.forEach(row => {
845
+ this.columnMap.forEach(column => {
846
+ const pos = {
847
+ row,
848
+ column
849
+ };
850
+ this.adaptiveClippingController.add(pos, this.getRaw(pos).renderedValue);
851
+ });
852
+ });
791
853
  this.height = this.rowMap.length;
792
854
  this.width = this.columnMap.length;
793
855
  this.adaptiveClippingController.finalize();