@rokkit/data 1.0.0-next.100

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/src/rollup.js ADDED
@@ -0,0 +1,138 @@
1
+ import { omit, pick, mergeLeft, map, difference, uniq, pipe } from 'ramda'
2
+
3
+ /**
4
+ * Groups data by specified keys.
5
+ *
6
+ * @param {Array} data - An array of data points.
7
+ * @param {Array} groupByKeys - Keys to group the data by.
8
+ * @param {Array} summaries - An array of summary objects to define aggregation operations.
9
+ * @returns {Object} Grouped data object with aggregation placeholders.
10
+ */
11
+ export function groupDataByKeys(data, groupByKeys, summaries) {
12
+ const keysExtractor = pick(groupByKeys)
13
+ const groupedData = data.reduce((grouped, row) => {
14
+ const key = JSON.stringify(keysExtractor(row))
15
+ if (!grouped[key]) {
16
+ grouped[key] = { ...keysExtractor(row), ...initialValues(summaries) }
17
+ }
18
+ addToSummaries(grouped[key], row, summaries)
19
+ return grouped
20
+ }, {})
21
+
22
+ return Object.values(groupedData)
23
+ }
24
+
25
+ /**
26
+ * Sets initial values for summaries.
27
+ *
28
+ * @param {Array} summaries - An array of summary objects.
29
+ * @returns {Object} An object with keys for each summary initialized to empty arrays.
30
+ */
31
+ export function initialValues(summaries) {
32
+ const fields = summaries.map(({ reducers }) => reducers.map((r) => r.field)).flat()
33
+ return fields.reduce((acc, name) => ({ ...acc, [name]: [] }), {})
34
+ }
35
+
36
+ /**
37
+ * Adds data to summary aggregation arrays.
38
+ *
39
+ * @param {Object} group - The group object to add data to.
40
+ * @param {Object} row - The data row being processed.
41
+ * @param {Array} summaries - An array of summary objects.
42
+ */
43
+ export function addToSummaries(group, row, summaries) {
44
+ summaries.forEach(({ mapper, reducers }) => {
45
+ const value = mapper(row)
46
+ reducers.forEach((r) => {
47
+ group[r.field].push(value)
48
+ })
49
+ })
50
+ }
51
+
52
+ /**
53
+ * Fills grouped data with aligned data if specified in configuration.
54
+ *
55
+ * @param {Object} groupedData - The grouped data object.
56
+ * @param {Object} config - Configuration object containing alignment settings.
57
+ * @returns {Array} Aligned data array.
58
+ */
59
+ export function fillAlignedData(groupedData, config, fillRowsFunc) {
60
+ // const fillRowsFunc = getAlignGenerator(df.data, config)
61
+ const { actual_flag, children } = config
62
+ return Object.values(groupedData).map((row) => ({
63
+ ...row,
64
+ [children]: [
65
+ ...row[children].map((value) => ({ ...value, [actual_flag]: 1 })),
66
+ ...fillRowsFunc(row[children])
67
+ ]
68
+ }))
69
+ }
70
+
71
+ /**
72
+ * Performs aggregation operations on grouped data.
73
+ *
74
+ * @param {Array} dataArray - An array of grouped data with aggregation arrays.
75
+ * @param {Array} summaries - An array of summary objects with reducer functions.
76
+ * @returns {Array} Aggregated data array with computed summary values.
77
+ */
78
+ export function aggregateData(dataArray, summaries) {
79
+ const fields = summaries.map(({ name, reducers }) => reducers.map((r) => ({ name, ...r }))).flat()
80
+ return dataArray.map((row) => ({
81
+ ...row,
82
+ ...fields.reduce(
83
+ (acc, { field, formula }) => ({
84
+ ...acc,
85
+ [field]: formula(row[field])
86
+ }),
87
+ {}
88
+ )
89
+ }))
90
+ }
91
+
92
+ /**
93
+ * Creates a generator that produces missing rows in a dataset based on specified columns.
94
+ * The function determines all unique combinations of the specified columns in config.align_by
95
+ * and returns a generator function to create the missing combinations, potentially with default
96
+ * values for other columns.
97
+ * @param {Array<Object>} data - The array of objects representing the dataset.
98
+ * @param {Object} config - The configuration object.
99
+ *
100
+ * @returns {Function} A generator function that when called, produces the missing rows.
101
+ */
102
+ export function getAlignGenerator(data, config) {
103
+ const { align_by, group_by, actual_flag } = config
104
+ const template = { ...omit([...align_by, ...group_by], config.template), [actual_flag]: 0 }
105
+ const subset = pipe(map(pick(align_by)), uniq)(data)
106
+
107
+ return pipe(map(pick(align_by)), uniq, difference(subset), map(mergeLeft(template)))
108
+ }
109
+
110
+ /**
111
+ * Returns an aggregator object with a mapper and reducer function.
112
+ *
113
+ * @param {Array<string>} from - The key or keys to aggregate.
114
+ * @param {Function} using - The aggregation function.
115
+ * @returns {import('./types').SummaryConfig} - An object with a mapper and reducer function.
116
+ */
117
+ // export function getAggregator(from, into, using = identity) {
118
+ // // const mapper = pick(Array.isArray ? from : [from])
119
+ // return {
120
+ // mapper: pick(from),
121
+ // reducers: [{ field: into, formula: using }]
122
+ // }
123
+ // }
124
+
125
+ /**
126
+ * Returns the default aggregator which rolls up the columns other than the group_by columns into a single object.
127
+ *
128
+ * @param {import('./types').Metadata} metadata - The metadata for the columns to be aggregated.
129
+ * @param {Object} config - The configuration used to build the aggregator.
130
+ *
131
+ * @returns {Object} An object containing the default aggregator for the specified metadata and configuration.
132
+ */
133
+ // export function defaultAggregator(metadata, config) {
134
+ // const child = metadata.filter((col) => !config.group_by.includes(col.name))
135
+ // const keys = child.map((col) => col.name)
136
+
137
+ // return { ...getAggregator(keys, config.children), metadata: child }
138
+ // }
package/src/types.js ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @typedef {import('@rokkit/core').FieldMapping} FieldMapping
3
+ * @typedef {import('@rokkit/core').SortOptions} SortOptions
4
+ * @typedef {import('@rokkit/core').HorizontalAlignOptions} HorizontalAlignOptions
5
+ * @typedef {import('@rokkit/core').ActionTypes} ActionTypes
6
+ * @typedef {import('@rokkit/core').SelectionState} SelectionState
7
+ */
8
+
9
+ /**
10
+ * @typedef {Object} ColumnSorter
11
+ * @property {string} name
12
+ * @property {function} sorter
13
+ *
14
+ * @typedef {string|[string,boolean]|ColumnSorter} SortableColumn
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} ColumnAggregator
19
+ * @property {string} name
20
+ * @property {function} aggregator
21
+ * @property {string} suffix
22
+ */
23
+
24
+ /**
25
+ * Options for joining two data sets
26
+ *
27
+ * @typedef OptionsToRenameKeys
28
+ * @property {string} [prefix] - Prefix to be added to each attribute
29
+ * @property {string} [suffix] - Suffix to be added to each attribute
30
+ * @property {string} [separator='_'] - Separator to be used when adding prefix or suffix. defaults to _
31
+ */
32
+
33
+ /**
34
+ * Column metadata for the table.
35
+ *
36
+ * @typedef {Object} ColumnMetadata
37
+ *
38
+ * @property {string} name - The name of the column.
39
+ * @property {string} type - The data type of the column (e.g., "string", "number", "date").
40
+ * @property {FieldMapping} [field] - The field mapping for the column.
41
+ * @property {number} [digits=0] - The number of digits for numeric values (defaults to 0).
42
+ * @property {Function} formatter - A function to format the column value.
43
+ * @property {boolean} [sortable] - Indicates if the column is sortable (true/false).
44
+ * @property {SortOptions} [sortOrder] - The sort order of the column.
45
+ * @property {HorizontalAlignOptions} [align] - The alignment of the column content.
46
+ * @property {ActionTypes} [action] - Action attribute for action columns.
47
+ */
48
+
49
+ /**
50
+ * @typedef {Array<ColumnMetadata>} Metadata
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object<string,number} ColumnIndexMap
55
+ */
56
+
57
+ /**
58
+ * Track the state of a row in the table.
59
+ *
60
+ * @typedef {Object} TreeTableNode
61
+ * @property {number} row - Reference to actual row in the data.
62
+ * @property {number} depth - The depth of the node in the hierarchy.
63
+ * @property {string} [value] - The value of the hierarchy node.
64
+ * @property {boolean} [isHidden] - Indicates whether the node is visible (true/false).
65
+ * @property {boolean} [isParent] - Indicates if this node is a parent (true/false).
66
+ * @property {boolean} [isExpanded] - Indicates whether the node is expanded (true/false).
67
+ * @property {number} [parent] - Reference to the parent node in the flat list.
68
+ * @property {SelectionState} [selected] - Indicates whether the node is selected (true/false/indeterminate).
69
+ * @property {Array<any>} children - Reference to the children nodes in the flat list.
70
+ */
71
+
72
+ /**
73
+ * Track the state of all rows in the table.
74
+ *
75
+ * @typedef {Object} TreeTable
76
+ * @property {TreeTableNode[]} rows - Flat list of hierarchy nodes.
77
+ */
78
+
79
+ /**
80
+ * @typedef {Object} OverrideConfig
81
+ * @property {string} actual_flag - the field used for identifying filler rows in the data
82
+ * @property {string} children - the field used for collecting children of a node in rollup
83
+ */
84
+
85
+ /**
86
+ * @typedef {Object} ReducerConfig
87
+ * @property {string} field - The target field to hold the reduced value
88
+ * @property {Function} using - The function to use for reducing the data
89
+ */
90
+
91
+ /**
92
+ * @typedef {Object} SummaryConfig
93
+ * @property {Function} mapper - A function used to collect the data for the summary
94
+ * @property {Array<ReducerConfig} reducers - Array of reducer configurations
95
+ * @property {Metadata} [metadata] - Array of column metadata for the summary
96
+ */
97
+
98
+ /**
99
+ * @typedef {Object} DataFrameConfig
100
+ * @property {Array<string>} group_by=[] - The columns to group by.
101
+ * @property {Array<string>} align_by=[] - The columns to be aligned during rollup.
102
+ * @property {Array<SummaryConfig>} summaries=[] - Array of summary configurations.
103
+ * @property {any} template={} - Template row to be used for filling in missing rows.
104
+ * @extends OverrideConfig
105
+ */
106
+
107
+ /**
108
+ * @typedef {Function} SortBy
109
+ * @description Sort the DataFrame by the specified columns.
110
+ * @param {SortableColumn} columns - The columns to sort by.
111
+ * @returns {DataFrame} - The sorted DataFrame.
112
+ */
113
+
114
+ /**
115
+ * DataFrame-like object with data manipulation methods.
116
+ *
117
+ * @typedef {Object} DataFrame
118
+ * @property {Array<Object>} data - Array of objects representing the rows of data.
119
+ * @property {Metadata} metadata - Array of column metadata for the data.
120
+ * @property {DataFrameConfig} config - Configuration options for the DataFrame.
121
+ * @property {ColumnIndexMap} columns - A map of column names to their index in the metadata.
122
+ * @property {function(OverrideConfig):DataFrame} override - Method to override the configuration of the DataFrame.
123
+ * @property {function(...string):DataFrame} groupBy - Method to group the DataFrame by specified columns.
124
+ * @property {function(function(any)):DataFrame} where - Sets the filter function for the DataFrame
125
+ * @property {function():DataFrame} align - Method to align the DataFrame.
126
+ * @property {function():DataFrame} using - set the template to be used for filling in missing rows.
127
+ * @property {function():DataFrame} summarize - add a summarizer to be used during rollup
128
+ * @property {function(...SortableColumn):DataFrame} sortBy - Method to sort the DataFrame by specified columns.
129
+ * @property {function():DataFrame} join - perform join with another DataFrame returning a new DataFrmae. Defaults to innerJoin, other join types can be specified.
130
+ * @property {function():DataFrame} innerJoin - perform an ineer join with another DataFrame and return a new DataFrame.
131
+ * @property {function():DataFrame} leftJoin - perform a left join with another DataFrame and return a new DataFrame.
132
+ * @property {function():DataFrame} rightJoin - perform a right join with another DataFrame returning a new DataFrame.
133
+ * @property {function():DataFrame} fullJoin - perform a full join with another DataFrame returning a new DataFrame.
134
+ * @property {function():DataFrame} nestedJoin - perform a nested join with another DataFrame returning a new DataFrame.
135
+ * @property {function():DataFrame} rename - Method to rename the columns of the DataFrame.
136
+ * @property {function():DataFrame} drop - Method to drop the columns of the DataFrame.
137
+ * @property {function():DataFrame} delete - Method to delete the rows of the DataFrame.
138
+ * @property {function():DataFrame} update - Method to update the rows of the DataFrame.
139
+ * @property {function():DataFrame} union - Method to combine the rows of the DataFrame with another DataFrame.
140
+ * @property {function():DataFrame} minus - Method to remove the rows of the DataFrame which are present in another DataFrame.
141
+ * @property {function():DataFrame} intersect - Method to keep the rows of the DataFrame which are present in another DataFrame.
142
+ * @property {function():DataFrame} rollup - Method to rollup the DataFrame.
143
+ * @property {function():Arrat<Object>} select - Method to select the columns of the DataFrame.
144
+
145
+ */
146
+
147
+ /**
148
+ * @typedef {inner|outer|full|nested} JoinType
149
+ */
150
+ /**
151
+ * Options for joining two data sets
152
+ *
153
+ * @typedef JoinOptions
154
+ * @property {boolean} [inner] - Flag indicating inner join
155
+ * @property {JoinType} [type='inner] - The join type to use (inner, outer, full, nested).
156
+ * @property {string} [prefix] - prefix to be used for renaming keys in the second data set.
157
+ * @property {string} [suffix] - suffix to be used for renaming keys in the second data set.
158
+ * @property {string} [separator='_'] - separator to be used for renaming keys in the second data set.
159
+ */
160
+
161
+ /**
162
+ * DataView
163
+ * @typedef {Object} DataView
164
+ * @property {TreeTable} hierarchy - The state of the rows in the table.
165
+ * @property {Metadata} columns - The metadata for the columns in the table.
166
+ * @property {Function} sortBy - Sort the DataView by the specified columns.
167
+ * @property {Function} clearSort - Clear the sorting of the DataView.
168
+ * @property {Function} filter - Filter the DataView by the specified columns.
169
+ * @property {Function} toggle - toggle expand or collapse the DataView by the specified columns.
170
+ * @property {Function} select - select the DataView by the specified columns.
171
+ */
172
+
173
+ export default {}
package/src/utils.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Check if a value is a date string
3
+ * @param {string} value
4
+ * @returns {boolean}
5
+ */
6
+ function isDateString(value) {
7
+ return !isNaN(Date.parse(value))
8
+ }
9
+
10
+ function deriveTypeFromEmptyValue(value) {
11
+ if (value === undefined || value === null) return 'string'
12
+ return typeof value
13
+ }
14
+ /**
15
+ * Infer the type of a value as array, date or the default type
16
+ * @param {any} value
17
+ * @returns {string}
18
+ */
19
+ function deriveArrayAndDateTypes(value) {
20
+ if (Array.isArray(value)) return 'array'
21
+ if (value instanceof Date) return 'date'
22
+
23
+ return deriveTypeFromEmptyValue(value)
24
+ }
25
+ /**
26
+ * Derive the type of a value
27
+ * @param {any} value
28
+ * @returns {string}
29
+ */
30
+ export function typeOf(value) {
31
+ const type = deriveArrayAndDateTypes(value)
32
+ if (type === 'string' && isDateString(value)) return 'date'
33
+ if (type === 'number' && Number.isInteger(value)) return 'integer'
34
+
35
+ return type
36
+ }
package/src/view.js ADDED
@@ -0,0 +1,181 @@
1
+ import { omit } from 'ramda'
2
+ import { get, writable } from 'svelte/store'
3
+ import { deriveMetadata, deriveSortableColumn } from './infer'
4
+
5
+ import {
6
+ flattenNestedChildren,
7
+ removeChildren,
8
+ deriveHierarchy,
9
+ toggleExpansion
10
+ } from './hierarchy'
11
+
12
+ /**
13
+ * Sorts a nested array of objects based on a group of sorters.
14
+ *
15
+ * @param {Array<Object>} elements - The array of objects to sort.
16
+ */
17
+ function sortNested(elements, group) {
18
+ elements
19
+ .sort((a, b) => {
20
+ let result = 0
21
+ for (let i = 0; i < group.length && result === 0; i++) {
22
+ result = group[i].sorter(a, b)
23
+ }
24
+ return result
25
+ })
26
+ .forEach((x) => {
27
+ if (Array.isArray(x.children) && x.children.length > 0) {
28
+ sortNested(x.children, group)
29
+ }
30
+ })
31
+ }
32
+
33
+ /**
34
+ * Sorts a hierarchy based on a group of sorters.
35
+ *
36
+ * @param {Array<Object>} hierarchy - The hierarchy to sort.
37
+ * @param {Array<Array<string, boolean>>} sortGroup - The group of sorters to apply.
38
+ */
39
+ export function groupSort(hierarchy, sortGroup) {
40
+ const group = sortGroup.map(deriveSortableColumn).map(({ name, sorter }) => ({
41
+ name,
42
+ sorter: (a, b) => sorter(a.row[name], b.row[name])
43
+ }))
44
+
45
+ removeChildren(hierarchy)
46
+ sortNested(hierarchy, group)
47
+ flattenNestedChildren(hierarchy)
48
+ }
49
+
50
+ /**
51
+ * Sorts the hierarchy based on a specific field name and order.
52
+ *
53
+ * @param {string} name - The field name to sort by.
54
+ * @param {boolean} [ascending=true] - Whether the sort should be in ascending order.
55
+ */
56
+ const sortHierarchyBy = (store, sortGroup, name, ascending = true) => {
57
+ sortGroup = [...sortGroup, [name, ascending]]
58
+ // change the sort order in the metadata
59
+ const { columns, hierarchy } = get(store)
60
+ columns.forEach((column) => {
61
+ if (column.name === name) {
62
+ column.order = ascending ? 'ascending' : 'descending'
63
+ }
64
+ })
65
+ groupSort(hierarchy, sortGroup)
66
+ store.set({ columns, hierarchy })
67
+ }
68
+
69
+ /**
70
+ * Updates the selection state for all children of a node in the hierarchy.
71
+ *
72
+ * @param {Array<import('./types').Hierarchy>} hierarchy - The hierarchy to update.
73
+ * @param {number} index - The index of the node to update.
74
+ */
75
+ function updateChildren(item) {
76
+ if (!item.children) return
77
+
78
+ item.children.forEach((child) => {
79
+ child.selected = item.selected
80
+ updateChildren(child)
81
+ })
82
+ }
83
+
84
+ /**
85
+ * Determines the selected state of a group of children in the hierarchy.
86
+ * If all children are checked, returns 'checked'.
87
+ * If all children are unchecked, returns 'unchecked'.
88
+ * Otherwise, returns 'indeterminate'.
89
+ *
90
+ * @param {Array<Object>} children - The array of child objects with a 'selected' property.
91
+ * @returns {string} The determined selected state: 'checked', 'unchecked', or 'indeterminate'.
92
+ */
93
+ export function determineSelectedState(children) {
94
+ const allChecked = children.every((child) => child.selected === 'checked')
95
+ const allUnchecked =
96
+ !allChecked && children.every((child) => !child.selected || child.selected === 'unchecked')
97
+
98
+ return allChecked ? 'checked' : allUnchecked ? 'unchecked' : 'indeterminate'
99
+ }
100
+
101
+ /**
102
+ * Updates the selection state of parent nodes in the hierarchy based on their children's states.
103
+ * It traverses up from the provided `index` position in the hierarchy array, ensuring each
104
+ * parent's selected state reflects whether all, none, or some of its children are selected.
105
+ *
106
+ * @param {Array<Object>} hierarchy - The hierarchy structure containing nodes with
107
+ * 'parent' and 'children' references.
108
+ * @param {number} index - The index of the node in the hierarchy array from where
109
+ * to start updating parent nodes.
110
+ */
111
+ function updateParents(item) {
112
+ let parent = item.parent
113
+
114
+ while (parent) {
115
+ parent.selected = determineSelectedState(parent.children)
116
+ parent = parent.parent
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Toggles the selection state of a node in the hierarchy.
122
+ * Depending on whether the node to toggle is a parent or child,
123
+ * it will either select or deselect all children, or update the parent's selection state based on its children's states.
124
+ *
125
+ * @param {import('./types').Hierarchy} item - The node to toggle.
126
+ *
127
+ */
128
+ export function toggleSelection(item) {
129
+ item.selected = item.selected === 'checked' ? 'unchecked' : 'checked'
130
+
131
+ updateParents(item)
132
+ updateChildren(item)
133
+ }
134
+
135
+ /**
136
+ * Toggles the expansion or selection state of a data element at the specified index.
137
+ *
138
+ * @param {import('svelte/store').Writable} store - The store object representing the view.
139
+ * @param {number} index - The index of the element in the hierarchy to expand or collapse.
140
+ * @param {string} mode - The mode to toggle, either 'expansion' or 'selection'.
141
+ */
142
+ function toggleState(store, index, mode) {
143
+ const { hierarchy } = get(store)
144
+ if (mode === 'selection') toggleSelection(hierarchy[index])
145
+ else if (mode === 'expansion') toggleExpansion(hierarchy[index])
146
+ store.update((state) => ({ ...state, hierarchy }))
147
+ }
148
+
149
+ /**
150
+ * Creates a view object for managing data presentation with metadata and hierarchy structures,
151
+ * as well as providing methods for sorting, selection, and toggling of data expansion.
152
+ *
153
+ * @param {Array<Object>} data - The source data array to be presented by the view.
154
+ * @param {Object} options - The configuration options used for deriving metadata and hierarchy from the data.
155
+ * @returns {import('svelte/store').Writable} An object representing the view, providing methods to manipulate and query the presentation state.
156
+ */
157
+ export function dataview(data, options) {
158
+ const store = writable({
159
+ columns: deriveMetadata(data, options),
160
+ hierarchy: deriveHierarchy(data, options)
161
+ })
162
+ let sortGroup = []
163
+
164
+ return {
165
+ ...omit(['set', 'update'], store),
166
+ // filter: noop,
167
+ /**
168
+ * Clears the applied sort order from the hierarchy.
169
+ */
170
+ clearSort: () => {
171
+ sortGroup = []
172
+ store.update((state) => ({
173
+ ...state,
174
+ columns: state.columns.map((column) => ({ ...column, order: 'none' }))
175
+ }))
176
+ },
177
+ sortBy: (name, ascending) => sortHierarchyBy(store, sortGroup, name, ascending),
178
+ select: (index) => toggleState(store, index, 'selection'),
179
+ toggle: (index) => toggleState(store, index, 'expansion')
180
+ }
181
+ }