@rokkit/chart 1.0.0-next.86 → 1.0.0-next.88

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 (82) hide show
  1. package/package.json +11 -6
  2. package/src/Chart.svelte +67 -0
  3. package/src/PatternDefs.svelte +14 -0
  4. package/src/Symbol.svelte +17 -0
  5. package/src/{chart/Texture.svelte → Texture.svelte} +3 -3
  6. package/src/elements/Bar.svelte +2 -2
  7. package/src/elements/ContinuousLegend.svelte +3 -2
  8. package/src/elements/DefinePatterns.svelte +22 -0
  9. package/src/elements/DiscreteLegend.svelte +1 -1
  10. package/src/elements/Label.svelte +7 -5
  11. package/src/elements/SymbolGrid.svelte +23 -0
  12. package/src/elements/index.js +6 -0
  13. package/src/index.js +5 -15
  14. package/src/lib/brewer.js +17 -0
  15. package/src/lib/chart.js +179 -160
  16. package/src/lib/grid.js +68 -0
  17. package/src/lib/index.js +4 -0
  18. package/src/lib/palette.js +279 -28
  19. package/src/lib/plots.js +23 -0
  20. package/src/lib/swatch.js +24 -8
  21. package/src/lib/ticks.js +19 -0
  22. package/src/patterns/Brick.svelte +17 -0
  23. package/src/patterns/Circles.svelte +18 -0
  24. package/src/patterns/CrossHatch.svelte +14 -0
  25. package/src/patterns/CurvedWave.svelte +9 -0
  26. package/src/patterns/Dots.svelte +19 -0
  27. package/src/patterns/OutlineCircles.svelte +15 -0
  28. package/src/patterns/Texture.svelte +20 -0
  29. package/src/patterns/Tile.svelte +17 -0
  30. package/src/patterns/Triangles.svelte +15 -0
  31. package/src/patterns/Waves.svelte +13 -0
  32. package/src/patterns/constants.js +43 -0
  33. package/src/patterns/index.js +13 -0
  34. package/src/patterns/paths/NamedPattern.svelte +12 -0
  35. package/src/patterns/paths/constants.js +7 -0
  36. package/src/patterns/templates/Circles.svelte +18 -0
  37. package/src/patterns/templates/Lines.svelte +17 -0
  38. package/src/patterns/templates/Path.svelte +17 -0
  39. package/src/patterns/templates/index.js +3 -0
  40. package/src/plots/Plot.svelte +36 -21
  41. package/src/plots/index.js +1 -10
  42. package/src/symbols/Circle.svelte +22 -0
  43. package/src/symbols/Shape.svelte +31 -0
  44. package/src/symbols/Square.svelte +27 -0
  45. package/src/symbols/Triangle.svelte +24 -0
  46. package/src/symbols/constants/index.js +7 -0
  47. package/src/symbols/index.js +13 -0
  48. package/src/chart/Axis.svelte +0 -81
  49. package/src/chart/AxisGrid.svelte +0 -22
  50. package/src/chart/Chart.svelte +0 -40
  51. package/src/chart/FacetGrid.svelte +0 -49
  52. package/src/chart/Legend.svelte +0 -16
  53. package/src/chart/Swatch.svelte +0 -84
  54. package/src/chart/SwatchButton.svelte +0 -29
  55. package/src/chart/SwatchGrid.svelte +0 -53
  56. package/src/chart/TexturedShape.svelte +0 -20
  57. package/src/chart/TimelapseChart.svelte +0 -90
  58. package/src/chart/Timer.svelte +0 -27
  59. package/src/elements/Tooltip.svelte +0 -19
  60. package/src/lib/axis.js +0 -77
  61. package/src/lib/color.js +0 -55
  62. package/src/lib/constants.js +0 -41
  63. package/src/lib/funnel.js +0 -230
  64. package/src/lib/geom.js +0 -99
  65. package/src/lib/heatmap.js +0 -68
  66. package/src/lib/lookup.js +0 -29
  67. package/src/lib/pattern.js +0 -182
  68. package/src/lib/rollup.js +0 -49
  69. package/src/lib/shape.js +0 -46
  70. package/src/lib/store.js +0 -63
  71. package/src/lib/summary.js +0 -28
  72. package/src/lib/theme.js +0 -31
  73. package/src/lib/utils.js +0 -158
  74. package/src/plots/BarPlot.svelte +0 -51
  75. package/src/plots/BarPlot2.svelte +0 -34
  76. package/src/plots/BoxPlot.svelte +0 -54
  77. package/src/plots/FunnelPlot.svelte +0 -26
  78. package/src/plots/HeatMapCalendar.svelte +0 -121
  79. package/src/plots/LinePlot.svelte +0 -51
  80. package/src/plots/RankBarPlot.svelte +0 -38
  81. package/src/plots/ScatterPlot.svelte +0 -28
  82. package/src/plots/ViolinPlot.svelte +0 -10
