@vue-pivottable/multi-value-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/dist/vue2.mjs ADDED
@@ -0,0 +1,399 @@
1
+ import { PivotUtilities } from "vue-pivottable";
2
+ import { c as createMultiValueAggregator } from "./MultiValuePivotData.js";
3
+ const { PivotData } = PivotUtilities;
4
+ function redColorScaleGenerator(values) {
5
+ const numericValues = values.filter((v) => typeof v === "number" && !isNaN(v));
6
+ if (numericValues.length === 0) {
7
+ return () => ({});
8
+ }
9
+ const min = Math.min(...numericValues);
10
+ const max = Math.max(...numericValues);
11
+ return (x) => {
12
+ if (typeof x !== "number" || isNaN(x) || max === min) {
13
+ return {};
14
+ }
15
+ const nonRed = 255 - Math.round(255 * (x - min) / (max - min));
16
+ return { backgroundColor: `rgb(255,${nonRed},${nonRed})` };
17
+ };
18
+ }
19
+ function makeMultiValueRenderer(opts = {}) {
20
+ return {
21
+ name: opts.name || "vue2-multi-value-table",
22
+ props: {
23
+ // Data props
24
+ data: {
25
+ type: [Array, Object, Function],
26
+ required: true
27
+ },
28
+ rows: {
29
+ type: Array,
30
+ default: () => []
31
+ },
32
+ cols: {
33
+ type: Array,
34
+ default: () => []
35
+ },
36
+ vals: {
37
+ type: Array,
38
+ default: () => []
39
+ },
40
+ // Multi-value specific props
41
+ aggregatorMap: {
42
+ type: Object,
43
+ default: () => ({})
44
+ },
45
+ aggregators: {
46
+ type: Object,
47
+ required: true
48
+ },
49
+ // Filter and sort props
50
+ valueFilter: {
51
+ type: Object,
52
+ default: () => ({})
53
+ },
54
+ sorters: {
55
+ type: [Object, Function],
56
+ default: () => ({})
57
+ },
58
+ derivedAttributes: {
59
+ type: [Object, Function],
60
+ default: () => ({})
61
+ },
62
+ rowOrder: {
63
+ type: String,
64
+ default: "key_a_to_z"
65
+ },
66
+ colOrder: {
67
+ type: String,
68
+ default: "key_a_to_z"
69
+ },
70
+ // Display props
71
+ rowTotal: {
72
+ type: Boolean,
73
+ default: true
74
+ },
75
+ colTotal: {
76
+ type: Boolean,
77
+ default: true
78
+ },
79
+ tableColorScaleGenerator: {
80
+ type: Function,
81
+ default: () => redColorScaleGenerator
82
+ },
83
+ tableOptions: {
84
+ type: Object,
85
+ default: () => ({})
86
+ },
87
+ localeStrings: {
88
+ type: Object,
89
+ default: () => ({ totals: "Totals" })
90
+ },
91
+ labels: {
92
+ type: Object,
93
+ default: () => ({})
94
+ },
95
+ // Multi-value display options
96
+ cellLayout: {
97
+ type: String,
98
+ default: "vertical",
99
+ validator: (v) => ["vertical", "horizontal", "compact"].includes(v)
100
+ },
101
+ showValueLabels: {
102
+ type: Boolean,
103
+ default: true
104
+ },
105
+ valueLabels: {
106
+ type: Object,
107
+ default: () => ({})
108
+ }
109
+ },
110
+ methods: {
111
+ /**
112
+ * Apply label transformation to a value
113
+ */
114
+ applyLabel(attr, value) {
115
+ if (this.labels && typeof this.labels[attr] === "function") {
116
+ return this.labels[attr](value);
117
+ }
118
+ return value;
119
+ },
120
+ /**
121
+ * Get display label for a value column
122
+ */
123
+ getValueLabel(valName) {
124
+ if (this.valueLabels && this.valueLabels[valName]) {
125
+ return this.valueLabels[valName];
126
+ }
127
+ return valName;
128
+ },
129
+ /**
130
+ * Calculate row/col span for merged cells
131
+ */
132
+ spanSize(arr, i, j) {
133
+ let x;
134
+ if (i !== 0) {
135
+ let noDraw = true;
136
+ for (x = 0; x <= j; x++) {
137
+ if (arr[i - 1][x] !== arr[i][x]) {
138
+ noDraw = false;
139
+ }
140
+ }
141
+ if (noDraw) return -1;
142
+ }
143
+ let len = 0;
144
+ while (i + len < arr.length) {
145
+ let stop = false;
146
+ for (x = 0; x <= j; x++) {
147
+ if (arr[i][x] !== arr[i + len][x]) {
148
+ stop = true;
149
+ }
150
+ }
151
+ if (stop) break;
152
+ len++;
153
+ }
154
+ return len;
155
+ },
156
+ /**
157
+ * Format a single value using the appropriate aggregator
158
+ */
159
+ formatValue(valName, value) {
160
+ const aggName = this.aggregatorMap[valName] || "Sum";
161
+ const agg = this.aggregators[aggName];
162
+ if (agg) {
163
+ try {
164
+ const instance = agg([valName])();
165
+ if (instance && instance.format) {
166
+ return instance.format(value);
167
+ }
168
+ } catch (e) {
169
+ }
170
+ }
171
+ if (value === null || value === void 0) return "";
172
+ if (typeof value === "number") {
173
+ return value.toLocaleString();
174
+ }
175
+ return String(value);
176
+ },
177
+ /**
178
+ * Render multi-value cell content
179
+ */
180
+ renderMultiValueCell(h, values, rowKey, colKey) {
181
+ if (!values || typeof values !== "object") {
182
+ return String(values || "");
183
+ }
184
+ const items = this.vals.map((val) => {
185
+ const value = values[val];
186
+ const formatted = this.formatValue(val, value);
187
+ const label = this.getValueLabel(val);
188
+ const aggName = this.aggregatorMap[val] || "Sum";
189
+ if (this.cellLayout === "compact") {
190
+ return formatted;
191
+ }
192
+ return h("div", {
193
+ staticClass: "multi-value-item",
194
+ key: val,
195
+ attrs: {
196
+ "data-value": val,
197
+ "data-aggregator": aggName
198
+ }
199
+ }, [
200
+ this.showValueLabels ? h("span", {
201
+ staticClass: "multi-value-label"
202
+ }, `${label}: `) : null,
203
+ h("span", {
204
+ staticClass: ["multi-value-value"]
205
+ }, formatted)
206
+ ]);
207
+ });
208
+ if (this.cellLayout === "compact") {
209
+ return items.join(" / ");
210
+ }
211
+ return h("div", {
212
+ staticClass: ["multi-value-cell", `layout-${this.cellLayout}`]
213
+ }, items);
214
+ },
215
+ /**
216
+ * Create PivotData-like structure with multi-value aggregation
217
+ */
218
+ createPivotData() {
219
+ const multiValueAgg = createMultiValueAggregator(
220
+ this.aggregatorMap,
221
+ this.aggregators,
222
+ this.vals
223
+ );
224
+ const modifiedAggregators = {
225
+ ...this.aggregators,
226
+ "Multi-Value": () => multiValueAgg
227
+ };
228
+ return new PivotData({
229
+ data: this.data,
230
+ rows: this.rows,
231
+ cols: this.cols,
232
+ vals: this.vals,
233
+ aggregators: modifiedAggregators,
234
+ aggregatorName: "Multi-Value",
235
+ valueFilter: this.valueFilter,
236
+ sorters: this.sorters,
237
+ derivedAttributes: this.derivedAttributes,
238
+ rowOrder: this.rowOrder,
239
+ colOrder: this.colOrder
240
+ });
241
+ }
242
+ },
243
+ render(h) {
244
+ let pivotData;
245
+ try {
246
+ pivotData = this.createPivotData();
247
+ } catch (error) {
248
+ console.error("Multi-Value Renderer Error:", error);
249
+ return h("div", {
250
+ staticClass: ["pvtError"]
251
+ }, `Error: ${error.message}`);
252
+ }
253
+ const colAttrs = pivotData.props.cols;
254
+ const rowAttrs = pivotData.props.rows;
255
+ const rowKeys = pivotData.getRowKeys();
256
+ const colKeys = pivotData.getColKeys();
257
+ const grandTotalAggregator = pivotData.getAggregator([], []);
258
+ const getClickHandler = (value, rowValues, colValues) => {
259
+ if (this.tableOptions && this.tableOptions.clickCallback) {
260
+ const filters = {};
261
+ colAttrs.forEach((attr, i) => {
262
+ if (colValues[i] !== null) {
263
+ filters[attr] = colValues[i];
264
+ }
265
+ });
266
+ rowAttrs.forEach((attr, i) => {
267
+ if (rowValues[i] !== null) {
268
+ filters[attr] = rowValues[i];
269
+ }
270
+ });
271
+ return (e) => this.tableOptions.clickCallback(e, value, filters, pivotData);
272
+ }
273
+ return null;
274
+ };
275
+ return h("table", {
276
+ staticClass: ["pvtTable", "pvtMultiValueTable"]
277
+ }, [
278
+ // THEAD
279
+ h("thead", [
280
+ // Column attribute headers
281
+ colAttrs.map((c, j) => {
282
+ return h("tr", { key: `colAttrs${j}` }, [
283
+ // Top-left corner cell
284
+ j === 0 && rowAttrs.length !== 0 ? h("th", {
285
+ attrs: {
286
+ colSpan: rowAttrs.length,
287
+ rowSpan: colAttrs.length
288
+ }
289
+ }) : void 0,
290
+ // Column attribute label
291
+ h("th", { staticClass: ["pvtAxisLabel"] }, c),
292
+ // Column keys
293
+ colKeys.map((colKey, i) => {
294
+ const x = this.spanSize(colKeys, i, j);
295
+ if (x === -1) return null;
296
+ return h("th", {
297
+ staticClass: ["pvtColLabel"],
298
+ attrs: {
299
+ key: `colKey${i}`,
300
+ colSpan: x,
301
+ rowSpan: j === colAttrs.length - 1 && rowAttrs.length !== 0 ? 2 : 1
302
+ }
303
+ }, this.applyLabel(colAttrs[j], colKey[j]));
304
+ }),
305
+ // Totals header
306
+ j === 0 && this.rowTotal ? h("th", {
307
+ staticClass: ["pvtTotalLabel"],
308
+ attrs: {
309
+ rowSpan: colAttrs.length + (rowAttrs.length === 0 ? 0 : 1)
310
+ }
311
+ }, this.localeStrings.totals) : void 0
312
+ ]);
313
+ }),
314
+ // Row attribute labels row
315
+ rowAttrs.length !== 0 ? h("tr", [
316
+ rowAttrs.map((r, i) => {
317
+ return h("th", {
318
+ staticClass: ["pvtAxisLabel"],
319
+ key: `rowAttr${i}`
320
+ }, r);
321
+ }),
322
+ this.rowTotal ? h(
323
+ "th",
324
+ { staticClass: ["pvtTotalLabel"] },
325
+ colAttrs.length === 0 ? this.localeStrings.totals : null
326
+ ) : colAttrs.length === 0 ? void 0 : h("th")
327
+ ]) : void 0
328
+ ]),
329
+ // TBODY
330
+ h("tbody", [
331
+ // Data rows
332
+ rowKeys.map((rowKey, i) => {
333
+ const totalAggregator = pivotData.getAggregator(rowKey, []);
334
+ return h("tr", { key: `rowKeyRow${i}` }, [
335
+ // Row labels
336
+ rowKey.map((text, j) => {
337
+ const x = this.spanSize(rowKeys, i, j);
338
+ if (x === -1) return null;
339
+ return h("th", {
340
+ staticClass: ["pvtRowLabel"],
341
+ attrs: {
342
+ key: `rowKeyLabel${i}-${j}`,
343
+ rowSpan: x,
344
+ colSpan: j === rowAttrs.length - 1 && colAttrs.length !== 0 ? 2 : 1
345
+ }
346
+ }, this.applyLabel(rowAttrs[j], text));
347
+ }),
348
+ // Data cells
349
+ colKeys.map((colKey, j) => {
350
+ const aggregator = pivotData.getAggregator(rowKey, colKey);
351
+ const value = aggregator.value();
352
+ const clickHandler = getClickHandler(value, rowKey, colKey);
353
+ return h("td", {
354
+ staticClass: ["pvVal", "pvtMultiVal"],
355
+ attrs: { key: `pvtVal${i}-${j}` },
356
+ on: clickHandler ? { click: clickHandler } : {}
357
+ }, [this.renderMultiValueCell(h, value, rowKey, colKey)]);
358
+ }),
359
+ // Row total
360
+ this.rowTotal ? h("td", {
361
+ staticClass: ["pvtTotal", "pvtMultiVal"],
362
+ on: getClickHandler(totalAggregator.value(), rowKey, []) ? { click: getClickHandler(totalAggregator.value(), rowKey, []) } : {}
363
+ }, [this.renderMultiValueCell(h, totalAggregator.value(), rowKey, [])]) : void 0
364
+ ]);
365
+ }),
366
+ // Column totals row
367
+ h("tr", [
368
+ this.colTotal ? h("th", {
369
+ staticClass: ["pvtTotalLabel"],
370
+ attrs: {
371
+ colSpan: rowAttrs.length + (colAttrs.length === 0 ? 0 : 1)
372
+ }
373
+ }, this.localeStrings.totals) : void 0,
374
+ this.colTotal ? colKeys.map((colKey, i) => {
375
+ const totalAggregator = pivotData.getAggregator([], colKey);
376
+ const clickHandler = getClickHandler(totalAggregator.value(), [], colKey);
377
+ return h("td", {
378
+ staticClass: ["pvtTotal", "pvtMultiVal"],
379
+ attrs: { key: `total${i}` },
380
+ on: clickHandler ? { click: clickHandler } : {}
381
+ }, [this.renderMultiValueCell(h, totalAggregator.value(), [], colKey)]);
382
+ }) : void 0,
383
+ this.colTotal && this.rowTotal ? h("td", {
384
+ staticClass: ["pvtGrandTotal", "pvtMultiVal"],
385
+ on: getClickHandler(grandTotalAggregator.value(), [], []) ? { click: getClickHandler(grandTotalAggregator.value(), [], []) } : {}
386
+ }, [this.renderMultiValueCell(h, grandTotalAggregator.value(), [], [])]) : void 0
387
+ ])
388
+ ])
389
+ ]);
390
+ }
391
+ };
392
+ }
393
+ const MultiValueTableRenderer = {
394
+ "Multi-Value Table": makeMultiValueRenderer({ name: "vue2-multi-value-table" })
395
+ };
396
+ export {
397
+ MultiValueTableRenderer as MultiValueRenderers,
398
+ makeMultiValueRenderer
399
+ };
package/dist/vue3.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const MultiValueTableRenderer = require("./MultiValueTableRenderer.js");
4
+ exports.MultiValueRenderers = MultiValueTableRenderer.MultiValueTableRenderer;
5
+ exports.makeMultiValueRenderer = MultiValueTableRenderer.makeMultiValueRenderer;
package/dist/vue3.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { M, m } from "./MultiValueTableRenderer.js";
2
+ export {
3
+ M as MultiValueRenderers,
4
+ m as makeMultiValueRenderer
5
+ };
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@vue-pivottable/multi-value-renderer",
3
+ "version": "0.1.0",
4
+ "description": "Multi-value aggregator renderer for vue-pivottable (Vue 2/3 compatible)",
5
+ "author": "Seungwoo321",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./types/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./types/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ },
17
+ "./vue2": {
18
+ "types": "./types/index.d.ts",
19
+ "import": "./dist/vue2.mjs",
20
+ "require": "./dist/vue2.js"
21
+ },
22
+ "./vue3": {
23
+ "types": "./types/index.d.ts",
24
+ "import": "./dist/vue3.mjs",
25
+ "require": "./dist/vue3.js"
26
+ },
27
+ "./dist/styles.css": "./dist/styles.css",
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "types",
33
+ "LICENSE",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "clean": "rimraf dist",
38
+ "build": "vite build && cp src/styles.css dist/styles.css",
39
+ "build:all": "npm run clean && npm run build",
40
+ "test": "vitest run",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "peerDependencies": {
44
+ "vue": "^2.7.0 || ^3.0.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "vue": {
48
+ "optional": false
49
+ }
50
+ },
51
+ "devDependencies": {
52
+ "@vitejs/plugin-vue": "^5.0.0",
53
+ "release-it": "^17.0.0",
54
+ "rimraf": "^5.0.0",
55
+ "typescript": "^5.0.0",
56
+ "vite": "^5.0.0",
57
+ "vitest": "^1.0.0"
58
+ },
59
+ "keywords": [
60
+ "vue",
61
+ "vue2",
62
+ "vue3",
63
+ "pivot",
64
+ "pivottable",
65
+ "vue-pivottable",
66
+ "aggregator",
67
+ "multi-value",
68
+ "renderer"
69
+ ],
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/vue-pivottable/multi-value-renderer.git"
73
+ },
74
+ "bugs": {
75
+ "url": "https://github.com/vue-pivottable/multi-value-renderer/issues"
76
+ },
77
+ "homepage": "https://github.com/vue-pivottable/multi-value-renderer#readme",
78
+ "publishConfig": {
79
+ "access": "public"
80
+ }
81
+ }
@@ -0,0 +1,170 @@
1
+ import type { Component, DefineComponent } from 'vue'
2
+
3
+ // ============================================================================
4
+ // Core Types
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Map of value column names to aggregator names
9
+ * @example { sales: 'Sum', quantity: 'Average' }
10
+ */
11
+ export type AggregatorMap = Record<string, string>
12
+
13
+ /**
14
+ * Aggregator result interface
15
+ */
16
+ export interface AggregatorResult {
17
+ push(record: Record<string, any>): void
18
+ value(): any
19
+ format(value: any): string
20
+ numInputs?: number
21
+ }
22
+
23
+ /**
24
+ * Aggregator factory function
25
+ */
26
+ export type AggregatorFactory = (
27
+ data?: any,
28
+ rowKey?: string[],
29
+ colKey?: string[]
30
+ ) => AggregatorResult
31
+
32
+ /**
33
+ * Aggregator definition
34
+ */
35
+ export type Aggregator = (attributes: string[]) => AggregatorFactory
36
+
37
+ /**
38
+ * Collection of aggregators
39
+ */
40
+ export type Aggregators = Record<string, Aggregator>
41
+
42
+ // ============================================================================
43
+ // Multi-Value Aggregator
44
+ // ============================================================================
45
+
46
+ /**
47
+ * Multi-value aggregator result with per-value access
48
+ */
49
+ export interface MultiValueAggregatorResult extends AggregatorResult {
50
+ valueOf(valName: string): any
51
+ formatOf(valName: string, value: any): string
52
+ getSubAggregator(valName: string): AggregatorResult | null
53
+ }
54
+
55
+ /**
56
+ * Creates a multi-value aggregator factory
57
+ */
58
+ export function createMultiValueAggregator(
59
+ aggregatorMap: AggregatorMap,
60
+ aggregators: Aggregators,
61
+ vals: string[]
62
+ ): AggregatorFactory
63
+
64
+ /**
65
+ * Legacy alias for createMultiValueAggregator
66
+ */
67
+ export function createMultiAggregator(
68
+ aggregatorMap: AggregatorMap,
69
+ aggregators: Aggregators
70
+ ): (vals: string[]) => AggregatorFactory
71
+
72
+ /**
73
+ * Default color scale generator for heatmaps
74
+ */
75
+ export function defaultColorScaleGenerator(
76
+ values: number[]
77
+ ): (value: number) => { backgroundColor: string }
78
+
79
+ /**
80
+ * Validates an aggregator map configuration
81
+ */
82
+ export function validateAggregatorMap(
83
+ aggregatorMap: AggregatorMap,
84
+ aggregators: Aggregators
85
+ ): {
86
+ valid: boolean
87
+ errors: string[]
88
+ }
89
+
90
+ // ============================================================================
91
+ // Renderer Props
92
+ // ============================================================================
93
+
94
+ /**
95
+ * Cell layout options for multi-value display
96
+ */
97
+ export type CellLayout = 'vertical' | 'horizontal' | 'compact'
98
+
99
+ /**
100
+ * Props for the multi-value renderer
101
+ */
102
+ export interface MultiValueRendererProps {
103
+ // Data props
104
+ data: any[] | Record<string, any>[] | ((callback: (record: Record<string, any>) => void) => void)
105
+ rows?: string[]
106
+ cols?: string[]
107
+ vals?: string[]
108
+
109
+ // Multi-value specific
110
+ aggregatorMap?: AggregatorMap
111
+ aggregators: Aggregators
112
+
113
+ // Filter and sort
114
+ valueFilter?: Record<string, Record<string, boolean>>
115
+ sorters?: Record<string, (a: any, b: any) => number> | ((attr: string) => (a: any, b: any) => number)
116
+ derivedAttributes?: Record<string, (record: Record<string, any>) => any>
117
+ rowOrder?: 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a'
118
+ colOrder?: 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a'
119
+
120
+ // Display options
121
+ rowTotal?: boolean
122
+ colTotal?: boolean
123
+ tableColorScaleGenerator?: (values: number[]) => (value: number) => { backgroundColor: string }
124
+ tableOptions?: {
125
+ clickCallback?: (
126
+ event: MouseEvent,
127
+ value: any,
128
+ filters: Record<string, any>,
129
+ pivotData: any
130
+ ) => void
131
+ }
132
+ localeStrings?: {
133
+ totals?: string
134
+ }
135
+ labels?: Record<string, (value: any) => any>
136
+
137
+ // Multi-value display options
138
+ cellLayout?: CellLayout
139
+ showValueLabels?: boolean
140
+ valueLabels?: Record<string, string>
141
+ }
142
+
143
+ // ============================================================================
144
+ // Renderer Factory
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Options for creating a multi-value renderer
149
+ */
150
+ export interface MultiValueRendererOptions {
151
+ name?: string
152
+ }
153
+
154
+ /**
155
+ * Creates a multi-value table renderer component
156
+ */
157
+ export function makeMultiValueRenderer(
158
+ opts?: MultiValueRendererOptions
159
+ ): DefineComponent<MultiValueRendererProps>
160
+
161
+ // ============================================================================
162
+ // Exports
163
+ // ============================================================================
164
+
165
+ /**
166
+ * Pre-configured multi-value renderers
167
+ */
168
+ export const MultiValueRenderers: Record<string, Component>
169
+
170
+ export default MultiValueRenderers