@rokkit/chart 1.0.0-next.11
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/LICENSE +21 -0
- package/README.md +55 -0
- package/package.json +63 -0
- package/src/chart/FacetGrid.svelte +51 -0
- package/src/chart/Grid.svelte +34 -0
- package/src/chart/Legend.svelte +16 -0
- package/src/chart/PatternDefs.svelte +13 -0
- package/src/chart/Swatch.svelte +93 -0
- package/src/chart/SwatchButton.svelte +29 -0
- package/src/chart/SwatchGrid.svelte +55 -0
- package/src/chart/Symbol.svelte +37 -0
- package/src/chart/Texture.svelte +16 -0
- package/src/chart/TexturedShape.svelte +27 -0
- package/src/chart/TimelapseChart.svelte +97 -0
- package/src/chart/Timer.svelte +27 -0
- package/src/chart.js +9 -0
- package/src/components/charts/Axis.svelte +66 -0
- package/src/components/charts/Chart.svelte +35 -0
- package/src/components/index.js +23 -0
- package/src/components/lib/axis.js +0 -0
- package/src/components/lib/chart.js +187 -0
- package/src/components/lib/color.js +327 -0
- package/src/components/lib/funnel.js +204 -0
- package/src/components/lib/index.js +19 -0
- package/src/components/lib/pattern.js +190 -0
- package/src/components/lib/rollup.js +55 -0
- package/src/components/lib/shape.js +199 -0
- package/src/components/lib/summary.js +145 -0
- package/src/components/lib/theme.js +23 -0
- package/src/components/lib/timer.js +41 -0
- package/src/components/lib/utils.js +165 -0
- package/src/components/plots/BarPlot.svelte +36 -0
- package/src/components/plots/BoxPlot.svelte +54 -0
- package/src/components/plots/ScatterPlot.svelte +30 -0
- package/src/components/store.js +70 -0
- package/src/constants.js +66 -0
- package/src/elements/Bar.svelte +35 -0
- package/src/elements/ColorRamp.svelte +51 -0
- package/src/elements/ContinuousLegend.svelte +46 -0
- package/src/elements/DiscreteLegend.svelte +41 -0
- package/src/elements/Label.svelte +12 -0
- package/src/elements/PatternDefs.svelte +13 -0
- package/src/elements/PatternMask.svelte +20 -0
- package/src/elements/Symbol.svelte +38 -0
- package/src/elements/Tooltip.svelte +23 -0
- package/src/funnel.svelte +35 -0
- package/src/geom.js +105 -0
- package/src/index.js +16 -0
- package/src/lib/axis.js +75 -0
- package/src/lib/colors.js +32 -0
- package/src/lib/geom.js +4 -0
- package/src/lib/shapes.js +144 -0
- package/src/lib/timer.js +44 -0
- package/src/lib/utils.js +157 -0
- package/src/lookup.js +29 -0
- package/src/plots/BarPlot.svelte +55 -0
- package/src/plots/BoxPlot.svelte +0 -0
- package/src/plots/FunnelPlot.svelte +33 -0
- package/src/plots/HeatMap.svelte +5 -0
- package/src/plots/HeatMapCalendar.svelte +129 -0
- package/src/plots/LinePlot.svelte +55 -0
- package/src/plots/Plot.svelte +25 -0
- package/src/plots/RankBarPlot.svelte +38 -0
- package/src/plots/ScatterPlot.svelte +20 -0
- package/src/plots/ViolinPlot.svelte +11 -0
- package/src/plots/heatmap.js +70 -0
- package/src/plots/index.js +10 -0
- package/src/swatch.js +11 -0
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
// // console.log(data)
|
|
135
|
+
|
|
136
|
+
// if ('fill' in aes) {
|
|
137
|
+
// let stats = flatten(data.stats.map((phase) => phase.value))
|
|
138
|
+
// // console.log(stats)
|
|
139
|
+
// data.stats = nest()
|
|
140
|
+
// .key((d) => d[aes.fill])
|
|
141
|
+
// .rollup((rows) => [...rows, { ...rows[rows.length - 1], x: rows.length }])
|
|
142
|
+
// .entries(stats)
|
|
143
|
+
// }
|
|
144
|
+
// return data
|
|
145
|
+
// }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { builtIn as patterns } from './pattern'
|
|
2
|
+
import { namedShapes as shapes } from './shape'
|
|
3
|
+
import { builtIn as symbols } from './shape'
|
|
4
|
+
import { colors, palette } from './color'
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
export class ThemeBrewer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.patterns = []
|
|
11
|
+
this.shapes = []
|
|
12
|
+
this.palette = []
|
|
13
|
+
this.defaults = {}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const builtIn = {
|
|
18
|
+
colors,
|
|
19
|
+
palette,
|
|
20
|
+
patterns,
|
|
21
|
+
shapes,
|
|
22
|
+
symbols
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { writable } from 'svelte/store'
|
|
2
|
+
|
|
3
|
+
let req
|
|
4
|
+
let prev
|
|
5
|
+
const elapsed = writable(0)
|
|
6
|
+
|
|
7
|
+
const tick = (timestamp) => {
|
|
8
|
+
if (!prev) prev = timestamp
|
|
9
|
+
const diff = Math.round(timestamp - prev)
|
|
10
|
+
prev = timestamp
|
|
11
|
+
elapsed.update((e) => e + diff)
|
|
12
|
+
req = window.requestAnimationFrame(tick)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const timer = {
|
|
16
|
+
start() {
|
|
17
|
+
if (typeof window === 'undefined') return
|
|
18
|
+
else if (!req) {
|
|
19
|
+
prev = null
|
|
20
|
+
req = window.requestAnimationFrame(tick)
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
stop() {
|
|
24
|
+
if (typeof window === 'undefined') return
|
|
25
|
+
else if (req) {
|
|
26
|
+
window.cancelAnimationFrame(req)
|
|
27
|
+
req = null
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
toggle() {
|
|
31
|
+
req ? timer.stop() : timer.start()
|
|
32
|
+
},
|
|
33
|
+
set(val) {
|
|
34
|
+
if (typeof val === 'number') elapsed.set(val)
|
|
35
|
+
},
|
|
36
|
+
reset() {
|
|
37
|
+
timer.set(0)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { timer, elapsed }
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { scaleBand, scaleLinear } from 'd3-scale'
|
|
2
|
+
import { min, max } from 'd3-array'
|
|
3
|
+
import { ascending, quantile } from 'd3-array'
|
|
4
|
+
import { nest } from 'd3-collection'
|
|
5
|
+
import { omit, filter } from 'ramda'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generates a unique id from current timestamp
|
|
9
|
+
*
|
|
10
|
+
* @returns {String} timestamp based unique id
|
|
11
|
+
*/
|
|
12
|
+
export function uniqueId(prefix = '') {
|
|
13
|
+
return prefix + Date.now().toString(36)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Capitalizes the first letter of input string
|
|
18
|
+
*
|
|
19
|
+
* @param {String} str
|
|
20
|
+
* @returns {String}
|
|
21
|
+
*/
|
|
22
|
+
export function initCap(str) {
|
|
23
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Removes undefined and null values from the input object.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} obj
|
|
30
|
+
* @returns {Object}
|
|
31
|
+
*/
|
|
32
|
+
export function compact(obj) {
|
|
33
|
+
return filter((x) => x !== undefined && x !== null, obj)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Converts an input number into it's hexadecimal representation, with optional left padded zeroes based on the `size`
|
|
38
|
+
*
|
|
39
|
+
* @param {number} value
|
|
40
|
+
* @param {number} size
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
export function toHexString(value, size = 2) {
|
|
44
|
+
return value.toString(16).padStart(size, '0')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Calculates a grid of centres to fit a list of items of `size` within the number of `columns` and `rows`.
|
|
49
|
+
*
|
|
50
|
+
* - Attempts to find a best fit square if both columns and rows are not specified
|
|
51
|
+
* - Value in columns is prioritized over rows for recalculating the grid
|
|
52
|
+
* - Supports padding between the items
|
|
53
|
+
*
|
|
54
|
+
* @param {number} count
|
|
55
|
+
* @param {number} size
|
|
56
|
+
* @param {number} pad
|
|
57
|
+
* @param {number} columns
|
|
58
|
+
* @param {number} rows
|
|
59
|
+
* @returns
|
|
60
|
+
*/
|
|
61
|
+
export function swatch(count, size, pad = 0, columns, rows) {
|
|
62
|
+
if (columns > 0) {
|
|
63
|
+
rows = Math.ceil(count / columns)
|
|
64
|
+
} else if (rows > 0) {
|
|
65
|
+
columns = Math.ceil(count / rows)
|
|
66
|
+
} else {
|
|
67
|
+
columns = Math.ceil(Math.sqrt(count))
|
|
68
|
+
rows = Math.ceil(count / columns)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const width = (size + pad) * columns + pad
|
|
72
|
+
const height = (size + pad) * rows + pad
|
|
73
|
+
const data = [...Array(count).keys()].map((index) => ({
|
|
74
|
+
cx: (size + pad) / 2 + (index % columns) * (size + pad),
|
|
75
|
+
cy: (size + pad) / 2 + Math.floor(index / columns) * (size + pad),
|
|
76
|
+
r: size / 2
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
return { width, height, data }
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get a scale function mapping the values between a range of lower and upper values
|
|
83
|
+
*
|
|
84
|
+
* @param {array} values
|
|
85
|
+
* @param {array[2]} bounds
|
|
86
|
+
* @param {number} buffer
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
export function getScale(values, bounds, buffer = 0) {
|
|
90
|
+
if (values.some(isNaN)) {
|
|
91
|
+
return scaleBand().range(bounds).domain(values).padding(0.5)
|
|
92
|
+
} else {
|
|
93
|
+
// ensure that all numeric values are converted to numbers so that d3 min/max provide correct results
|
|
94
|
+
values = values.map((n) => +n)
|
|
95
|
+
|
|
96
|
+
let minValue = min(values)
|
|
97
|
+
let maxValue = max(values)
|
|
98
|
+
|
|
99
|
+
if (minValue < 0 && maxValue > 0) {
|
|
100
|
+
maxValue = max([-1 * minValue, maxValue])
|
|
101
|
+
minValue = -1 * maxValue
|
|
102
|
+
}
|
|
103
|
+
const margin = (maxValue - minValue) * buffer
|
|
104
|
+
return scaleLinear()
|
|
105
|
+
.domain([minValue - margin, maxValue + margin])
|
|
106
|
+
.range(bounds)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Obtain the scale function for the `x` and `y` fields in the data set.
|
|
111
|
+
*
|
|
112
|
+
* @param {array<dict>} data
|
|
113
|
+
* @param {string} x
|
|
114
|
+
* @param {string} y
|
|
115
|
+
* @param {number} width
|
|
116
|
+
* @param {number} height
|
|
117
|
+
* @returns
|
|
118
|
+
*/
|
|
119
|
+
export function getScales(data, x, y, width, height) {
|
|
120
|
+
const xValues = [...new Set(data.map((item) => item[x]))]
|
|
121
|
+
const yValues = [...new Set(data.map((item) => item[y]))]
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
scaleX: getScale(xValues, [0, width]),
|
|
125
|
+
scaleY: getScale(yValues, [height, 0], 0.1)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Summarize `data` by fields `x` and `y` and return a nested array with
|
|
131
|
+
* key as unique `x` values and value as statistical summaries of `y` values
|
|
132
|
+
*
|
|
133
|
+
* @param {*} data
|
|
134
|
+
* @param {*} x
|
|
135
|
+
* @param {*} y
|
|
136
|
+
* @returns
|
|
137
|
+
*/
|
|
138
|
+
export function aggregate(data, x, y) {
|
|
139
|
+
const summary = nest()
|
|
140
|
+
.key((d) => d[x])
|
|
141
|
+
.rollup((d) => {
|
|
142
|
+
let values = d.map((g) => g[y]).sort(ascending)
|
|
143
|
+
let q1 = quantile(values, 0.25)
|
|
144
|
+
let q3 = quantile(values, 0.75)
|
|
145
|
+
let median = quantile(values, 0.5)
|
|
146
|
+
let interQuantileRange = q3 - q1
|
|
147
|
+
let min = q1 - 1.5 * interQuantileRange
|
|
148
|
+
let max = q3 + 1.5 * interQuantileRange
|
|
149
|
+
return { q1, q3, median, interQuantileRange, min, max }
|
|
150
|
+
})
|
|
151
|
+
.entries(data)
|
|
152
|
+
return summary
|
|
153
|
+
}
|
|
154
|
+
export function getPaletteForValues(values, { palette, fallback }) {
|
|
155
|
+
return values.map((value, index) =>
|
|
156
|
+
index < palette.length ? palette[index] : fallback
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function toNested(data, key, label) {
|
|
161
|
+
return nest()
|
|
162
|
+
.key((d) => d[key])
|
|
163
|
+
.rollup((values) => values.map((value) => omit([key], value)))
|
|
164
|
+
.entries(data.sort((a, b) => ascending(a[label], b[label])))
|
|
165
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
|
|
4
|
+
let chart = getContext('chart')
|
|
5
|
+
|
|
6
|
+
// export let data = []
|
|
7
|
+
export let limit = 8
|
|
8
|
+
export let fields
|
|
9
|
+
// export let valueFormat
|
|
10
|
+
// export let scales
|
|
11
|
+
export let colors
|
|
12
|
+
export let h = 10
|
|
13
|
+
|
|
14
|
+
function size(value) {
|
|
15
|
+
// console.log($chart.axis.x.scale, value)
|
|
16
|
+
return $chart.axis.x.scale(value)
|
|
17
|
+
}
|
|
18
|
+
function top(value) {
|
|
19
|
+
// console.log('Y', value)
|
|
20
|
+
return $chart.axis.y.scale(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
$: bars = $chart.data.slice(0, limit)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
{#each bars as item}
|
|
27
|
+
<rect
|
|
28
|
+
x="0"
|
|
29
|
+
y={top(item[fields.y]) + h / 2}
|
|
30
|
+
width={size(item[fields.x])}
|
|
31
|
+
height={h * 3}
|
|
32
|
+
fill={colors[item[fields.label]]}
|
|
33
|
+
/>
|
|
34
|
+
<text x="10" y={top(item[fields.y]) + 2.5 * h}>{item[fields.label]}</text>
|
|
35
|
+
<!-- <text x="10" y={top(item[fields.y]) + 10}>{item[fields.x]}</text> -->
|
|
36
|
+
{/each}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
|
|
4
|
+
let chart = getContext('chart')
|
|
5
|
+
|
|
6
|
+
export let whisker = true
|
|
7
|
+
export let boxWidth = 150
|
|
8
|
+
export let stroke = 'green'
|
|
9
|
+
export let fill = '#69b3a2' // fixed color or attribute to be used for color
|
|
10
|
+
export let scaleFill = () => fill // defaults to fill color
|
|
11
|
+
|
|
12
|
+
$: data = $chart.summary()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#each data as { key, value }}
|
|
16
|
+
<rect
|
|
17
|
+
width={boxWidth}
|
|
18
|
+
height={$chart.axis.y.scale(value.q1) - $chart.axis.y.scale(value.q3)}
|
|
19
|
+
x={$chart.axis.x.scale(key) - boxWidth / 2}
|
|
20
|
+
y={$chart.axis.y.scale(value.q3)}
|
|
21
|
+
fill={scaleFill(value[fill])}
|
|
22
|
+
{stroke}
|
|
23
|
+
/>
|
|
24
|
+
<line
|
|
25
|
+
x1={$chart.axis.x.scale(key) - boxWidth / 2}
|
|
26
|
+
x2={$chart.axis.x.scale(key) + boxWidth / 2}
|
|
27
|
+
y1={$chart.axis.y.scale(value.median)}
|
|
28
|
+
y2={$chart.axis.y.scale(value.median)}
|
|
29
|
+
{stroke}
|
|
30
|
+
/>
|
|
31
|
+
<line
|
|
32
|
+
x1={$chart.axis.x.scale(key)}
|
|
33
|
+
x2={$chart.axis.x.scale(key)}
|
|
34
|
+
y1={$chart.axis.y.scale(value.min)}
|
|
35
|
+
y2={$chart.axis.y.scale(value.max)}
|
|
36
|
+
{stroke}
|
|
37
|
+
/>
|
|
38
|
+
{#if whisker}
|
|
39
|
+
<line
|
|
40
|
+
x1={$chart.axis.x.scale(key) - boxWidth / 8}
|
|
41
|
+
x2={$chart.axis.x.scale(key) + boxWidth / 8}
|
|
42
|
+
y1={$chart.axis.y.scale(value.min)}
|
|
43
|
+
y2={$chart.axis.y.scale(value.min)}
|
|
44
|
+
{stroke}
|
|
45
|
+
/>
|
|
46
|
+
<line
|
|
47
|
+
x1={$chart.axis.x.scale(key) - boxWidth / 8}
|
|
48
|
+
x2={$chart.axis.x.scale(key) + boxWidth / 8}
|
|
49
|
+
y1={$chart.axis.y.scale(value.max)}
|
|
50
|
+
y2={$chart.axis.y.scale(value.max)}
|
|
51
|
+
{stroke}
|
|
52
|
+
/>
|
|
53
|
+
{/if}
|
|
54
|
+
{/each}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { clamp } from 'yootils'
|
|
3
|
+
import Symbol from '../chart/Symbol.svelte'
|
|
4
|
+
import { getContext } from 'svelte'
|
|
5
|
+
|
|
6
|
+
const chart = getContext('chart')
|
|
7
|
+
|
|
8
|
+
export let size = 8
|
|
9
|
+
export let fill = '#c0c0c0'
|
|
10
|
+
export let stroke = '#3c3c3c'
|
|
11
|
+
export let jitterWidth = 50
|
|
12
|
+
export let offset
|
|
13
|
+
|
|
14
|
+
$: jitterWidth = clamp(jitterWidth, 0, 100 / 2)
|
|
15
|
+
$: offset = clamp(offset | (jitterWidth / 2), 0, 100)
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
{#if $chart.data}
|
|
19
|
+
{#each $chart.data as d}
|
|
20
|
+
<Symbol
|
|
21
|
+
x={$chart.axis.x.scale(d[$chart.x]) -
|
|
22
|
+
offset +
|
|
23
|
+
Math.random() * jitterWidth}
|
|
24
|
+
y={$chart.axis.y.scale(d[$chart.y])}
|
|
25
|
+
{fill}
|
|
26
|
+
{stroke}
|
|
27
|
+
{size}
|
|
28
|
+
/>
|
|
29
|
+
{/each}
|
|
30
|
+
{/if}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { writable } from 'svelte/store'
|
|
2
|
+
|
|
3
|
+
function createChart() {
|
|
4
|
+
const { subscribe, set } = writable({
|
|
5
|
+
data: [],
|
|
6
|
+
x: '',
|
|
7
|
+
y: ''
|
|
8
|
+
})
|
|
9
|
+
return { subscribe, set }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function animatedChart(input, key, valueFields = [], previous = []) {
|
|
13
|
+
const previousKeys = new Set(previous.map((item) => item[key]))
|
|
14
|
+
const currentKeys = new Set(input.map((item) => item[key]))
|
|
15
|
+
const toAdd = new Set(
|
|
16
|
+
[...currentKeys].filter((key) => !previousKeys.has(key))
|
|
17
|
+
)
|
|
18
|
+
const toRemove = new Set(
|
|
19
|
+
[...previousKeys].filter((key) => !currentKeys.has(key))
|
|
20
|
+
)
|
|
21
|
+
console.log(toAdd, toRemove)
|
|
22
|
+
let data = input
|
|
23
|
+
.filter((item) => toAdd.has(item[key]))
|
|
24
|
+
.map((item) => {
|
|
25
|
+
let el = { ...item }
|
|
26
|
+
valueFields.forEach(
|
|
27
|
+
({ field, initialValue, attrs }) =>
|
|
28
|
+
(el[field] =
|
|
29
|
+
typeof initialValue === 'function'
|
|
30
|
+
? initialValue(el, attrs)
|
|
31
|
+
: initialValue)
|
|
32
|
+
)
|
|
33
|
+
return el
|
|
34
|
+
})
|
|
35
|
+
let prev = previous
|
|
36
|
+
.filter((item) => !toRemove.has(item[key]))
|
|
37
|
+
.map((item) => {
|
|
38
|
+
let el = { ...item }
|
|
39
|
+
valueFields.forEach(
|
|
40
|
+
({ field, initialValue, attrs }) =>
|
|
41
|
+
(el[field] =
|
|
42
|
+
typeof initialValue === 'function'
|
|
43
|
+
? initialValue(el, { ...attrs, isPrevious: true })
|
|
44
|
+
: initialValue)
|
|
45
|
+
)
|
|
46
|
+
return el
|
|
47
|
+
})
|
|
48
|
+
data = [...prev, ...data]
|
|
49
|
+
console.log(data)
|
|
50
|
+
return tweened(data, { duration: 500, easing: cubicOut })
|
|
51
|
+
}
|
|
52
|
+
// function createAxis() {
|
|
53
|
+
// const { subscribe, set } = writable({})
|
|
54
|
+
|
|
55
|
+
// let data
|
|
56
|
+
// let x = {}
|
|
57
|
+
// let y = {}
|
|
58
|
+
|
|
59
|
+
// // find origin & orientation
|
|
60
|
+
// function getOriginAndOrientation(data, x, y) {
|
|
61
|
+
// this.data = data
|
|
62
|
+
// this.x = x
|
|
63
|
+
// this.y = y
|
|
64
|
+
// }
|
|
65
|
+
// function init(config, data) {}
|
|
66
|
+
// return { subscribe, init, set, getOriginAndOrientation }
|
|
67
|
+
// }
|
|
68
|
+
|
|
69
|
+
// export const chart = createChart()
|
|
70
|
+
// export const axis = createAxis()
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const __muted__ = {
|
|
2
|
+
color: '#eeeeee',
|
|
3
|
+
fill: 'empty',
|
|
4
|
+
shape: 'circle'
|
|
5
|
+
}
|
|
6
|
+
export const __colors__ = [
|
|
7
|
+
'#FFDE6B',
|
|
8
|
+
'#EF89EE',
|
|
9
|
+
'#F79F1E',
|
|
10
|
+
'#02B8FF',
|
|
11
|
+
'#9F84EC',
|
|
12
|
+
'#15CBC4',
|
|
13
|
+
'#0092FD',
|
|
14
|
+
'#F63A57',
|
|
15
|
+
'#A2CB39',
|
|
16
|
+
'#FF6E2F',
|
|
17
|
+
'#FEB8B9',
|
|
18
|
+
'#af7aa1',
|
|
19
|
+
'#7EFFF5'
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export const __patterns__ = {
|
|
23
|
+
A: 'M0 5A6 6 0 0 0 10 5',
|
|
24
|
+
B: 'M0 10L10 0',
|
|
25
|
+
C: 'M0 0A10 10 0 0 0 10 10',
|
|
26
|
+
D: 'M0 0L10 10',
|
|
27
|
+
E: 'M10 5A6 6 0 0 0 0 5',
|
|
28
|
+
F: 'M10 10A10 10 0 0 0 0 0',
|
|
29
|
+
G: 'M0 0L10 10ZM10 0L0 10Z',
|
|
30
|
+
H: 'M1 1L9 1L9 9L1 9Z',
|
|
31
|
+
I: 'M4 0L4 10M6 10L6 0M0 4L10 4M10 6L0 6',
|
|
32
|
+
J: 'M0 2L8 10M2 0L10 8M0 8L8 0M2 10L10 2',
|
|
33
|
+
K: 'M5 1A 4 4 0 0 0 9 5A4 4 0 0 0 5 9A4 4 0 0 0 1 5A4 4 0 0 0 5 1',
|
|
34
|
+
L: 'M1 3L7 9M3 1L9 7M1 7L7 1M3 9L9 3',
|
|
35
|
+
M: 'M2 2A4 4 0 0 0 8 2A4 4 0 0 0 8 8A4 4 0 0 0 2 8A4 4 0 0 0 2 2',
|
|
36
|
+
N: 'M0 0A5 5 0 0 0 10 0A5 5 0 0 0 10 10A5 5 0 0 0 0 10A5 5 0 0 0 0 0',
|
|
37
|
+
O: 'M5 2A 3 3 0 0 0 8 5A3 3 0 0 0 5 8A3 3 0 0 0 2 5A3 3 0 0 0 5 2',
|
|
38
|
+
P: 'M2 5L5 2L8 5L5 8Z',
|
|
39
|
+
Q: 'M3 5A2 2 0 0 0 7 5A2 2 0 0 0 3 5M1 5L9 5M5 1L5 9',
|
|
40
|
+
R: 'M2 8L8 2ZM1.5 3.5L3.5 1.5ZM6.5 8.5L8.5 6.5ZM0 0L10 10Z',
|
|
41
|
+
S:
|
|
42
|
+
'M2 8L8 2ZM1.5 3.5L3.5 1.5Z' +
|
|
43
|
+
'M6.5 8.5L8.5 6.5Z' +
|
|
44
|
+
'M2 2L8 8M1.5 6.5L3.5 8.5' +
|
|
45
|
+
'M6.5 1.5L8.5 3.5',
|
|
46
|
+
T:
|
|
47
|
+
'M5 1 A6 6 0 0 0 5 9' +
|
|
48
|
+
'A6 6 0 0 0 5 1' +
|
|
49
|
+
'M1 5A6 6 0 0 0 9 5A6 6 0 0 0 1 5',
|
|
50
|
+
U:
|
|
51
|
+
'M1.5 5A1 1 0 0 0 3.5 5A1 1 0 0 0 1.5 5' +
|
|
52
|
+
'M6.5 5A1 1 0 0 0 8.5 5A1 1 0 0 0 6.5 5' +
|
|
53
|
+
'M5 1.5A1 1 0 0 0 5 3.5A1 1 0 0 0 5 1.5' +
|
|
54
|
+
'M5 6.5A1 1 0 0 0 5 8.5A1 1 0 0 0 5 6.5',
|
|
55
|
+
V:
|
|
56
|
+
'M1.5 2.5A1 1 0 0 0 3.5 2.5A1 1 0 0 0 1.5 2.5' +
|
|
57
|
+
'M6.5 2.5A1 1 0 0 0 8.5 2.5A1 1 0 0 0 6.5 2.5' +
|
|
58
|
+
'M2.5 6.5A1 1 0 0 0 2.5 8.5A1 1 0 0 0 2.5 6.5' +
|
|
59
|
+
'M7.5 6.5A1 1 0 0 0 7.5 8.5A1 1 0 0 0 7.5 6.5' +
|
|
60
|
+
'M3.5 5A1 1 0 0 0 6.5 5A1 1 0 0 0 3.5 5',
|
|
61
|
+
W: 'M5 0L6 4L10 5L6 6L5 10L4 6L0 5L4 4Z' + 'M2 1V3M1 2H3' + 'M8 9V7M9 8H7',
|
|
62
|
+
X: 'M5 2L2.5 9L8.8 4.6L1.2 4.6L7.5 9Z',
|
|
63
|
+
Y: 'M0 5A5 5 0 0 0 5 0' + 'M5 10A5 5 0 0 0 0 5' + 'M5 10A5 5 0 0 0 5 0',
|
|
64
|
+
Z: 'M0 0L10 10M5 0L10 5M0 5 L5 10',
|
|
65
|
+
Z1: 'M0 0L10 10M3 0L10 7M0 7 L3 10'
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { format } from 'd3-format'
|
|
3
|
+
import Label from './Label.svelte'
|
|
4
|
+
|
|
5
|
+
export let rank
|
|
6
|
+
export let value
|
|
7
|
+
export let name
|
|
8
|
+
export let formatString = '.1%'
|
|
9
|
+
export let scales
|
|
10
|
+
export let height = 60
|
|
11
|
+
export let fill
|
|
12
|
+
export let spaceBetween = 5
|
|
13
|
+
|
|
14
|
+
const textHeight = 16
|
|
15
|
+
const charWidth = 12
|
|
16
|
+
$: y = rank * (height + spaceBetween)
|
|
17
|
+
$: width = $scales.x(value)
|
|
18
|
+
|
|
19
|
+
$: textWidth = name.length * charWidth
|
|
20
|
+
$: textOffset = width <= textWidth ? width + charWidth : width
|
|
21
|
+
$: textAnchor = textOffset > width ? 'start' : 'end'
|
|
22
|
+
|
|
23
|
+
$: formattedValue = format(formatString)(value)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<rect x={$scales.x(0)} {y} {width} {height} {fill} opacity={0.5} />
|
|
27
|
+
<rect x={$scales.x(0)} {y} width={5} {height} {fill} />
|
|
28
|
+
<Label x={width} y={y + textHeight + 8} anchor={textAnchor} label={name} />
|
|
29
|
+
<Label
|
|
30
|
+
x={width}
|
|
31
|
+
y={y + height - 14}
|
|
32
|
+
anchor={textAnchor}
|
|
33
|
+
label={formattedValue}
|
|
34
|
+
small
|
|
35
|
+
/>
|