@rokkit/chart 1.0.0-next.39 → 1.0.0-next.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/chart",
3
- "version": "1.0.0-next.39",
3
+ "version": "1.0.0-next.42",
4
4
  "description": "Components for making interactive charts.",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "typescript": "^5.1.6",
24
24
  "vite": "^4.4.7",
25
25
  "vitest": "~0.33.0",
26
- "shared-config": "1.0.0-next.39"
26
+ "shared-config": "1.0.0-next.42"
27
27
  },
28
28
  "dependencies": {
29
29
  "d3-array": "^3.2.4",
@@ -35,10 +35,10 @@
35
35
  "date-fns": "^2.30.0",
36
36
  "ramda": "^0.29.0",
37
37
  "yootils": "^0.3.1",
38
- "@rokkit/core": "1.0.0-next.39",
39
- "@rokkit/molecules": "1.0.0-next.39",
40
- "@rokkit/atoms": "1.0.0-next.39",
41
- "@rokkit/stores": "1.0.0-next.39"
38
+ "@rokkit/core": "1.0.0-next.42",
39
+ "@rokkit/molecules": "1.0.0-next.42",
40
+ "@rokkit/atoms": "1.0.0-next.42",
41
+ "@rokkit/stores": "1.0.0-next.42"
42
42
  },
43
43
  "files": [
44
44
  "src/**/*.js",
@@ -61,6 +61,6 @@
61
61
  "test": "vitest",
62
62
  "coverage": "vitest run --coverage",
63
63
  "latest": "pnpm upgrade --latest && pnpm test:ci",
64
- "release": "tsc && pnpm publish --access public"
64
+ "release": "pnpm publish --access public"
65
65
  }
66
66
  }
package/src/lib/funnel.js CHANGED
@@ -1,34 +1,9 @@
1
- import { sum, median, max, mean, min, quantile, cumsum } from 'd3-array'
1
+ import { max, cumsum } from 'd3-array'
2
2
  import { nest } from 'd3-collection'
3
- import { pick, flatten } from 'ramda'
3
+ import { flatten } from 'ramda'
4
4
  import { area, curveBasis, curveBumpX, curveBumpY } from 'd3-shape'
5
5
  import { scaleLinear } from 'd3-scale'
6
-
7
- const aggregate = {
8
- count: (values) => values.length,
9
- sum: (values) => sum(values),
10
- min: (values) => min(values),
11
- max: (values) => max(values),
12
- mean: (values) => mean(values),
13
- median: (values) => median(values),
14
- q1: (values) => quantile(values, 0.25),
15
- q3: (values) => quantile(values, 0.75)
16
- }
17
-
18
- export function summarize(data, by, attr, stat = 'count') {
19
- const stats = Array.isArray(stat) ? stat : [stat]
20
- const grouped = nest()
21
- .key((d) => by.map((f) => d[f]).join('|'))
22
- .rollup((rows) => {
23
- let agg = pick(by, rows[0])
24
- stats.map(
25
- (stat) => (agg[stat] = aggregate[stat](rows.map((d) => d[attr])))
26
- )
27
- return [agg]
28
- })
29
- .entries(data)
30
- return flatten(grouped.map((group) => group.value))
31
- }
6
+ import { summarize } from './summary'
32
7
 
