@vue-pivottable/subtotal-renderer 0.1.0
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/README.md +295 -0
- package/dist/core.js +157 -0
- package/dist/core.mjs +157 -0
- package/dist/index.js +443 -0
- package/dist/index.mjs +444 -0
- package/dist/vue2.js +452 -0
- package/dist/vue2.mjs +453 -0
- package/package.json +56 -0
- package/types/index.d.ts +141 -0
package/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# @vue-pivottable/subtotal-renderer
|
|
2
|
+
|
|
3
|
+
Subtotal renderer for [vue-pivottable](https://github.com/Seungwoo321/vue-pivottable) with expand/collapse support. Inspired by [subtotal.js](https://github.com/nagarajanchinnasamy/subtotal).
|
|
4
|
+
|
|
5
|
+
Supports both **Vue 2** and **Vue 3**.
|
|
6
|
+
|
|
7
|
+
## Screenshots
|
|
8
|
+
|
|
9
|
+
### Vue 3 - Subtotal Table
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
### Vue 3 - With UI
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
### Vue 2 - Subtotal Table
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
### Vue 2 - With UI
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- 📊 **Subtotal rows and columns** - Automatically calculates and displays subtotals for hierarchical data
|
|
28
|
+
- 🔄 **Expand/Collapse** - Click to expand or collapse row/column groups
|
|
29
|
+
- 🎨 **Styled subtotal rows** - Visual distinction for subtotal rows
|
|
30
|
+
- 🖱️ **Click callbacks** - Supports the same click callback as the standard renderer
|
|
31
|
+
- 📝 **Labels support** - Custom label formatting for row/column values
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @vue-pivottable/subtotal-renderer
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Vue 3
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<template>
|
|
45
|
+
<VuePivottable
|
|
46
|
+
:data="data"
|
|
47
|
+
:rows="['Category', 'Subcategory', 'Product']"
|
|
48
|
+
:cols="['Region', 'City']"
|
|
49
|
+
:vals="['Sales']"
|
|
50
|
+
aggregatorName="Sum"
|
|
51
|
+
:renderers="SubtotalRenderers"
|
|
52
|
+
rendererName="Subtotal Table"
|
|
53
|
+
/>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup>
|
|
57
|
+
import { VuePivottable, PivotUtilities } from 'vue-pivottable'
|
|
58
|
+
import { createSubtotalRenderers } from '@vue-pivottable/subtotal-renderer'
|
|
59
|
+
import 'vue-pivottable/dist/vue-pivottable.css'
|
|
60
|
+
|
|
61
|
+
// Create SubtotalRenderers with PivotData from vue-pivottable
|
|
62
|
+
const SubtotalRenderers = createSubtotalRenderers(PivotUtilities.PivotData)
|
|
63
|
+
|
|
64
|
+
const data = [
|
|
65
|
+
{ Category: 'Electronics', Subcategory: 'Phones', Product: 'iPhone', Region: 'North', City: 'Seoul', Sales: 1000 },
|
|
66
|
+
{ Category: 'Electronics', Subcategory: 'Phones', Product: 'Samsung', Region: 'North', City: 'Seoul', Sales: 800 },
|
|
67
|
+
{ Category: 'Electronics', Subcategory: 'Laptops', Product: 'MacBook', Region: 'North', City: 'Busan', Sales: 1500 },
|
|
68
|
+
{ Category: 'Clothing', Subcategory: 'Shirts', Product: 'T-Shirt', Region: 'South', City: 'Daegu', Sales: 200 },
|
|
69
|
+
// ... more data
|
|
70
|
+
]
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Vue 2
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<template>
|
|
78
|
+
<VuePivottable
|
|
79
|
+
:data="data"
|
|
80
|
+
:rows="['Category', 'Subcategory', 'Product']"
|
|
81
|
+
:cols="['Region', 'City']"
|
|
82
|
+
:vals="['Sales']"
|
|
83
|
+
aggregatorName="Sum"
|
|
84
|
+
:renderers="SubtotalRenderers"
|
|
85
|
+
rendererName="Subtotal Table"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<script>
|
|
90
|
+
import { VuePivottable, PivotUtilities } from 'vue-pivottable'
|
|
91
|
+
import { createSubtotalRenderers } from '@vue-pivottable/subtotal-renderer/vue2'
|
|
92
|
+
import 'vue-pivottable/dist/vue-pivottable.css'
|
|
93
|
+
|
|
94
|
+
// Create SubtotalRenderers with PivotData from vue-pivottable
|
|
95
|
+
const SubtotalRenderers = createSubtotalRenderers(PivotUtilities.PivotData)
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
components: { VuePivottable },
|
|
99
|
+
data() {
|
|
100
|
+
return {
|
|
101
|
+
SubtotalRenderers,
|
|
102
|
+
data: [
|
|
103
|
+
// ... your data
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## With PivotTable UI
|
|
112
|
+
|
|
113
|
+
You can also use the subtotal renderer with the interactive PivotTable UI:
|
|
114
|
+
|
|
115
|
+
### Vue 3
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<template>
|
|
119
|
+
<VuePivottableUi
|
|
120
|
+
:data="data"
|
|
121
|
+
:rows="['Category', 'Subcategory']"
|
|
122
|
+
:cols="['Region']"
|
|
123
|
+
:vals="['Sales']"
|
|
124
|
+
aggregatorName="Sum"
|
|
125
|
+
:renderers="SubtotalRenderers"
|
|
126
|
+
rendererName="Subtotal Table"
|
|
127
|
+
/>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<script setup>
|
|
131
|
+
import { VuePivottableUi, PivotUtilities } from 'vue-pivottable'
|
|
132
|
+
import { createSubtotalRenderers } from '@vue-pivottable/subtotal-renderer'
|
|
133
|
+
import 'vue-pivottable/dist/vue-pivottable.css'
|
|
134
|
+
|
|
135
|
+
const SubtotalRenderers = createSubtotalRenderers(PivotUtilities.PivotData)
|
|
136
|
+
</script>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Subtotal Options
|
|
140
|
+
|
|
141
|
+
You can customize the subtotal behavior using the `subtotalOptions` prop:
|
|
142
|
+
|
|
143
|
+
```vue
|
|
144
|
+
<VuePivottable
|
|
145
|
+
:data="data"
|
|
146
|
+
:renderers="SubtotalRenderers"
|
|
147
|
+
rendererName="Subtotal Table"
|
|
148
|
+
:subtotalOptions="{
|
|
149
|
+
rowSubtotalDisplay: {
|
|
150
|
+
displayOnTop: false,
|
|
151
|
+
enabled: true,
|
|
152
|
+
hideOnExpand: false
|
|
153
|
+
},
|
|
154
|
+
colSubtotalDisplay: {
|
|
155
|
+
displayOnTop: false,
|
|
156
|
+
enabled: true,
|
|
157
|
+
hideOnExpand: false
|
|
158
|
+
},
|
|
159
|
+
arrowCollapsed: '▶',
|
|
160
|
+
arrowExpanded: '▼'
|
|
161
|
+
}"
|
|
162
|
+
/>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Options
|
|
166
|
+
|
|
167
|
+
| Option | Type | Default | Description |
|
|
168
|
+
|--------|------|---------|-------------|
|
|
169
|
+
| `rowSubtotalDisplay.enabled` | `boolean` | `true` | Enable row subtotals |
|
|
170
|
+
| `rowSubtotalDisplay.displayOnTop` | `boolean` | `false` | Display subtotals above the group |
|
|
171
|
+
| `rowSubtotalDisplay.hideOnExpand` | `boolean` | `false` | Hide subtotals when group is expanded |
|
|
172
|
+
| `colSubtotalDisplay.enabled` | `boolean` | `true` | Enable column subtotals |
|
|
173
|
+
| `colSubtotalDisplay.displayOnTop` | `boolean` | `false` | Display subtotals to the left of the group |
|
|
174
|
+
| `colSubtotalDisplay.hideOnExpand` | `boolean` | `false` | Hide subtotals when group is expanded |
|
|
175
|
+
| `arrowCollapsed` | `string` | `'▶'` | Arrow character for collapsed state |
|
|
176
|
+
| `arrowExpanded` | `string` | `'▼'` | Arrow character for expanded state |
|
|
177
|
+
|
|
178
|
+
## Click Callback
|
|
179
|
+
|
|
180
|
+
The subtotal renderer supports the same `clickCallback` as the standard renderer:
|
|
181
|
+
|
|
182
|
+
```vue
|
|
183
|
+
<VuePivottable
|
|
184
|
+
:data="data"
|
|
185
|
+
:renderers="SubtotalRenderers"
|
|
186
|
+
rendererName="Subtotal Table"
|
|
187
|
+
:tableOptions="{
|
|
188
|
+
clickCallback: (event, value, filters, pivotData) => {
|
|
189
|
+
console.log('Clicked value:', value)
|
|
190
|
+
console.log('Filters:', filters)
|
|
191
|
+
}
|
|
192
|
+
}"
|
|
193
|
+
/>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Custom Labels
|
|
197
|
+
|
|
198
|
+
Use the `labels` prop to customize how values are displayed:
|
|
199
|
+
|
|
200
|
+
```vue
|
|
201
|
+
<VuePivottable
|
|
202
|
+
:data="data"
|
|
203
|
+
:renderers="SubtotalRenderers"
|
|
204
|
+
rendererName="Subtotal Table"
|
|
205
|
+
:labels="{
|
|
206
|
+
Category: (val) => val.toUpperCase(),
|
|
207
|
+
Region: (val) => `Region: ${val}`
|
|
208
|
+
}"
|
|
209
|
+
/>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Styling
|
|
213
|
+
|
|
214
|
+
The renderer adds specific CSS classes for styling:
|
|
215
|
+
|
|
216
|
+
```css
|
|
217
|
+
/* Subtotal rows */
|
|
218
|
+
.pvtSubtotalRow {
|
|
219
|
+
background-color: #f5f5f5;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Subtotal label cells */
|
|
223
|
+
.pvtSubtotalLabel {
|
|
224
|
+
font-weight: bold;
|
|
225
|
+
background-color: #e8e8e8;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Subtotal value cells */
|
|
229
|
+
.pvtSubtotalVal {
|
|
230
|
+
font-weight: bold;
|
|
231
|
+
background-color: #f0f0f0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* Collapse toggle */
|
|
235
|
+
.pvtCollapseToggle {
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
user-select: none;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.pvtCollapseToggle:hover {
|
|
241
|
+
color: #0066cc;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## How It Works
|
|
246
|
+
|
|
247
|
+
1. **Hierarchical Keys**: When you have multiple row/column attributes (e.g., `['Category', 'Subcategory', 'Product']`), the renderer creates subtotal rows for each level of the hierarchy.
|
|
248
|
+
|
|
249
|
+
2. **Subtotal Calculation**: For each subtotal row, the renderer sums up all values in the child rows. For example, the "Electronics" subtotal includes all phones and laptops.
|
|
250
|
+
|
|
251
|
+
3. **Expand/Collapse**: Clicking the arrow icon collapses or expands the child rows. When collapsed, only the subtotal row is visible.
|
|
252
|
+
|
|
253
|
+
## Comparison with Standard Renderer
|
|
254
|
+
|
|
255
|
+
| Feature | Standard Renderer | Subtotal Renderer |
|
|
256
|
+
|---------|-------------------|-------------------|
|
|
257
|
+
| Row/Column totals | ✅ | ✅ |
|
|
258
|
+
| Grand total | ✅ | ✅ |
|
|
259
|
+
| Subtotals | ❌ | ✅ |
|
|
260
|
+
| Expand/Collapse | ❌ | ✅ |
|
|
261
|
+
| Click callback | ✅ | ✅ |
|
|
262
|
+
| Labels | ✅ | ✅ |
|
|
263
|
+
| Heatmap | ✅ | ❌ (planned) |
|
|
264
|
+
|
|
265
|
+
## Running Examples
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Clone the repository
|
|
269
|
+
git clone https://github.com/user/vue3-pivottable.git
|
|
270
|
+
|
|
271
|
+
# Navigate to the subtotal-renderer package
|
|
272
|
+
cd vue3-pivottable/subtotal-renderer
|
|
273
|
+
|
|
274
|
+
# Install dependencies
|
|
275
|
+
npm install
|
|
276
|
+
|
|
277
|
+
# Run Vue 3 example
|
|
278
|
+
cd example/vue3
|
|
279
|
+
npm install
|
|
280
|
+
npm run dev
|
|
281
|
+
|
|
282
|
+
# Run Vue 2 example (in another terminal)
|
|
283
|
+
cd example/vue2
|
|
284
|
+
npm install
|
|
285
|
+
npm run dev
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Requirements
|
|
289
|
+
|
|
290
|
+
- Vue 2.6+ or Vue 3.0+
|
|
291
|
+
- vue-pivottable (matching version)
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function getSubtotalKeys(key) {
|
|
4
|
+
const result = [];
|
|
5
|
+
for (let i = 1; i <= key.length; i++) {
|
|
6
|
+
result.push(key.slice(0, i));
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
function isSubtotalKey(key, maxDepth) {
|
|
11
|
+
return key.length < maxDepth;
|
|
12
|
+
}
|
|
13
|
+
function generateRowKeysWithSubtotals(rowKeys, rowAttrsCount, collapsedKeys = /* @__PURE__ */ new Set()) {
|
|
14
|
+
if (rowAttrsCount <= 1) {
|
|
15
|
+
return rowKeys;
|
|
16
|
+
}
|
|
17
|
+
const result = [];
|
|
18
|
+
const addedSubtotals = /* @__PURE__ */ new Set();
|
|
19
|
+
for (const rowKey of rowKeys) {
|
|
20
|
+
for (let level = 1; level < rowKey.length; level++) {
|
|
21
|
+
const subtotalKey = rowKey.slice(0, level);
|
|
22
|
+
const subtotalKeyStr = JSON.stringify(subtotalKey);
|
|
23
|
+
if (!addedSubtotals.has(subtotalKeyStr)) {
|
|
24
|
+
addedSubtotals.add(subtotalKeyStr);
|
|
25
|
+
const isCollapsed = collapsedKeys.has(subtotalKeyStr);
|
|
26
|
+
result.push({
|
|
27
|
+
key: subtotalKey,
|
|
28
|
+
isSubtotal: true,
|
|
29
|
+
level,
|
|
30
|
+
isCollapsed
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let isHidden = false;
|
|
35
|
+
for (let level = 1; level < rowKey.length; level++) {
|
|
36
|
+
const parentKey = rowKey.slice(0, level);
|
|
37
|
+
if (collapsedKeys.has(JSON.stringify(parentKey))) {
|
|
38
|
+
isHidden = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!isHidden) {
|
|
43
|
+
result.push({
|
|
44
|
+
key: rowKey,
|
|
45
|
+
isSubtotal: false,
|
|
46
|
+
level: rowKey.length,
|
|
47
|
+
isCollapsed: false
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function generateColKeysWithSubtotals(colKeys, colAttrsCount, collapsedKeys = /* @__PURE__ */ new Set()) {
|
|
54
|
+
if (colAttrsCount <= 1) {
|
|
55
|
+
return colKeys;
|
|
56
|
+
}
|
|
57
|
+
const result = [];
|
|
58
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const colKey of colKeys) {
|
|
60
|
+
for (let level = 1; level <= colKey.length; level++) {
|
|
61
|
+
const partialKey = colKey.slice(0, level);
|
|
62
|
+
const keyStr = JSON.stringify(partialKey);
|
|
63
|
+
if (!grouped.has(keyStr)) {
|
|
64
|
+
grouped.set(keyStr, {
|
|
65
|
+
key: partialKey,
|
|
66
|
+
isSubtotal: level < colKey.length,
|
|
67
|
+
level
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const sortedKeys = Array.from(grouped.values()).sort((a, b) => {
|
|
73
|
+
for (let i = 0; i < Math.min(a.key.length, b.key.length); i++) {
|
|
74
|
+
if (a.key[i] < b.key[i])
|
|
75
|
+
return -1;
|
|
76
|
+
if (a.key[i] > b.key[i])
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
return a.key.length - b.key.length;
|
|
80
|
+
});
|
|
81
|
+
for (const item of sortedKeys) {
|
|
82
|
+
const keyStr = JSON.stringify(item.key);
|
|
83
|
+
const isCollapsed = collapsedKeys.has(keyStr);
|
|
84
|
+
let isHidden = false;
|
|
85
|
+
for (let level = 1; level < item.key.length; level++) {
|
|
86
|
+
const parentKey = item.key.slice(0, level);
|
|
87
|
+
if (collapsedKeys.has(JSON.stringify(parentKey))) {
|
|
88
|
+
isHidden = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!isHidden) {
|
|
93
|
+
result.push({
|
|
94
|
+
key: item.key,
|
|
95
|
+
isSubtotal: item.isSubtotal,
|
|
96
|
+
level: item.level,
|
|
97
|
+
isCollapsed
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function createSubtotalAggregatorGetter(pivotData) {
|
|
104
|
+
const subtotalCache = /* @__PURE__ */ new Map();
|
|
105
|
+
return function getAggregator(rowKey, colKey) {
|
|
106
|
+
const isFullRowKey = rowKey.length === pivotData.props.rows.length;
|
|
107
|
+
const isFullColKey = colKey.length === pivotData.props.cols.length;
|
|
108
|
+
if (isFullRowKey && isFullColKey) {
|
|
109
|
+
return pivotData.getAggregator(rowKey, colKey);
|
|
110
|
+
}
|
|
111
|
+
const cacheKey = JSON.stringify({ row: rowKey, col: colKey });
|
|
112
|
+
if (subtotalCache.has(cacheKey)) {
|
|
113
|
+
return subtotalCache.get(cacheKey);
|
|
114
|
+
}
|
|
115
|
+
const matchingRowKeys = pivotData.getRowKeys().filter((rk) => {
|
|
116
|
+
if (rowKey.length === 0)
|
|
117
|
+
return true;
|
|
118
|
+
for (let i = 0; i < rowKey.length; i++) {
|
|
119
|
+
if (rk[i] !== rowKey[i])
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
const matchingColKeys = pivotData.getColKeys().filter((ck) => {
|
|
125
|
+
if (colKey.length === 0)
|
|
126
|
+
return true;
|
|
127
|
+
for (let i = 0; i < colKey.length; i++) {
|
|
128
|
+
if (ck[i] !== colKey[i])
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
let totalValue = 0;
|
|
134
|
+
let count = 0;
|
|
135
|
+
for (const rk of matchingRowKeys) {
|
|
136
|
+
for (const ck of matchingColKeys) {
|
|
137
|
+
const agg = pivotData.getAggregator(rk, ck);
|
|
138
|
+
const val = agg.value();
|
|
139
|
+
if (val !== null && val !== void 0 && !isNaN(val)) {
|
|
140
|
+
totalValue += val;
|
|
141
|
+
count++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const subtotalAgg = {
|
|
146
|
+
value: () => count > 0 ? totalValue : null,
|
|
147
|
+
format: pivotData.getAggregator([], []).format || ((v) => v)
|
|
148
|
+
};
|
|
149
|
+
subtotalCache.set(cacheKey, subtotalAgg);
|
|
150
|
+
return subtotalAgg;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
exports.createSubtotalAggregatorGetter = createSubtotalAggregatorGetter;
|
|
154
|
+
exports.generateColKeysWithSubtotals = generateColKeysWithSubtotals;
|
|
155
|
+
exports.generateRowKeysWithSubtotals = generateRowKeysWithSubtotals;
|
|
156
|
+
exports.getSubtotalKeys = getSubtotalKeys;
|
|
157
|
+
exports.isSubtotalKey = isSubtotalKey;
|
package/dist/core.mjs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
function getSubtotalKeys(key) {
|
|
2
|
+
const result = [];
|
|
3
|
+
for (let i = 1; i <= key.length; i++) {
|
|
4
|
+
result.push(key.slice(0, i));
|
|
5
|
+
}
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
8
|
+
function isSubtotalKey(key, maxDepth) {
|
|
9
|
+
return key.length < maxDepth;
|
|
10
|
+
}
|
|
11
|
+
function generateRowKeysWithSubtotals(rowKeys, rowAttrsCount, collapsedKeys = /* @__PURE__ */ new Set()) {
|
|
12
|
+
if (rowAttrsCount <= 1) {
|
|
13
|
+
return rowKeys;
|
|
14
|
+
}
|
|
15
|
+
const result = [];
|
|
16
|
+
const addedSubtotals = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const rowKey of rowKeys) {
|
|
18
|
+
for (let level = 1; level < rowKey.length; level++) {
|
|
19
|
+
const subtotalKey = rowKey.slice(0, level);
|
|
20
|
+
const subtotalKeyStr = JSON.stringify(subtotalKey);
|
|
21
|
+
if (!addedSubtotals.has(subtotalKeyStr)) {
|
|
22
|
+
addedSubtotals.add(subtotalKeyStr);
|
|
23
|
+
const isCollapsed = collapsedKeys.has(subtotalKeyStr);
|
|
24
|
+
result.push({
|
|
25
|
+
key: subtotalKey,
|
|
26
|
+
isSubtotal: true,
|
|
27
|
+
level,
|
|
28
|
+
isCollapsed
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let isHidden = false;
|
|
33
|
+
for (let level = 1; level < rowKey.length; level++) {
|
|
34
|
+
const parentKey = rowKey.slice(0, level);
|
|
35
|
+
if (collapsedKeys.has(JSON.stringify(parentKey))) {
|
|
36
|
+
isHidden = true;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!isHidden) {
|
|
41
|
+
result.push({
|
|
42
|
+
key: rowKey,
|
|
43
|
+
isSubtotal: false,
|
|
44
|
+
level: rowKey.length,
|
|
45
|
+
isCollapsed: false
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function generateColKeysWithSubtotals(colKeys, colAttrsCount, collapsedKeys = /* @__PURE__ */ new Set()) {
|
|
52
|
+
if (colAttrsCount <= 1) {
|
|
53
|
+
return colKeys;
|
|
54
|
+
}
|
|
55
|
+
const result = [];
|
|
56
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const colKey of colKeys) {
|
|
58
|
+
for (let level = 1; level <= colKey.length; level++) {
|
|
59
|
+
const partialKey = colKey.slice(0, level);
|
|
60
|
+
const keyStr = JSON.stringify(partialKey);
|
|
61
|
+
if (!grouped.has(keyStr)) {
|
|
62
|
+
grouped.set(keyStr, {
|
|
63
|
+
key: partialKey,
|
|
64
|
+
isSubtotal: level < colKey.length,
|
|
65
|
+
level
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const sortedKeys = Array.from(grouped.values()).sort((a, b) => {
|
|
71
|
+
for (let i = 0; i < Math.min(a.key.length, b.key.length); i++) {
|
|
72
|
+
if (a.key[i] < b.key[i])
|
|
73
|
+
return -1;
|
|
74
|
+
if (a.key[i] > b.key[i])
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
return a.key.length - b.key.length;
|
|
78
|
+
});
|
|
79
|
+
for (const item of sortedKeys) {
|
|
80
|
+
const keyStr = JSON.stringify(item.key);
|
|
81
|
+
const isCollapsed = collapsedKeys.has(keyStr);
|
|
82
|
+
let isHidden = false;
|
|
83
|
+
for (let level = 1; level < item.key.length; level++) {
|
|
84
|
+
const parentKey = item.key.slice(0, level);
|
|
85
|
+
if (collapsedKeys.has(JSON.stringify(parentKey))) {
|
|
86
|
+
isHidden = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!isHidden) {
|
|
91
|
+
result.push({
|
|
92
|
+
key: item.key,
|
|
93
|
+
isSubtotal: item.isSubtotal,
|
|
94
|
+
level: item.level,
|
|
95
|
+
isCollapsed
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function createSubtotalAggregatorGetter(pivotData) {
|
|
102
|
+
const subtotalCache = /* @__PURE__ */ new Map();
|
|
103
|
+
return function getAggregator(rowKey, colKey) {
|
|
104
|
+
const isFullRowKey = rowKey.length === pivotData.props.rows.length;
|
|
105
|
+
const isFullColKey = colKey.length === pivotData.props.cols.length;
|
|
106
|
+
if (isFullRowKey && isFullColKey) {
|
|
107
|
+
return pivotData.getAggregator(rowKey, colKey);
|
|
108
|
+
}
|
|
109
|
+
const cacheKey = JSON.stringify({ row: rowKey, col: colKey });
|
|
110
|
+
if (subtotalCache.has(cacheKey)) {
|
|
111
|
+
return subtotalCache.get(cacheKey);
|
|
112
|
+
}
|
|
113
|
+
const matchingRowKeys = pivotData.getRowKeys().filter((rk) => {
|
|
114
|
+
if (rowKey.length === 0)
|
|
115
|
+
return true;
|
|
116
|
+
for (let i = 0; i < rowKey.length; i++) {
|
|
117
|
+
if (rk[i] !== rowKey[i])
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
const matchingColKeys = pivotData.getColKeys().filter((ck) => {
|
|
123
|
+
if (colKey.length === 0)
|
|
124
|
+
return true;
|
|
125
|
+
for (let i = 0; i < colKey.length; i++) {
|
|
126
|
+
if (ck[i] !== colKey[i])
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
});
|
|
131
|
+
let totalValue = 0;
|
|
132
|
+
let count = 0;
|
|
133
|
+
for (const rk of matchingRowKeys) {
|
|
134
|
+
for (const ck of matchingColKeys) {
|
|
135
|
+
const agg = pivotData.getAggregator(rk, ck);
|
|
136
|
+
const val = agg.value();
|
|
137
|
+
if (val !== null && val !== void 0 && !isNaN(val)) {
|
|
138
|
+
totalValue += val;
|
|
139
|
+
count++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const subtotalAgg = {
|
|
144
|
+
value: () => count > 0 ? totalValue : null,
|
|
145
|
+
format: pivotData.getAggregator([], []).format || ((v) => v)
|
|
146
|
+
};
|
|
147
|
+
subtotalCache.set(cacheKey, subtotalAgg);
|
|
148
|
+
return subtotalAgg;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
createSubtotalAggregatorGetter,
|
|
153
|
+
generateColKeysWithSubtotals,
|
|
154
|
+
generateRowKeysWithSubtotals,
|
|
155
|
+
getSubtotalKeys,
|
|
156
|
+
isSubtotalKey
|
|
157
|
+
};
|