@rokkit/chart 1.0.0-next.87 → 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
package/src/lib/chart.js CHANGED
@@ -1,189 +1,208 @@
1
- // export function scales(data, aes, opts) {
2
- // const { x, y } = aes
3
- // const { width, height, flipCoords } = {
4
- // width: 800,
5
- // height: 600,
6
- // flipCoords: false,
7
- // ...opts
8
- // }
9
-
10
- // return { x, y, width, height, flipCoords }
11
- // }
12
-
13
- import { nest } from 'd3-collection'
14
- import { max, quantile, ascending, bin } from 'd3-array'
15
- import { scaleLinear } from 'd3-scale'
16
- import { area, curveCatmullRom } from 'd3-shape'
17
- import { getScale } from './utils'
18
-
19
- /**
20
- * axis, theme, params, fields
21
- */
22
- export class ChartBrewer {
23
- constructor(data, x, y) {
24
- this.data = data
25
- this.x = x
26
- this.y = y
27
- this.fill = x
28
- this.axis = null
29
- this.stats = {}
30
- // this.yOffset = 20
31
- this.padding = 10
1
+ import { min, max } from 'd3-array'
2
+ import { scaleBand, scaleLinear, scaleTime } from 'd3-scale'
3
+
4
+ function getScale(domain, range, padding = 0) {
5
+ if (domain.some(isNaN)) {
6
+ return scaleBand().domain(domain).range(range).padding(padding)
7
+ } else if (domain[0] instanceof Date) {
8
+ return scaleTime()
9
+ .domain([min(domain), max(domain)])
10
+ .range(range)
11
+ .nice()
12
+ }
13
+
14
+ return scaleLinear()
15
+ .domain([min([0, ...domain]), max([0, ...domain])])
16
+ .range(range)
17
+ .nice()
18
+ }
19
+
20
+ class Chart {
21
+ // data = []
22
+ // width = 512
23
+ // height = 512
24
+ // origin = { x: 0, y: 0 }
25
+ // range = {
26
+ // x: [0, this.width],
27
+ // y: [this.height, 0]
28
+ // }
29
+ // x
30
+ // y
31
+ // stat = 'identity'
32
+ // scale
33
+ // fill
34
+ // color
35
+ // value
36
+ // shape
37
+ // valueFormat
38
+ // valueLabel
39
+ // domain
40
+ // margin
41
+ // spacing
42
+ // padding
43
+ // flipCoords = false
44
+
45
+ constructor(data, opts) {
46
+ this.width = +opts.width || 2048
47
+ this.height = +opts.height || 2048
48
+ this.flipCoords = opts.flipCoords || false
49
+ this.x = opts.x
50
+ this.y = opts.y
51
+ this.value = opts.value || opts.y
52
+ this.valueLabel = opts.valueLabel || this.value
53
+ this.valueFormat = opts.valueFormat || ((d) => d)
54
+ this.fill = opts.fill || opts.x
55
+ this.color = opts.color || opts.fill
56
+ this.shape = opts.shape || opts.fill
57
+
58
+ this.padding = opts.padding !== undefined ? +opts.padding : 32
59
+
60
+ this.spacing = +opts.spacing >= 0 && +opts.spacing <= 0.5 ? +opts.spacing : 0
32
61
  this.margin = {
33
- left: 10,
34
- top: 10,
35
- right: 10,
36
- bottom: 10
62
+ top: +opts.margin?.top || 0,
63
+ left: +opts.margin?.left || 0,
64
+ right: +opts.margin?.right || 0,
65
+ bottom: +opts.margin?.bottom || 0
37
66
  }
38
- this.params = {
39
- ticks: {}
67
+ this.domain = {
68
+ x: [...new Set(data.map((d) => d[this.x]))],
69
+ y: [...new Set(data.map((d) => d[this.y]))]
40
70
  }
41
- this.labels = []
42
- this.width = 800
43
- this.height = (this.width * 7) / 16
44
- this.scaleValues = null
45
- this.theme = {}
46
- }
47
-
48
- computeMargin(xAxisOrientation, yAxisOrientation) {
49
- this.scaleValues = {
50
- x: [...new Set(this.data.map((item) => item[this.x]))],
51
- y: [...new Set(this.data.map((item) => item[this.y]))],
52
- fill: [...new Set(this.data.map((item) => item[this.fill]))]
71
+ if (this.flipCoords) {
72
+ this.domain = { y: this.domain.x, x: this.domain.y }
53
73
  }
74
+ this.stat = opts.stat || 'identity'
75
+
76
+ this.data = data.map((d) => ({
77
+ x: this.flipCoords ? d[this.y] : d[this.x],
78
+ y: this.flipCoords ? d[this.x] : d[this.y],
79
+ fill: d[this.fill],
80
+ color: d[this.color],
81
+ shape: d[this.shape]
82
+ }))
54
83
 
55
- let xOffset = max(this.scaleValues.y.map((value) => value.toString().length)) * 10
56
- let yOffset = 20
84
+ this.refresh()
85
+ }
57
86
 
58
- this.margin = {
59
- left: this.padding + (yAxisOrientation === 'left' ? xOffset : 0),
60
- right: this.padding + (yAxisOrientation === 'left' ? 0 : xOffset),
61
- top: this.padding + (xAxisOrientation === 'bottom' ? 0 : yOffset),
62
- bottom: this.padding + (xAxisOrientation === 'bottom' ? yOffset : 0)
63
- }
87
+ padding(value) {
88
+ this.padding = value
89
+ return this.refresh()
64
90
  }
65
91
 
66
- computeAxis(buffer = 0, inverse = false) {
67
- let x = {}
68
- let y = {}
69
- let fill = {}
92
+ margin(value) {
93
+ this.margin = value
94
+ return this.refresh()
95
+ }
70
96
 
71
- if (!this.scaleValues) {
72
- this.computeMargin('bottom', 'left')
97
+ refresh() {
98
+ this.range = {
99
+ x: [this.margin.left + this.padding, this.width - this.margin.right - this.padding],
100
+ y: [this.height - this.padding - this.margin.bottom, this.margin.top + this.padding]
73
101
  }
74
102
 
75
- x.scale = getScale(
76
- this.scaleValues.x,
77
- [this.margin.left, this.width - this.margin.right],
78
- buffer
79
- )
80
- const domainY = inverse
81
- ? [this.margin.top, this.height - this.margin.bottom]
82
- : [this.height - this.margin.bottom, this.margin.top]
83
- y.scale = getScale(this.scaleValues.y, domainY, buffer)
103
+ let scale = {
104
+ x: getScale(this.domain.x, this.range.x, this.spacing),
105
+ y: getScale(this.domain.y, this.range.y, this.spacing)
106
+ }
84
107
 
85
- x.ticks = tickValues(x.scale, 'x', this.params)
86
- y.ticks = tickValues(y.scale, 'y', this.params)
108
+ // scale['value'] = this.value === this.x ? scale.x : scale.y
87
109
 
88
- this.axis = { x, y, fill }
89
- return this
90
- }
91
- use(theme) {
92
- this.theme = theme
93
- return this
94
- }
110
+ this.origin = {
111
+ x: scale.x.ticks ? scale.x(Math.max(0, Math.min(...scale.x.domain()))) : scale.x.range()[0],
112
+ y: scale.y.ticks ? scale.y(Math.max(0, Math.min(...scale.y.domain()))) : scale.y.range()[0]
113
+ }
95
114
 
96
- params(margin, ticks) {
97
- this.margin = margin
98
- this.ticks = ticks
99
- return this
100
- }
115
+ this.scale = scale
101
116
 
102
- fillWith(fill) {
103
- this.fill = fill
104
117
  return this
105
118
  }
106
119
 
107
- highlight(values) {
108
- this.highlight = values
109
- return this
120
+ // get scale() {
121
+ // return this.scale
122
+ // }
123
+ // get origin() {
124
+ // return this.origin
125
+ // }
126
+ // get margin() {
127
+ // return this.margin
128
+ // }
129
+ // get range() {
130
+ // const [x1, x2] = this.scale.x.range()
131
+ // const [y1, y2] = this.scale.y.range()
132
+
133
+ // return { x1, y1, x2, y2 }
134
+ // }
135
+ // get data() {
136
+ // // aggregate data group by x,y,fill,shape, color
137
+ // // stat = [min, max, avg, std, q1, q3, median, sum, count, box, all]
138
+
139
+ // return this.data
140
+ // }
141
+ // get width() {
142
+ // return this.width
143
+ // }
144
+ // get height() {
145
+ // return this.height
146
+ // }
147
+ // set width(value) {
148
+ // this.width = value
149
+ // }
150
+ // set height(value) {
151
+ // this.height = value
152
+ // }
153
+ // get domain() {
154
+ // return this.domain
155
+ // }
156
+ // get flipCoords() {
157
+ // return this.flipCoords
158
+ // }
159
+ aggregate(value, stat) {
160
+ this.value = value
161
+ this.stat = stat
162
+
163
+ // this.data = nest(this.data)
110
164
  }
111
165
 
112
- animate() {
113
- return this
114
- }
166
+ ticks(axis, count, fontSize = 8) {
167
+ const scale = this.scale[axis]
168
+ const [minRange, maxRange] = scale.range()
169
+ let ticks = []
170
+ let offset = 0
115
171
 
116
- summary() {
117
- const result = nest()
118
- .key((d) => d[this.x])
119
- .rollup((d) => {
120
- let values = d.map((g) => g[this.y]).sort(ascending)
121
- let q1 = quantile(values, 0.25)
122
- let q3 = quantile(values, 0.75)
123
- let median = quantile(values, 0.5)
124
- let interQuantileRange = q3 - q1
125
- let min = q1 - 1.5 * interQuantileRange
126
- let max = q3 + 1.5 * interQuantileRange
127
- return { q1, q3, median, interQuantileRange, min, max }
128
- })
129
- .entries(this.data)
130
- return result
131
- }
172
+ count = count || Math.abs((maxRange - minRange) / (fontSize * (axis === 'y' ? 8 : 8)))
173
+
174
+ if (scale.ticks) {
175
+ ticks = scale.ticks(Math.round(count))
176
+ } else {
177
+ offset = scale.bandwidth() / 2
178
+ count = Math.min(Math.round(count), scale.domain().length)
132
179
 
133
- // assumes axis has been computes
134
- violin() {
135
- if (!this.axis) this.computeAxis()
136
- // Features of the histogram
137
- var histogramBins = bin()
138
- .domain(this.axis.y.scale.domain())
139
- .thresholds(this.axis.y.scale.ticks(20)) // Important: how many bins approx are going to be made? It is the 'resolution' of the violin plot
140
- .value((d) => d)
141
-
142
- // Compute the binning for each group of the dataset
143
- var sumstat = nest()
144
- .key((d) => d[this.x])
145
- .rollup((d) => histogramBins(d.map((g) => +g[this.y])))
146
- .entries(this.data)
147
-
148
- // What is the biggest number of value in a bin? We need it cause this value will have a width of 100% of the bandwidth.
149
- var maxNum = 0
150
- for (let i in sumstat) {
151
- let allBins = sumstat[i].value
152
- let lengths = allBins.map((a) => a.length)
153
- let longest = max(lengths)
154
- if (longest > maxNum) {
155
- maxNum = longest
180
+ ticks = scale.domain()
181
+ if (count < scale.domain().length) {
182
+ let diff = scale.domain().length - count
183
+ ticks = ticks.filter((d, i) => i % diff == 0)
156
184
  }
157
185
  }
158
186
 
159
- // The maximum width of a violin must be x.bandwidth = the width dedicated to a group
160
- var xNum = scaleLinear().range([0, this.axis.x.scale.bandwidth()]).domain([0, maxNum])
161
-
162
- let result = area()
163
- .x0(xNum(0))
164
- .x1(function (d) {
165
- return xNum(d.length)
166
- })
167
- .y((d) => this.axis.y.scale(d.x0))
168
- .curve(curveCatmullRom)
169
-
170
- let areas = sumstat.map((d) => ({
171
- curve: result(d.value),
172
- x: this.axis.x.scale(d.key)
173
- }))
174
- return areas
187
+ ticks = ticks
188
+ .map((t) => ({
189
+ label: t,
190
+ pos: scale(t)
191
+ }))
192
+ .map(({ label, pos }) => ({
193
+ label,
194
+ offset: {
195
+ x: axis === 'x' ? offset : 0,
196
+ y: axis === 'y' ? offset : 0
197
+ },
198
+ x: axis === 'x' ? pos : this.origin.x,
199
+ y: axis === 'y' ? pos : this.origin.y
200
+ }))
201
+
202
+ return ticks
175
203
  }
176
204
  }
177
205
 
178
- function tickValues(scale, whichAxis, params) {
179
- let { values, count } = whichAxis in params.ticks ? params.ticks[whichAxis] : {}
180
- values =
181
- Array.isArray(values) && values.length > 2
182
- ? values
183
- : scale.ticks
184
- ? scale.ticks.apply(scale, [count])
185
- : scale.domain()
186
- const ticks = values.map((label) => ({ label, position: scale(label) }))
187
-
188
- return ticks
206
+ export function chart(data, aes) {
207
+ return new Chart(data, aes)
189
208
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @typedef GridPoint
3
+ * @property {number} x
4
+ * @property {number} y
5
+ * @property {number} r
6
+ */
7
+
8
+ /**
9
+ * @typedef SwatchGrid
10
+ * @property {number} width
11
+ * @property {number} height
12
+ * @property {GridPoint[]} data
13
+ */
14
+ /**
15
+ * Calculates a grid of centres to fit a list of items of `size` within the number of `columns` and `rows`.
16
+ *
17
+ * - Attempts to find a best fit square if both columns and rows are not specified
18
+ * - Value in columns is prioritized over rows for recalculating the grid
19
+ * - Supports padding between the items
20
+ *
21
+ * @param {number} count
22
+ * @param {number} size
23
+ * @param {number} pad
24
+ * @param {number} columns
25
+ * @param {number} rows
26
+ * @returns {SwatchGrid}
27
+ */
28
+ export function swatchGrid(count, size, pad = 0, columns = 0, rows = 0) {
29
+ if (columns > 0) {
30
+ rows = Math.ceil(count / columns)
31
+ } else if (rows > 0) {
32
+ columns = Math.ceil(count / rows)
33
+ } else {
34
+ columns = Math.ceil(Math.sqrt(count))
35
+ rows = Math.ceil(count / columns)
36
+ }
37
+
38
+ const width = (size + pad) * columns + pad
39
+ const height = (size + pad) * rows + pad
40
+ const radius = size / 2
41
+ const data = [...Array(count).keys()].map((index) => ({
42
+ x: pad + radius + (index % columns) * (size + pad),
43
+ y: pad + radius + Math.floor(index / columns) * (size + pad),
44
+ r: radius
45
+ }))
46
+
47
+ return { width, height, data }
48
+ }
49
+
50
+ export function spreadValuesAsPatterns(values, patterns, palette) {
51
+ values
52
+ .map((value, index) => ({
53
+ pattern: patterns[index % patterns.length],
54
+ color: palette[index % palette.length],
55
+ value
56
+ }))
57
+ .reduce(
58
+ (acc, { value, pattern, color }) => ({
59
+ ...acc,
60
+ [value]: {
61
+ id: pattern + '_' + color,
62
+ pattern,
63
+ color
64
+ }
65
+ }),
66
+ {}
67
+ )
68
+ }
package/src/lib/index.js CHANGED
@@ -0,0 +1,4 @@
1
+ export { swatch } from './swatch'
2
+ export { swatchGrid } from './grid'
3
+ export * from './chart'
4
+ export * from './brewer'