33
8
  export function getUniques(input, aes) {
34
9
  const attrs = ['x', 'y', 'fill']
@@ -58,76 +33,58 @@ export function fillMissing(fill, rows, key, aes) {
58
33
  return filled
59
34
  }
60
35
 
61
- export function convertToPhases(input, aes) {
62
- const uniques = getUniques(input, aes)
36
+ /**
37
+ * Determines if the layout is vertical based on the given parameters.
38
+ * @param {Object} uniques - The unique values.
39
+ * @param {Object} aes - The aes object.
40
+ * @returns {boolean} - Returns true if the layout is vertical, false otherwise.
41
+ */
42
+ function determineLayout(uniques, aes) {
63
43
  let vertical = 'y' in uniques && uniques.y.some(isNaN)
64
44
  const horizontal = 'x' in uniques && uniques.x.some(isNaN)
65
45
 
66
- let summary = []
67
-
68
46
  if (horizontal && vertical) {
69
47
  if ((aes.stat || 'count') === 'count') {
70
48
  vertical = false
71
- console.warn('Assuming horizontal layout becuse stat is count')
49
+ console.warn('Assuming horizontal layout because stat is count')
72
50
  } else {
73
51
  console.error(
74
- 'cannot plot without at least one axis having numeric values'
52
+ 'Cannot plot without at least one axis having numeric values'
75
53
  )
76
- return { uniques, vertical }
54
+ return null
77
55
  }
78
56
  }
57
+ return vertical
58
+ }
59
+
60
+ /**
61
+ * Converts to phases.
62
+ * @param {Array} input - The input data.
63
+ * @param {Object} aes - The aes object.
64
+ * @returns {Object} - The phases, uniques, and vertical properties.
65
+ */
66
+ export function convertToPhases(input, aes) {
67
+ const uniques = getUniques(input, aes)
68
+ const vertical = determineLayout(uniques, aes)
69
+ if (vertical === null) return { uniques, vertical }
79
70
 
80
71
  const key = vertical ? aes.y : aes.x
81
72
  const value = vertical ? aes.x : aes.y
82
73
 
83
74
  let by = [key]
84
- if ('fill' in aes) {
85
- by.push(aes.fill)
86
- }
87
- summary = summarize(input, by, value, aes.stat)
75
+ if ('fill' in aes) by.push(aes.fill)
76
+
77
+ const summary = summarize(input, by, value, aes.stat)
88
78
  const phases = nest()
89
79
  .key((d) => d[key])
90
- .rollup((rows) => {
91
- return 'fill' in aes ? fillMissing(uniques.fill, rows, key, aes) : rows
92
- })
80
+ .rollup((rows) =>
81
+ 'fill' in aes ? fillMissing(uniques.fill, rows, key, aes) : rows
82
+ )
93
83
  .entries(summary)
94
84
 
95
85
  return { phases, uniques, vertical }
96
86
  }
97
87
 
98
- export function mirror(input, aes) {
99
- let domain = 0
100
-
101
- const stats = input.phases.map((phase) => {
102
- const stat = cumsum(phase.value.map((row) => row[aes.stat]))
103
- const midpoint = max(stat) / 2
104
- domain = Math.max(domain, midpoint)
105
-
106
- const rows = phase.value.map((row, index) => {
107
- if (input.vertical) {
108
- return {
109
- ...row,
110
- y: input.uniques.y.indexOf(row[aes.y]),
111
- x1: stat[index] - midpoint,
112
- x0: stat[index] - midpoint - row[aes.stat]
113
- }
114
- } else {
115
- return {
116
- ...row,
117
- x: input.uniques.x.indexOf(row[aes.x]),
118
- y1: stat[index] - midpoint,
119
- y0: stat[index] - midpoint - row[aes.stat]
120
- }
121
- }
122
- })
123
-
124
- phase.value = rows
125
- return phase
126
- })
127
-
128
- return { ...input, stats, domain }
129
- }
130
-
131
88
  export function getScales(input, width, height) {
132
89
  let scale
133
90
  if (input.vertical) {
@@ -178,6 +135,82 @@ export function getPaths(vertical, scale, curve) {
178
135
  .y1((d) => scale.y(d.y1))
179
136
  .curve(curve)
180
137
  }
138
+
139
+ /**
140
+ * Mirrors the input based on the provided aesthetic mappings.
141
+ *
142
+ * @param {Object} input - The input data with phases and uniques.
143
+ * @param {Object} aes - The aesthetic mappings.
144
+ * @returns {Object} The mirrored input with updated stats and domain.
145
+ */
146
+ export function mirror(input, aes) {
147
+ const domain = calculateDomain(input)
148
+ const stats = calculateStats(input, aes, domain)
149
+
150
+ return { ...input, stats, domain }
151
+ }
152
+
153
+ /**
154
+ * Calculates the domain for the mirror operation.
155
+ *
156
+ * @param {Object} input - The input data with phases.
157
+ * @returns {number} The calculated domain.
158
+ */
159
+ function calculateDomain(input) {
160
+ return input.phases.reduce((maxDomain, phase) => {
161
+ const stat = cumsum(phase.value.map((row) => row[aes.stat]))
162
+ const midpoint = max(stat) / 2
163
+ return Math.max(maxDomain, midpoint)
164
+ }, 0)
165
+ }
166
+
167
+ /**
168
+ * Calculates the stats for the mirror operation.
169
+ *
170
+ * @param {Object} input - The input data with phases and uniques.
171
+ * @param {Object} aes - The aesthetic mappings.
172
+ * @param {number} domain - The domain for the mirror operation.
173
+ * @returns {Array} The calculated stats.
174
+ */
175
+ function calculateStats(input, aes, domain) {
176
+ return input.phases.map((phase) => {
177
+ const stat = cumsum(phase.value.map((row) => row[aes.stat]))
178
+ const midpoint = max(stat) / 2
179
+
180
+ phase.value = phase.value.map((row, index) => {
181
+ const position = calculatePosition(input, aes, row, stat, index, midpoint)
182
+ return { ...row, ...position }
183
+ })
184
+
185
+ return phase
186
+ })
187
+ }
188
+
189
+ /**
190
+ * Calculates the position for the mirror operation.
191
+ *
192
+ * @param {Object} input - The input data with uniques.
193
+ * @param {Object} aes - The aesthetic mappings.
194
+ * @param {Object} row - The current row.
195
+ * @param {Array} stat - The stat data.
196
+ * @param {number} index - The current index.
197
+ * @param {number} midpoint - The midpoint for the mirror operation.
198
+ * @returns {Object} The calculated position.
199
+ */
200
+ function calculatePosition(input, aes, row, stat, index, midpoint) {
201
+ const axis = input.vertical ? 'y' : 'x'
202
+ const oppositeAxis = input.vertical ? 'x' : 'y'
203
+ const axisValue = input.uniques[axis].indexOf(row[aes[axis]])
204
+ const position1 = stat[index] - midpoint
205
+ const position0 = position1 - row[aes.stat]
206
+
207
+ return {
208
+ [axis]: axisValue,
209
+ [`${oppositeAxis}1`]: position1,
210
+ [`${oppositeAxis}0`]: position0
211
+ }
212
+ }
213
+
181
214
  export function funnel(input, aes, width, height) {
182
215
  let data = convertToPhases(input, aes)
183
216
  data = mirror(data, aes)
@@ -1,143 +1,30 @@
1
- // import { sum, median, max, mean, min, quantile, cumsum } from 'd3-array'
2
- // import { nest } from 'd3-collection'
3
- // import { pick, flatten } from 'ramda'
4
-
5
- // const aggregate = {
6
- // count: (values) => values.length,
7
- // sum: (values) => sum(values),
8
- // min: (values) => min(values),
9
- // max: (values) => max(values),
10
- // mean: (values) => mean(values),
11
- // median: (values) => median(values),
12
- // q1: (values) => quantile(values, 0.25),
13
- // q3: (values) => quantile(values, 0.75),
14
- // }
15
-
16
- // export function summarize(data, by, attr, stat = 'count') {
17
- // const stats = Array.isArray(stat) ? stat : [stat]
18
- // const grouped = nest()
19
- // .key((d) => by.map((f) => d[f]).join('|'))
20
- // .rollup((rows) => {
21
- // let agg = pick(by, rows[0])
22
- // stats.map(
23
- // (stat) => (agg[stat] = aggregate[stat](rows.map((d) => d[attr])))
24
- // )
25
- // return [agg]
26
- // })
27
- // .entries(data)
28
- // return flatten(grouped.map((group) => group.value))
29
- // }
30
-
31
- // export function getUniques(input, aes) {
32
- // const attrs = ['x', 'y', 'fill']
33
- // let values = {}
34
-
35
- // attrs.map((attr) => {
36
- // if (attr in aes) {
37
- // values[attr] = [...new Set(input.map((d) => d[aes[attr]]))]
38
- // }
39
- // })
40
- // // x: 'x' in aes ? [...new Set(input.map((d) => d[aes.x]))] : [],
41
- // // y: 'y' in aes ? [...new Set(input.map((d) => d[aes.y]))] : [],
42
- // // fill: 'fill' in aes ? [...new Set(input.map((d) => d[aes.fill]))] : [],
43
- // // }
44
- // return values
45
- // }
46
-
47
- // export function fillMissing(fill, rows, key, aes) {
48
- // const filled = fill.map((f) => {
49
- // let matched = rows.filter((r) => r[aes.fill] === f)
50
- // if (matched.length == 0) {
51
- // let row = {}
52
- // row[key] = rows[0][key]
53
- // row[aes.fill] = f
54
- // row[aes.stat] = 0
55
- // return row
56
- // } else {
57
- // return matched[0]
58
- // }
59
- // })
60
- // return filled
61
- // }
62
-
63
- // export function convertToPhases(input, aes) {
64
- // const uniques = getUniques(input, aes)
65
- // let vertical = 'y' in uniques && uniques.y.some(isNaN)
66
- // const horizontal = 'x' in uniques && uniques.x.some(isNaN)
67
-
68
- // let summary = []
69
-
70
- // if (horizontal && vertical) {
71
- // if ((aes.stat || 'count') === 'count') {
72
- // vertical = false
73
- // console.warn('Assuming horizontal layout becuse stat is count')
74
- // } else {
75
- // console.error(
76
- // 'cannot plot without at least one axis having numeric values'
77
- // )
78
- // return { uniques, vertical }
79
- // }
80
- // }
81
-
82
- // const key = vertical ? aes.y : aes.x
83
- // const value = vertical ? aes.x : aes.y
84
-
85
- // let by = [key]
86
- // if ('fill' in aes) {
87
- // by.push(aes.fill)
88
- // }
89
- // summary = summarize(input, by, value, aes.stat)
90
- // const phases = nest()
91
- // .key((d) => d[key])
92
- // .rollup((rows) => {
93
- // return 'fill' in aes ? fillMissing(uniques.fill, rows, key, aes) : rows
94
- // })
95
- // .entries(summary)
96
-
97
- // return { phases, uniques, vertical }
98
- // }
99
-
100
- // export function mirror(input, aes) {
101
- // let domain = 0
102
-
103
- // const stats = input.phases.map((phase) => {
104
- // const stat = cumsum(phase.value.map((row) => row[aes.stat]))
105
- // const midpoint = max(stat) / 2
106
- // domain = Math.max(domain, midpoint)
107
-
108
- // const rows = phase.value.map((row, index) => {
109
- // if (input.vertical) {
110
- // return {
111
- // ...row,
112
- // y: input.uniques.y.indexOf(row[aes.y]),
113
- // x1: stat[index] - midpoint,
114
- // x0: stat[index] - midpoint - row[aes.stat],
115
- // }
116
- // } else {
117
- // return {
118
- // ...row,
119
- // x: input.uniques.x.indexOf(row[aes.x]),
120
- // y1: stat[index] - midpoint,
121
- // y0: stat[index] - midpoint - row[aes.stat],
122
- // }
123
- // }
124
- // })
125
- // phase.value = rows
126
- // return phase
127
- // })
128
- // return { ...input, stats }
129
- // }
130
-
131
- // export function funnel(input, aes) {
132
- // let data = convertToPhases(input, aes)
133
- // data = mirror(data, aes)
134
-
135
- // if ('fill' in aes) {
136
- // let stats = flatten(data.stats.map((phase) => phase.value))
137
- // data.stats = nest()
138
- // .key((d) => d[aes.fill])
139
- // .rollup((rows) => [...rows, { ...rows[rows.length - 1], x: rows.length }])
140
- // .entries(stats)
141
- // }
142
- // return data
143
- // }
1
+ import { sum, median, max, mean, min, quantile, cumsum } from 'd3-array'
2
+ import { nest } from 'd3-collection'
3
+ import { pick, flatten } from 'ramda'
4
+
5
+ export const aggregate = {
6
+ count: (values) => values.length,
7
+ sum: (values) => sum(values),
8
+ min: (values) => min(values),
9
+ max: (values) => max(values),
10
+ mean: (values) => mean(values),
11
+ median: (values) => median(values),
12
+ cumsum: (values) => cumsum(values),
13
+ q1: (values) => quantile(values, 0.25),
14
+ q3: (values) => quantile(values, 0.75)
15
+ }
16
+
17
+ export function summarize(data, by, attr, stat = 'count') {
18
+ const stats = Array.isArray(stat) ? stat : [stat]
19
+ const grouped = nest()
20
+ .key((d) => by.map((f) => d[f]).join('|'))
21
+ .rollup((rows) => {
22
+ let agg = pick(by, rows[0])
23
+ stats.map(
24
+ (stat) => (agg[stat] = aggregate[stat](rows.map((d) => d[attr])))
25
+ )
26
+ return [agg]
27
+ })
28
+ .entries(data)
29
+ return flatten(grouped.map((group) => group.value))
30
+ }