@@ -1,90 +0,0 @@
1
- <script>
2
- import { setContext } from 'svelte'
3
- import { writable } from 'svelte/store'
4
- import { slidingWindow, uniques, brewer } from '../lib'
5
-
6
- import Grid from './AxisGrid.svelte'
7
- import Axis from './Axis.svelte'
8
- import BoxPlot from '../plots/BoxPlot.svelte'
9
- import ViolinPlot from '../plots/ViolinPlot.svelte'
10
- import ScatterPlot from '../plots/ScatterPlot.svelte'
11
-
12
- let chart = writable({})
13
- let axis
14
- setContext('chart', chart)
15
-
16
- export let data
17
- export let x
18
- export let y
19
- export let time
20
- export let theme
21
-
22
- export let current
23
- export let stages
24
-
25
- function base(data, x, y) {
26
- return {
27
- data,
28
- x,
29
- y,
30
- width: 800,
31
- height: 350,
32
- values: {
33
- x: uniques(data, x),
34
- y: uniques(data, y)
35
- }
36
- }
37
- }
38
-
39
- function sliceBy(data, attr, size, step, offset) {
40
- const values = uniques(data, attr)
41
- const groups = slidingWindow(values, size, step, offset).map((x) => ({
42
- ...x,
43
- data: data.filter((y) => y.Petal_Length >= x.lowerBound && y.Petal_Length < x.upperBound)
44
- }))
45
-
46
- return groups
47
- }
48
-
49
- function timed(data, x, y, time) {
50
- let chart = base(data, x, y)
51
- let temp = brewer().chart(data, x, y).use(theme).computeAxis()
52
- axis = temp.axis
53
- chart.data = sliceBy(chart.data, time, 3, 1)
54
- stages = chart.data.length - 1
55
- return chart
56
- }
57
-
58
- function switchChart(index) {
59
- let chart = {}
60
- if (index != null) {
61
- chart = brewer().chart(phased.data[index].data, x, y).use(theme).computeAxis()
62
- chart.axis = axis
63
- // chart.margin = { left: 40, right: 10, top: 10, bottom: 30 }
64
- }
65
- return chart
66
- }
67
- let phased
68
-
69
- $: phased = timed(data, x, y, time)
70
- $: $chart = switchChart(current)
71
-
72
- // setup chart attributes that do not change over time
73
- // get scales for x & y
74
- // set margins
75
-
76
- // nest data by time attribute
77
- // set up sequence based on ascending values of time
78
- // set up the timer to switch between data values
79
- // On change set the context with new data set
80
- // Old data set needs exit animation, new data set needs entry animation
81
- </script>
82
-
83
- <svg viewBox="0 0 {phased.width} {phased.height}" class="chart flex">
84
- <Grid />
85
- <Axis orient="bottom" />
86
- <Axis orient="left" />
87
- <BoxPlot />
88
- <ViolinPlot />
89
- <ScatterPlot jitterWidth={50} offset={50} />
90
- </svg>
@@ -1,27 +0,0 @@
1
- <script>
2
- import { createEventDispatcher } from 'svelte'
3
- import { elapsed, timer } from '@rokkit/stores'
4
-
5
- const dispatch = createEventDispatcher()
6
-
7
- export let isEnabled = false
8
- export let currentKeyframe = 0
9
- export let keyframeCount = 0
10
- export let duration = 1000
11
-
12
- $: if (isEnabled) currentKeyframe = Math.floor($elapsed / duration)
13
- $: if (currentKeyframe === keyframeCount) dispatch('end')
14
- $: isEnabled && currentKeyframe < keyframeCount ? timer.start() : timer.stop()
15
-
16
- function onReset() {
17
- timer.reset()
18
- isEnabled = false
19
- currentKeyframe = 0
20
- }
21
- </script>
22
-
23
- <div class="timer flex flex-row gap-2">
24
- <button on:click|preventDefault={() => (isEnabled = true)}>play</button>
25
- <button on:click|preventDefault={() => (isEnabled = false)}>pause</button>
26
- <button on:click|preventDefault={onReset}>reset</button>
27
- </div>
@@ -1,19 +0,0 @@
1
- <script>
2
- export let left
3
- export let top
4
- export let hidden = true
5
- </script>
6
-
7
- <div class="tooltip {$$props.class}" {hidden} style="--top: {top};--left: {left}">
8
- <slot />
9
- </div>
10
-
11
- <style lang="postcss">
12
- .tooltip {
13
- padding: 1em 0.5em;
14
- left: var(--left);
15
- top: var(--top);
16
- position: absolute;
17
- z-index: 10;
18
- }
19
- </style>
package/src/lib/axis.js DELETED
@@ -1,77 +0,0 @@
1
- function getOrigin(scale, axis) {
2
- const origin = scale[axis].ticks
3
- ? scale(Math.max(0, Math.min(...scale[axis].domain())))
4
- : scale[axis].range()[0]
5
- return origin
6
- }
7
- export function axis(scale) {
8
- const origin = {
9
- x: getOrigin(scale, 'x'),
10
- y: getOrigin(scale, 'y')
11
- }
12
- const ticks = {
13
- x: axisTicks(scale.x, { axis: 'x', origin }),
14
- y: axisTicks(scale.y, { axis: 'y', origin })
15
- }
16
- return { origin, ticks }
17
- }
18
-
19
- export function axisTicks(scale, opts) {
20
- let [minRange, maxRange] = scale.range()
21
- let count = Math.abs((maxRange - minRange) / 40)
22
- let ticks = scale.domain()
23
- let offset = 0
24
- let { axis, format, origin } = {
25
- axis: 'x',
26
- format: (x) => x,
27
- origin: { x: 0, y: 0 },
28
- ...opts
29
- }
30
- if (scale.ticks) {
31
- ticks = scale.ticks(count)
32
- } else {
33
- offset = Math.sign(maxRange - minRange) * (scale.bandwidth() / 2)
34
- count = Math.min(Math.round(count), scale.domain().length)
35
- if (count < scale.domain().length) {
36
- let diff = scale.domain().length - count
37
- ticks = ticks.filter((d, i) => i % diff == 0)
38
- }
39
- // let diff = scale.domain().length - count
40
- }
41
- ticks = ticks
42
- .map((t) => ({
43
- label: format(t),
44
- pos: scale(t)
45
- }))
46
- .map(({ label, pos }) => ({
47
- label,
48
- offset: { x: axis === 'x' ? offset : 0, y: axis === 'y' ? offset : 0 },
49
- x: axis === 'x' ? pos : origin.x,
50
- y: axis === 'y' ? pos : origin.y
51
- }))
52
-
53
- return ticks
54
- }
55
-
56
- export class Axis {
57
- constructor(name, chart, offset) {
58
- this.name = ['x', 'y'].includes(name) ? name : 'x'
59
- this.chart = chart
60
- this.offset = offset
61
- }
62
-
63
- set offset(value) {
64
- const [min, max] = this.chart.scale[this.name].range()
65
- const otherAxis = this.name === 'x' ? 'y' : 'x'
66
- const origin = this.chart.origin[otherAxis]
67
-
68
- this.offset = value * (origin == min ? 1 : origin == max ? -1 : 0)
69
- }
70
-
71
- // get domain() {
72
- // let coords =
73
- // (coords[axis + '1'] =
74
- // coords[axis + '2'] =
75
- // origin[axis] - offset[axis])
76
- // }
77
- }
package/src/lib/color.js DELETED
@@ -1,55 +0,0 @@
1
- import { palette } from './constants'
2
-
3
- export class ColorBrewer {
4
- constructor() {
5
- this.colors = ['blue', 'pink', 'teal', 'indigo', 'purple', 'amber', 'rose']
6
- this.palette = palette
7
- this.grayscale = this.palette['trueGray']
8
- this.fill = 100
9
- this.stroke = 600
10
- this.contrast = 600
11
- }
12
-
13
- dark() {
14
- this.fill = 500
15
- this.stroke = 700
16
- this.contrast = 100
17
- return this
18
- }
19
-
20
- mix(fill, stroke, contrast) {
21
- this.fill = Object.keys(this.grayscale).includes(fill) ? fill : this.fill
22
- this.stroke = Object.keys(this.grayscale).includes(stroke) ? stroke : this.stroke
23
- this.contrast = Object.keys(this.grayscale).includes(contrast) ? contrast : this.contrast
24
-
25
- return this
26
- }
27
-
28
- swatch(colors) {
29
- this.palette = colors
30
- return this
31
- }
32
-
33
- filter(colors) {
34
- this.colors = colors
35
- return this
36
- }
37
-
38
- gray() {
39
- return {
40
- fill: this.grayscale[this.fill],
41
- stroke: this.grayscale[this.stroke],
42
- contrast: this.grayscale[this.contrast]
43
- }
44
- }
45
-
46
- brew() {
47
- const palette = this.colors.map((color) => ({
48
- fill: this.palette[color][this.fill],
49
- stroke: this.palette[color][this.stroke],
50
- contrast: this.palette[color][this.contrast]
51
- }))
52
-
53
- return palette
54
- }
55
- }
@@ -1,41 +0,0 @@
1
- import patterns__ from './patterns.json'
2
- import __palette from './palette.json'
3
-
4
- export const __patterns__ = patterns__
5
- export const palette = __palette
6
- export const __muted__ = {
7
- color: '#eeeeee',
8
- fill: 'empty',
9
- shape: 'circle'
10
- }
11
- export const __colors__ = [
12
- '#FFDE6B',
13
- '#EF89EE',
14
- '#F79F1E',
15
- '#02B8FF',
16
- '#9F84EC',
17
- '#15CBC4',
18
- '#0092FD',
19
- '#F63A57',
20
- '#A2CB39',
21
- '#FF6E2F',
22
- '#FEB8B9',
23
- '#af7aa1',
24
- '#7EFFF5'
25
- ]
26
-
27
- export const colors = [
28
- '#FFDE6B',
29
- '#EF89EE',
30
- '#F79F1E',
31
- '#02B8FF',
32
- '#9F84EC',
33
- '#15CBC4',
34
- '#0092FD',
35
- '#F63A57',
36
- '#A2CB39',
37
- '#FF6E2F',
38
- '#FEB8B9',
39
- '#af7aa1',
40
- '#7EFFF5'
41
- ]
package/src/lib/funnel.js DELETED
@@ -1,230 +0,0 @@
1
- import { max, cumsum } from 'd3-array'
2
- import { nest } from 'd3-collection'
3
- import { flatten } from 'ramda'
4
- import { area, curveBasis, curveBumpX, curveBumpY } from 'd3-shape'
5
- import { scaleLinear } from 'd3-scale'
6
- import { summarize } from './summary'
7
-
8
- export function getUniques(input, aes) {
9
- const attrs = ['x', 'y', 'fill']
10
- let values = {}
11
-
12
- attrs.map((attr) => {
13
- if (attr in aes) {
14
- values[attr] = [...new Set(input.map((d) => d[aes[attr]]))]
15
- }
16
- })
17
- return values
18
- }
19
-
20
- export function fillMissing(fill, rows, key, aes) {
21
- const filled = fill.map((f) => {
22
- let matched = rows.filter((r) => r[aes.fill] === f)
23
- if (matched.length == 0) {
24
- let row = {}
25
- row[key] = rows[0][key]
26
- row[aes.fill] = f
27
- row[aes.stat] = 0
28
- return row
29
- } else {
30
- return matched[0]
31
- }
32
- })
33
- return filled
34
- }
35
-
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) {
43
- let vertical = 'y' in uniques && uniques.y.some(isNaN)
44
- const horizontal = 'x' in uniques && uniques.x.some(isNaN)
45
-
46
- if (horizontal && vertical) {
47
- if ((aes.stat || 'count') === 'count') {
48
- vertical = false
49
- console.warn('Assuming horizontal layout because stat is count')
50
- } else {
51
- console.error('Cannot plot without at least one axis having numeric values')
52
- return null
53
- }
54
- }
55
- return vertical
56
- }
57
-
58
- /**
59
- * Converts to phases.
60
- * @param {Array} input - The input data.
61
- * @param {Object} aes - The aes object.
62
- * @returns {Object} - The phases, uniques, and vertical properties.
63
- */
64
- export function convertToPhases(input, aes) {
65
- const uniques = getUniques(input, aes)
66
- const vertical = determineLayout(uniques, aes)
67
- if (vertical === null) return { uniques, vertical }
68
-
69
- const key = vertical ? aes.y : aes.x
70
- const value = vertical ? aes.x : aes.y
71
-
72
- let by = [key]
73
- if ('fill' in aes) by.push(aes.fill)
74
-
75
- const summary = summarize(input, by, value, aes.stat)
76
- const phases = nest()
77
- .key((d) => d[key])
78
- .rollup((rows) => ('fill' in aes ? fillMissing(uniques.fill, rows, key, aes) : rows))
79
- .entries(summary)
80
-
81
- return { phases, uniques, vertical }
82
- }
83
-
84
- export function getScales(input, width, height) {
85
- let scale
86
- if (input.vertical) {
87
- scale = {
88
- x: scaleLinear()
89
- .domain([-input.domain * 1.4, input.domain * 1.4])
90
- .range([0, width]),
91
- y: scaleLinear().domain([0, input.uniques.y.length]).range([0, height])
92
- }
93
- } else {
94
- scale = {
95
- x: scaleLinear().domain([0, input.uniques.x.length]).range([0, width]),
96
- y: scaleLinear()
97
- .domain([-input.domain, input.domain * 1.4])
98
- .range([height - 20, 0])
99
- }
100
- }
101
- return scale
102
- }
103
-
104
- function getLabels(data) {
105
- let key = data.vertical ? 'y' : 'x'
106
- let opp = key === 'x' ? 'y' : 'x'
107
-
108
- let labels = data.uniques[key].map((label, index) => {
109
- let row = { label }
110
- let domain = data.scale[opp].domain()
111
- row[`${key}1`] = row[`${key}2`] = data.scale[key](index + 1)
112
- row[`${opp}1`] = data.scale[opp](domain[0])
113
- row[`${opp}2`] = data.scale[opp](domain[1])
114
- row[opp] = 20
115
- row[key] = data.scale[key](index) + 20
116
- return row
117
- })
118
- return labels
119
- }
120
-
121
- export function getPaths(vertical, scale, curve) {
122
- return vertical
123
- ? area()
124
- .x0((d) => scale.x(d.x0))
125
- .x1((d) => scale.x(d.x1))
126
- .y((d) => scale.y(d.y))
127
- .curve(curve)
128
- : area()
129
- .x((d) => scale.x(d.x))
130
- .y0((d) => scale.y(d.y0))
131
- .y1((d) => scale.y(d.y1))
132
- .curve(curve)
133
- }
134
-
135
- /**
136
- * Mirrors the input based on the provided aesthetic mappings.
137
- *
138
- * @param {Object} input - The input data with phases and uniques.
139
- * @param {Object} aes - The aesthetic mappings.
140
- * @returns {Object} The mirrored input with updated stats and domain.
141
- */
142
- export function mirror(input, aes) {
143
- const domain = calculateDomain(input)
144
- const stats = calculateStats(input, aes, domain)
145
-
146
- return { ...input, stats, domain }
147
- }
148
-
149
- /**
150
- * Calculates the domain for the mirror operation.
151
- *
152
- * @param {Object} input - The input data with phases.
153
- * @returns {number} The calculated domain.
154
- */
155
- function calculateDomain(input) {
156
- return input.phases.reduce((maxDomain, phase) => {
157
- const stat = cumsum(phase.value.map((row) => row[aes.stat]))
158
- const midpoint = max(stat) / 2
159
- return Math.max(maxDomain, midpoint)
160
- }, 0)
161
- }
162
-
163
- /**
164
- * Calculates the stats for the mirror operation.
165
- *
166
- * @param {Object} input - The input data with phases and uniques.
167
- * @param {Object} aes - The aesthetic mappings.
168
- * @param {number} domain - The domain for the mirror operation.
169
- * @returns {Array} The calculated stats.
170
- */
171
- function calculateStats(input, aes, domain) {
172
- return input.phases.map((phase) => {
173
- const stat = cumsum(phase.value.map((row) => row[aes.stat]))
174
- const midpoint = max(stat) / 2
175
-
176
- phase.value = phase.value.map((row, index) => {
177
- const position = calculatePosition(input, aes, row, stat, index, midpoint)
178
- return { ...row, ...position }
179
- })
180
-
181
- return phase
182
- })
183
- }
184
-
185
- /**
186
- * Calculates the position for the mirror operation.
187
- *
188
- * @param {Object} input - The input data with uniques.
189
- * @param {Object} aes - The aesthetic mappings.
190
- * @param {Object} row - The current row.
191
- * @param {Array} stat - The stat data.
192
- * @param {number} index - The current index.
193
- * @param {number} midpoint - The midpoint for the mirror operation.
194
- * @returns {Object} The calculated position.
195
- */
196
- function calculatePosition(input, aes, row, stat, index, midpoint) {
197
- const axis = input.vertical ? 'y' : 'x'
198
- const oppositeAxis = input.vertical ? 'x' : 'y'
199
- const axisValue = input.uniques[axis].indexOf(row[aes[axis]])
200
- const position1 = stat[index] - midpoint
201
- const position0 = position1 - row[aes.stat]
202
-
203
- return {
204
- [axis]: axisValue,
205
- [`${oppositeAxis}1`]: position1,
206
- [`${oppositeAxis}0`]: position0
207
- }
208
- }
209
-
210
- export function funnel(input, aes, width, height) {
211
- let data = convertToPhases(input, aes)
212
- data = mirror(data, aes)
213
- const curve = aes.curve === 'basis' ? curveBasis : data.vertical ? curveBumpY : curveBumpX
214
-
215
- if ('fill' in aes) {
216
- let stats = flatten(data.stats.map((phase) => phase.value))
217
-
218
- data.stats = nest()
219
- .key((d) => d[aes.fill])
220
- .rollup((rows) => {
221
- let last = data.vertical ? { y: rows.length } : { x: rows.length }
222
- return [...rows, { ...rows[rows.length - 1], ...last }]
223
- })
224
- .entries(stats)
225
- }
226
- data.scale = getScales(data, width, height)
227
- data.path = getPaths(data.vertical, data.scale, curve)
228
- data.labels = getLabels(data)
229
- return data
230
- }
package/src/lib/geom.js DELETED
@@ -1,99 +0,0 @@
1
- import { sum, min, max, mean, mode, median, deviation, variance, flatRollup } from 'd3-array'
2
-
3
- const summaries = {
4
- identity: (value) => value,
5
- count: (values) => values.length,
6
- sum: (values) => sum(values),
7
- min: (values) => min(values),
8
- max: (values) => max(values),
9
- mean: (values) => mean(values),
10
- median: (values) => median(values),
11
- mode: (values) => mode(values),
12
- variance: (values) => variance(values),
13
- deviation: (values) => deviation(values)
14
- }
15
-
16
- /**
17
- * Returns an aggregator function for an input string or function.
18
- *
19
- * @param {string|function} stat
20
- * @returns
21
- */
22
- export function rollup(stat) {
23
- if (typeof stat === 'function') return stat
24
- if (typeof stat !== 'string') throw new TypeError('stat must be a string or function')
25
- if (!(stat in summaries)) throw new TypeError('Unknown stat: ' + stat)
26
-
27
- return summaries[stat]
28
- }
29
-
30
- /**
31
- * Aesthetics for a chart.
32
- *
33
- * @typedef Aesthetics
34
- * @property {string} x
35
- * @property {string} y
36
- * @property {string} [fill]
37
- * @property {string} [size]
38
- * @property {string} [color]
39
- * @property {string} [shape]
40
- * @property {string} [pattern]
41
- */
42
-
43
- /**
44
- *
45
- * @param {Array<any>} data
46
- * @param {Aesthetics} aes
47
- * @param {function|string} stat
48
- * @returns
49
- */
50
- export function aggregate(data, aes, stat = 'identity') {
51
- const agg = rollup(stat)
52
- const keys = ['color', 'fill', 'pattern', 'shape', 'size'].filter((k) =>
53
- Object.keys(aes).includes(k)
54
- )
55
-
56
- let groups = keys.map((k) => (d) => d[aes[k]])
57
-
58
- return flatRollup(
59
- data,
60
- (v) => agg(v.map((d) => d[aes.y])),
61
- (d) => d[aes.x],
62
- ...groups
63
- )
64
- }
65
-
66
- // export function geomBars(chart, aes) {
67
- // const { x, y, fill, color, pattern } = { ...aes, ...chart.aes }
68
- // return aggregate(chart.data, { x, y, fill, color, pattern })
69
- // }
70
-
71
- // export function geomLines(chart, aes) {
72
- // const { x, y, color } = { ...aes, ...chart.aes }
73
- // return aggregate(chart.data, { x, y, color })
74
- // }
75
-
76
- // export function geomViolin(chart, aes) {
77
- // const { x, y, fill, color, pattern } = { ...aes, ...chart.aes }
78
- // return { x, y, fill, color, pattern, ...opts }
79
- // }
80
-
81
- // export function geomArea(chart, aes) {
82
- // const { x, y, fill, color, pattern } = { ...aes, ...chart.aes }
83
- // return { x, y, fill, color, pattern, ...opts }
84
- // }
85
-
86
- // export function geomTrend(chart, aes) {
87
- // const { x, y, fill, color, pattern } = { ...aes, ...chart.aes }
88
- // return { x, y, fill, color, pattern, ...opts }
89
- // }
90
-
91
- // export function geomPoints(chart, aes) {
92
- // const { x, y, fill, color, shape, size } = { ...aes, ...chart.aes }
93
- // return { x, y, fill, color, shape, size, ...opts }
94
- // }
95
-
96
- // export function violin(data, mapping) {}
97
- // export function bar(data, mapping) {}
98
- // export function scatter(data, mapping) {}
99
- // export function line(data, mapping) {}
@@ -1,68 +0,0 @@
1
- import {
2
- isSunday,
3
- addDays,
4
- subMonths,
5
- startOfMonth,
6
- format,
7
- previousSunday,
8
- differenceInDays,
9
- differenceInWeeks,
10
- endOfWeek
11
- } from 'date-fns'
12
- import { nest } from 'd3-collection'
13
- // import { group } from 'd3-array'
14
-
15
- const DATE_FORMAT = 'yyyy-MM-dd'
16
- const weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
17
-
18
- export function summarize(data, dateField = 'date', valueField) {
19
- // const summary = group(data, d => d[dateField])
20
- const nested = nest()
21
- .key((d) => format(new Date(d[dateField]), DATE_FORMAT))
22
- .rollup((d) =>
23
- valueField ? d.map((value) => value[valueField]).reduce((a, b) => a + b, 0) : d.length
24
- )
25
- .entries(data)
26
-
27
- return nested.reduce((obj, item) => ((obj[item.key] = item.value), obj), {})
28
- }
29
-
30
- export function heatmap(data, numberOfMonths) {
31
- const today = new Date()
32
- const firstDay = getFirstDay(numberOfMonths, today)
33
- const months = {}
34
- const grid = generateGrid(firstDay, today).map((d) => ({
35
- ...d,
36
- value: d.date in data ? data[d.date] : 0
37
- }))
38
-
39
- grid.map((d) => {
40
- const month = format(endOfWeek(new Date(d.date)), 'MMM')
41
-
42
- if (!(month in months)) {
43
- months[month] = d.x
44
- }
45
- })
46
- return {
47
- grid,
48
- months,
49
- weekdays,
50
- numberOfWeeks: differenceInWeeks(today, firstDay) + 1
51
- }
52
- }
53
-
54
- function getFirstDay(months, lastDay = new Date()) {
55
- const firstDay = subMonths(startOfMonth(lastDay), months)
56
- return isSunday(firstDay) ? firstDay : previousSunday(firstDay)
57
- }
58
-
59
- function generateGrid(firstDay, lastDay) {
60
- const days = differenceInDays(lastDay, firstDay) + 1
61
-
62
- const grid = [...Array(days).keys()].map((day) => ({
63
- x: Math.floor(day / 7),
64
- y: day % 7,
65
- date: format(addDays(firstDay, day), DATE_FORMAT)
66
- }))
67
- return grid
68
- }