@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
package/src/lib/timer.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
|
|
11
|
+
if (diff > 0) {
|
|
12
|
+
prev = timestamp
|
|
13
|
+
}
|
|
14
|
+
elapsed.update((e) => e + diff)
|
|
15
|
+
req = window.requestAnimationFrame(tick)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const timer = {
|
|
19
|
+
start() {
|
|
20
|
+
if (typeof window === 'undefined') return
|
|
21
|
+
else if (!req) {
|
|
22
|
+
prev = null
|
|
23
|
+
req = window.requestAnimationFrame(tick)
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
stop() {
|
|
27
|
+
if (typeof window === 'undefined') return
|
|
28
|
+
else if (req) {
|
|
29
|
+
window.cancelAnimationFrame(req)
|
|
30
|
+
req = null
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
toggle() {
|
|
34
|
+
req ? timer.stop() : timer.start()
|
|
35
|
+
},
|
|
36
|
+
set(val) {
|
|
37
|
+
if (typeof val === 'number') elapsed.set(val)
|
|
38
|
+
},
|
|
39
|
+
reset() {
|
|
40
|
+
timer.set(0)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { timer, elapsed }
|
package/src/lib/utils.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
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 } from 'ramda'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calculates a grid of centres to fit a list of items of `size` within the number of `columns` and `rows`.
|
|
9
|
+
*
|
|
10
|
+
* - Attempts to find a best fit square if both columns and rows are not specified
|
|
11
|
+
* - Value in columns is prioritized over rows for recalculating the grid
|
|
12
|
+
* - Supports padding between the items
|
|
13
|
+
*
|
|
14
|
+
* @param {number} count
|
|
15
|
+
* @param {number} size
|
|
16
|
+
* @param {number} pad
|
|
17
|
+
* @param {number} columns
|
|
18
|
+
* @param {number} rows
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
export function swatch(count, size, pad = 0, columns, rows) {
|
|
22
|
+
if (columns > 0) {
|
|
23
|
+
rows = Math.ceil(count / columns)
|
|
24
|
+
} else if (rows > 0) {
|
|
25
|
+
columns = Math.ceil(count / rows)
|
|
26
|
+
} else {
|
|
27
|
+
columns = Math.ceil(Math.sqrt(count))
|
|
28
|
+
rows = Math.ceil(count / columns)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const width = (size + pad) * columns + pad
|
|
32
|
+
const height = (size + pad) * rows + pad
|
|
33
|
+
const data = [...Array(count).keys()].map((index) => ({
|
|
34
|
+
cx: (size + pad) / 2 + (index % columns) * (size + pad),
|
|
35
|
+
cy: (size + pad) / 2 + Math.floor(index / columns) * (size + pad),
|
|
36
|
+
r: size / 2
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
return { width, height, data }
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a scale function mapping the values between a range of lower and upper values
|
|
43
|
+
*
|
|
44
|
+
* @param {Array} values
|
|
45
|
+
* @param {Array[2]} bounds
|
|
46
|
+
* @param {number} buffer
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
export function getScale(values, bounds, buffer = 0, ordinal = false) {
|
|
50
|
+
if (ordinal || values.some(isNaN)) {
|
|
51
|
+
return scaleBand().range(bounds).domain(values).padding(0.5)
|
|
52
|
+
} else {
|
|
53
|
+
values = values.map((x) => +x)
|
|
54
|
+
|
|
55
|
+
let minValue = min([...values, 0])
|
|
56
|
+
let maxValue = max(values)
|
|
57
|
+
|
|
58
|
+
if (minValue < 0 && maxValue > 0) {
|
|
59
|
+
maxValue = max([-1 * minValue, maxValue])
|
|
60
|
+
minValue = -1 * maxValue
|
|
61
|
+
}
|
|
62
|
+
const margin = (maxValue - minValue) * buffer
|
|
63
|
+
return scaleLinear()
|
|
64
|
+
.domain([minValue - margin, maxValue + margin])
|
|
65
|
+
.range(bounds)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Obtain the scale function for the `x` and `y` fields in the data set.
|
|
70
|
+
*
|
|
71
|
+
* @param {array<dict>} data
|
|
72
|
+
* @param {string} x
|
|
73
|
+
* @param {string} y
|
|
74
|
+
* @param {number} width
|
|
75
|
+
* @param {number} height
|
|
76
|
+
* @returns
|
|
77
|
+
*/
|
|
78
|
+
export function getScales(data, x, y, width, height, margin, ordinal) {
|
|
79
|
+
const xValues = [...new Set(data.map((item) => item[x]))]
|
|
80
|
+
const yValues = [...new Set(data.map((item) => item[y]))]
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
x: getScale(xValues, [0 + margin.left, width - margin.right], 0, ordinal.x),
|
|
84
|
+
y: getScale(yValues, [height - margin.top, margin.bottom], 0.1, ordinal.y)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Summarize `data` by fields `x` and `y` and return a nested array with
|
|
90
|
+
* key as unique `x` values and value as statistical summaries of `y` values
|
|
91
|
+
*
|
|
92
|
+
* @param {*} data
|
|
93
|
+
* @param {*} x
|
|
94
|
+
* @param {*} y
|
|
95
|
+
* @returns
|
|
96
|
+
*/
|
|
97
|
+
export function aggregate(data, x, y) {
|
|
98
|
+
const summary = nest()
|
|
99
|
+
.key((d) => d[x])
|
|
100
|
+
.rollup((d) => {
|
|
101
|
+
let values = d.map((g) => g[y]).sort(ascending)
|
|
102
|
+
let q1 = quantile(values, 0.25)
|
|
103
|
+
let q3 = quantile(values, 0.75)
|
|
104
|
+
let median = quantile(values, 0.5)
|
|
105
|
+
let interQuantileRange = q3 - q1
|
|
106
|
+
let min = q1 - 1.5 * interQuantileRange
|
|
107
|
+
let max = q3 + 1.5 * interQuantileRange
|
|
108
|
+
return { q1, q3, median, interQuantileRange, min, max }
|
|
109
|
+
})
|
|
110
|
+
.entries(data)
|
|
111
|
+
return summary
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate a palette with same size as input data containing values from palette array.
|
|
116
|
+
* After the palette array is exhausted the fallback value is used
|
|
117
|
+
*
|
|
118
|
+
* @param {Array} values
|
|
119
|
+
* @param {Array} palette
|
|
120
|
+
* @param {*} fallback
|
|
121
|
+
* @returns
|
|
122
|
+
*/
|
|
123
|
+
export function getPaletteForValues(values, palette, fallback) {
|
|
124
|
+
return values.map((_, index) =>
|
|
125
|
+
index < palette.length ? palette[index] : fallback
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Converts input object array into a nested key,value array.
|
|
131
|
+
* 'key' contains unique values for the attribute specified by the key parameter
|
|
132
|
+
* and value contains array of all remaining attributes in an array.
|
|
133
|
+
*
|
|
134
|
+
* @param {Array<Object>} data
|
|
135
|
+
* @param {string} key
|
|
136
|
+
* @param {string} label
|
|
137
|
+
* @returns
|
|
138
|
+
*/
|
|
139
|
+
export function toNested(data, key, label) {
|
|
140
|
+
return nest()
|
|
141
|
+
.key((d) => d[key])
|
|
142
|
+
.rollup((values) => values.map((value) => omit([key], value)))
|
|
143
|
+
.entries(data.sort((a, b) => ascending(a[label], b[label])))
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Repeats array items of b using array items of a ask keys
|
|
147
|
+
*
|
|
148
|
+
* @param {Array} b
|
|
149
|
+
* @param {Array} a
|
|
150
|
+
* @returns {Object} with keys as items in a and values as items in b
|
|
151
|
+
*/
|
|
152
|
+
export function repeatAcross(b, a) {
|
|
153
|
+
return a.reduce(
|
|
154
|
+
(acc, item, index) => ({ ...acc, [item]: b[index % b.length] }),
|
|
155
|
+
{}
|
|
156
|
+
)
|
|
157
|
+
}
|
package/src/lookup.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { writable } from 'svelte/store'
|
|
2
|
+
|
|
3
|
+
// import { __patterns__, __colors__, __shapes__ } from './constants'
|
|
4
|
+
|
|
5
|
+
export const swatchStore = writable({})
|
|
6
|
+
|
|
7
|
+
// export function swatch(colors, patterns, shapes, defaults) {
|
|
8
|
+
// const limit = min([colors.length, patterns.length, shapes.length])
|
|
9
|
+
|
|
10
|
+
// swatchStore.set({
|
|
11
|
+
// colors: colors.slice(0, limit),
|
|
12
|
+
// patterns: patterns.slice(0, limit),
|
|
13
|
+
// shapes: shapes.slice(0, limit),
|
|
14
|
+
// defaults: {
|
|
15
|
+
// color: '#eeeeee',
|
|
16
|
+
// shape: __shapes__.circle,
|
|
17
|
+
// pattern: __patterns__.empty,
|
|
18
|
+
// ...defaults
|
|
19
|
+
// }
|
|
20
|
+
// })
|
|
21
|
+
// }
|
|
22
|
+
|
|
23
|
+
export function spread(values, across, filler) {
|
|
24
|
+
const unique = [...new Set(values)]
|
|
25
|
+
const lookup = unique.map((k, i) => ({
|
|
26
|
+
[k]: i < across.length ? across[i] : filler
|
|
27
|
+
}))
|
|
28
|
+
return (k) => lookup[k]
|
|
29
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { colorBrewer } from '../lib/colors'
|
|
4
|
+
|
|
5
|
+
let chart = getContext('chart')
|
|
6
|
+
|
|
7
|
+
export let labels = false
|
|
8
|
+
export let fontSize = $chart.height / 32
|
|
9
|
+
export let color = 'white'
|
|
10
|
+
|
|
11
|
+
$: fills = colorBrewer($chart.data.map((d) => d.fill))
|
|
12
|
+
// export let limit = 8
|
|
13
|
+
|
|
14
|
+
$: data = $chart.data.map((d) => ({
|
|
15
|
+
x: $chart.flipCoords ? $chart.scale.x(0) : $chart.scale.x(d.x),
|
|
16
|
+
y: $chart.flipCoords ? $chart.scale.y(d.y) : $chart.scale.y(d.y),
|
|
17
|
+
y0: $chart.scale.y(0),
|
|
18
|
+
width: $chart.flipCoords
|
|
19
|
+
? $chart.scale.x(d.x) - $chart.scale.x(0)
|
|
20
|
+
: $chart.scale.x.bandwidth(),
|
|
21
|
+
height: $chart.flipCoords
|
|
22
|
+
? $chart.scale.y.bandwidth()
|
|
23
|
+
: $chart.scale.y(0) - $chart.scale.y(d.y),
|
|
24
|
+
fill: fills[d.fill],
|
|
25
|
+
label: {
|
|
26
|
+
x: $chart.flipCoords
|
|
27
|
+
? $chart.scale.x(d.x) - $chart.scale.x(0) - 10
|
|
28
|
+
: $chart.scale.x.bandwidth() / 2,
|
|
29
|
+
y: $chart.flipCoords ? $chart.scale.y.bandwidth() / 2 : 10,
|
|
30
|
+
angle: $chart.flipCoords ? 0 : -90,
|
|
31
|
+
text: $chart.flipCoords ? d.y + ' (' + d.x + ')' : d.x + ' (' + d.y + ')'
|
|
32
|
+
}
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
// $: console.log(data)
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#each data as { x, y, width, height, fill, label }}
|
|
39
|
+
<rect {x} {y} {width} {height} {fill} />
|
|
40
|
+
{#if labels}
|
|
41
|
+
{@const tx = x + label.x}
|
|
42
|
+
{@const ty = y + label.y}
|
|
43
|
+
<text
|
|
44
|
+
x={tx}
|
|
45
|
+
y={ty}
|
|
46
|
+
transform="rotate({label.angle},{tx},{ty})"
|
|
47
|
+
font-size={fontSize}
|
|
48
|
+
text-anchor="end"
|
|
49
|
+
alignment-baseline="middle"
|
|
50
|
+
fill={color}
|
|
51
|
+
>
|
|
52
|
+
{label.text}
|
|
53
|
+
</text>
|
|
54
|
+
{/if}
|
|
55
|
+
{/each}
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { compact } from '../components/lib/utils'
|
|
4
|
+
import { funnel } from '../components/lib/funnel'
|
|
5
|
+
|
|
6
|
+
const chart = getContext('chart')
|
|
7
|
+
|
|
8
|
+
export let x
|
|
9
|
+
export let y
|
|
10
|
+
export let fill
|
|
11
|
+
export let curve = 'bump'
|
|
12
|
+
export let stat = 'count'
|
|
13
|
+
|
|
14
|
+
$: aes = { ...$chart.aes, ...compact({ x, y, fill, stat, curve }) }
|
|
15
|
+
$: data = funnel($chart.data, aes, $chart.width, $chart.height)
|
|
16
|
+
// $: console.log(data)
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#each data.stats as stat, i}
|
|
20
|
+
<path d={data.path(stat.value)} fill={$chart.theme.colors[i]} />
|
|
21
|
+
{/each}
|
|
22
|
+
{#each data.labels as label, index}
|
|
23
|
+
{#if index < data.labels.length - 1}
|
|
24
|
+
<line
|
|
25
|
+
x1={label.x1}
|
|
26
|
+
x2={label.x2}
|
|
27
|
+
y1={label.y1}
|
|
28
|
+
y2={label.y2}
|
|
29
|
+
stroke="currentColor"
|
|
30
|
+
/>
|
|
31
|
+
{/if}
|
|
32
|
+
<text x={label.x} y={label.y} fill="currentColor">{label.label}</text>
|
|
33
|
+
{/each}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { summarize, heatmap } from './heatmap'
|
|
3
|
+
import { interpolateHsl } from 'd3-interpolate'
|
|
4
|
+
import { scaleLinear } from 'd3-scale'
|
|
5
|
+
import ContinuousLegend from '../elements/ContinuousLegend.svelte'
|
|
6
|
+
import DiscreteLegend from '../elements/DiscreteLegend.svelte'
|
|
7
|
+
import ColorRamp from '../elements/ColorRamp.svelte'
|
|
8
|
+
|
|
9
|
+
const dayLabelWidth = 20
|
|
10
|
+
const labelHeight = 6
|
|
11
|
+
|
|
12
|
+
export let data
|
|
13
|
+
export let dateField = 'date'
|
|
14
|
+
export let valueField = null
|
|
15
|
+
export let months = 12
|
|
16
|
+
export let colors = [transparent, '#FB8C00']
|
|
17
|
+
export let padding = 8
|
|
18
|
+
export let space = 2
|
|
19
|
+
export let size = 10
|
|
20
|
+
export let maximum = 10
|
|
21
|
+
export let tickCount = 5
|
|
22
|
+
export let discreteLegend = false
|
|
23
|
+
|
|
24
|
+
export let tooltipText = (d) => `${d.date} => ${d.value}`
|
|
25
|
+
|
|
26
|
+
let tooltip = null
|
|
27
|
+
|
|
28
|
+
function showToolTip(event, d) {
|
|
29
|
+
tooltip = d
|
|
30
|
+
}
|
|
31
|
+
function hideToolTip() {
|
|
32
|
+
tooltip = null
|
|
33
|
+
}
|
|
34
|
+
$: scale = scaleLinear()
|
|
35
|
+
.domain([0, maximum])
|
|
36
|
+
.range(colors)
|
|
37
|
+
.interpolate(interpolateHsl)
|
|
38
|
+
|
|
39
|
+
$: legendHeight = 2 * size + space * 3
|
|
40
|
+
$: sizeWithSpace = size + space
|
|
41
|
+
$: summary = summarize(data, dateField, valueField)
|
|
42
|
+
$: datamap = heatmap(summary, months)
|
|
43
|
+
$: width =
|
|
44
|
+
datamap.numberOfWeeks * sizeWithSpace + dayLabelWidth + 2 * padding - space
|
|
45
|
+
$: height = 7 * sizeWithSpace + labelHeight + 2 * padding + legendHeight
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<div>
|
|
49
|
+
<svg class="chart" viewBox="0 0 {width} {height}">
|
|
50
|
+
{#if tooltip}
|
|
51
|
+
<text
|
|
52
|
+
x={padding + dayLabelWidth}
|
|
53
|
+
y={padding + legendHeight - labelHeight}
|
|
54
|
+
text-anchor="start"
|
|
55
|
+
font-size={labelHeight}
|
|
56
|
+
>
|
|
57
|
+
{tooltipText(tooltip)}
|
|
58
|
+
</text>
|
|
59
|
+
{/if}
|
|
60
|
+
{#if discreteLegend}
|
|
61
|
+
<DiscreteLegend
|
|
62
|
+
{scale}
|
|
63
|
+
x={width - (tickCount + 1) * sizeWithSpace - padding - space}
|
|
64
|
+
y={padding}
|
|
65
|
+
{tickCount}
|
|
66
|
+
/>
|
|
67
|
+
{:else}
|
|
68
|
+
<ColorRamp {scale} x={width - 100 - padding} y={padding} {tickCount} />
|
|
69
|
+
<!-- <ContinuousLegend
|
|
70
|
+
{scale}
|
|
71
|
+
x={width - 100 - padding}
|
|
72
|
+
y={padding}
|
|
73
|
+
{tickCount}
|
|
74
|
+
/> -->
|
|
75
|
+
{/if}
|
|
76
|
+
{#each datamap.weekdays as name, i}
|
|
77
|
+
<text
|
|
78
|
+
x={padding + dayLabelWidth - 2 * space}
|
|
79
|
+
y={padding +
|
|
80
|
+
legendHeight +
|
|
81
|
+
i * sizeWithSpace +
|
|
82
|
+
labelHeight +
|
|
83
|
+
(size - labelHeight) / 2}
|
|
84
|
+
text-anchor="end"
|
|
85
|
+
font-size={labelHeight}>{name}</text
|
|
86
|
+
>
|
|
87
|
+
{/each}
|
|
88
|
+
|
|
89
|
+
{#each datamap.grid as d}
|
|
90
|
+
<rect
|
|
91
|
+
x={d.x * sizeWithSpace + padding + dayLabelWidth}
|
|
92
|
+
y={d.y * sizeWithSpace + padding + legendHeight}
|
|
93
|
+
width={size}
|
|
94
|
+
height={size}
|
|
95
|
+
fill={scale(d.value)}
|
|
96
|
+
rx="1"
|
|
97
|
+
ry="1"
|
|
98
|
+
on:mouseover={(e) => showToolTip(e, d)}
|
|
99
|
+
on:focus={(e) => showToolTip(e, d)}
|
|
100
|
+
on:blur={hideToolTip}
|
|
101
|
+
on:mouseout={hideToolTip}
|
|
102
|
+
/>
|
|
103
|
+
{/each}
|
|
104
|
+
|
|
105
|
+
{#each Object.keys(datamap.months) as name}
|
|
106
|
+
<text
|
|
107
|
+
x={padding + dayLabelWidth + datamap.months[name] * sizeWithSpace}
|
|
108
|
+
y={padding + legendHeight + 7 * sizeWithSpace + 3 * space}
|
|
109
|
+
text-anchor="start"
|
|
110
|
+
font-size={labelHeight}
|
|
111
|
+
fill="currentColor">{name}</text
|
|
112
|
+
>
|
|
113
|
+
{/each}
|
|
114
|
+
</svg>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<style>
|
|
118
|
+
div {
|
|
119
|
+
position: relative;
|
|
120
|
+
}
|
|
121
|
+
rect {
|
|
122
|
+
stroke: currentColor;
|
|
123
|
+
stroke-width: 0.5;
|
|
124
|
+
stroke-opacity: 0.1;
|
|
125
|
+
}
|
|
126
|
+
text {
|
|
127
|
+
fill: currentColor;
|
|
128
|
+
}
|
|
129
|
+
</style>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { colorBrewer } from '../lib/colors'
|
|
4
|
+
|
|
5
|
+
let chart = getContext('chart')
|
|
6
|
+
|
|
7
|
+
export let labels = false
|
|
8
|
+
export let fontSize = $chart.height / 32
|
|
9
|
+
// export let color = 'white'
|
|
10
|
+
|
|
11
|
+
$: colors = colorBrewer($chart.data.map((d) => d.color))
|
|
12
|
+
// export let limit = 8
|
|
13
|
+
|
|
14
|
+
$: data = $chart.data.map((d) => ({
|
|
15
|
+
x: $chart.flipCoords ? $chart.scale.x(0) : $chart.scale.x(d.x),
|
|
16
|
+
y: $chart.flipCoords ? $chart.scale.y(d.y) : $chart.scale.y(d.y),
|
|
17
|
+
y0: $chart.scale.y(0),
|
|
18
|
+
width: $chart.flipCoords
|
|
19
|
+
? $chart.scale.x(d.x) - $chart.scale.x(0)
|
|
20
|
+
: $chart.scale.x.bandwidth(),
|
|
21
|
+
height: $chart.flipCoords
|
|
22
|
+
? $chart.scale.y.bandwidth()
|
|
23
|
+
: $chart.scale.y(0) - $chart.scale.y(d.y),
|
|
24
|
+
color: colors[d.color],
|
|
25
|
+
label: {
|
|
26
|
+
x: $chart.flipCoords
|
|
27
|
+
? $chart.scale.x(d.x) - $chart.scale.x(0) - 10
|
|
28
|
+
: $chart.scale.x.bandwidth() / 2,
|
|
29
|
+
y: $chart.flipCoords ? $chart.scale.y.bandwidth() / 2 : 10,
|
|
30
|
+
angle: $chart.flipCoords ? 0 : -90,
|
|
31
|
+
text: $chart.flipCoords ? d.y + ' (' + d.x + ')' : d.x + ' (' + d.y + ')'
|
|
32
|
+
}
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
// $: console.log(data)
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#each data as { x1, y1, x2, y2, color, label }}
|
|
39
|
+
<line {x1} {y1} {x2} {y2} stroke={color} />
|
|
40
|
+
{#if labels}
|
|
41
|
+
{@const tx = x1 + label.x}
|
|
42
|
+
{@const ty = y1 + label.y}
|
|
43
|
+
<text
|
|
44
|
+
x={tx}
|
|
45
|
+
y={ty}
|
|
46
|
+
transform="rotate({label.angle},{tx},{ty})"
|
|
47
|
+
font-size={fontSize}
|
|
48
|
+
text-anchor="end"
|
|
49
|
+
alignment-baseline="middle"
|
|
50
|
+
fill={color}
|
|
51
|
+
>
|
|
52
|
+
{label.text}
|
|
53
|
+
</text>
|
|
54
|
+
{/if}
|
|
55
|
+
{/each}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { aggregate, getScales } from '../lib/utils'
|
|
3
|
+
|
|
4
|
+
import BoxPlot from './BoxPlot.svelte'
|
|
5
|
+
import ScatterPlot from './ScatterPlot.svelte'
|
|
6
|
+
|
|
7
|
+
export let data
|
|
8
|
+
export let width
|
|
9
|
+
export let height
|
|
10
|
+
export let x
|
|
11
|
+
export let y
|
|
12
|
+
export let plots = []
|
|
13
|
+
|
|
14
|
+
$: nested = aggregate(data, x, y)
|
|
15
|
+
$: scales = getScales(data, x, y, width, height)
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<svg viewBox="0 0 {width} {height}">
|
|
19
|
+
{#if plots.includes('box')}
|
|
20
|
+
<BoxPlot data={nested} {...scales} />
|
|
21
|
+
{/if}
|
|
22
|
+
{#if plots.includes('scatter')}
|
|
23
|
+
<ScatterPlot {data} {x} {y} {...scales} />
|
|
24
|
+
{/if}
|
|
25
|
+
</svg>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Bar from '../elements/Bar.svelte'
|
|
3
|
+
import { max } from 'd3-array'
|
|
4
|
+
import { writable } from 'svelte/store'
|
|
5
|
+
import { tweened } from 'svelte/motion'
|
|
6
|
+
import { scaleLinear } from 'd3-scale'
|
|
7
|
+
|
|
8
|
+
export let data
|
|
9
|
+
export let colors
|
|
10
|
+
export let width = 800
|
|
11
|
+
export let limit = 8
|
|
12
|
+
export let duration = 300
|
|
13
|
+
const h = 50
|
|
14
|
+
let height
|
|
15
|
+
const pad = 5
|
|
16
|
+
|
|
17
|
+
const xMax = tweened(null, { duration })
|
|
18
|
+
const scales = writable({})
|
|
19
|
+
|
|
20
|
+
$: xMax.set(max(data.map((d) => d.value)))
|
|
21
|
+
$: height = pad + (h + pad) * limit
|
|
22
|
+
$: scales.set({
|
|
23
|
+
x: scaleLinear()
|
|
24
|
+
.domain([0, $xMax])
|
|
25
|
+
.range([10, width - 10]),
|
|
26
|
+
y: scaleLinear().domain([0, limit]).range([0, height])
|
|
27
|
+
})
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<svg viewBox="0 0 {width} {height}">
|
|
31
|
+
{#if Array.isArray(data)}
|
|
32
|
+
{#each data as item, i}
|
|
33
|
+
{#if item.rank < limit}
|
|
34
|
+
<Bar {...item} fill={colors[item.name]} {scales} />
|
|
35
|
+
{/if}
|
|
36
|
+
{/each}
|
|
37
|
+
{/if}
|
|
38
|
+
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { colorBrewer } from '../lib/colors'
|
|
4
|
+
|
|
5
|
+
let chart = getContext('chart')
|
|
6
|
+
|
|
7
|
+
export let size = $chart.height / 128
|
|
8
|
+
|
|
9
|
+
$: colors = colorBrewer($chart.data.map((d) => d.fill))
|
|
10
|
+
$: points = $chart.data.map((d) => ({
|
|
11
|
+
cx: $chart.scale.x(d.x),
|
|
12
|
+
cy: $chart.scale.y(d.y),
|
|
13
|
+
fill: colors[d.fill]
|
|
14
|
+
}))
|
|
15
|
+
// support shapes and sizes for scatter
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
{#each points as { cx, cy, fill }}
|
|
19
|
+
<circle {cx} {cy} r={size} {fill} fill-opacity="0.5" />
|
|
20
|
+
{/each}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
|
|
4
|
+
let chart = getContext('chart')
|
|
5
|
+
$: data = $chart.violin()
|
|
6
|
+
// $: console.log(data)
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
{#each data as { x, curve }, i}
|
|
10
|
+
<path d={curve} transform="translate({x},0)" fill="gray" stroke="none" />
|
|
11
|
+
{/each}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
|
24
|
+
? d.map((value) => value[valueField]).reduce((a, b) => a + b, 0)
|
|
25
|
+
: d.length
|
|
26
|
+
)
|
|
27
|
+
.entries(data)
|
|
28
|
+
// console.log(Object.fromEntries(nested))
|
|
29
|
+
return nested.reduce((obj, item) => ((obj[item.key] = item.value), obj), {})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function heatmap(data, numberOfMonths) {
|
|
33
|
+
const today = new Date()
|
|
34
|
+
const firstDay = getFirstDay(numberOfMonths, today)
|
|
35
|
+
const months = {}
|
|
36
|
+
const grid = generateGrid(firstDay, today).map((d) => ({
|
|
37
|
+
...d,
|
|
38
|
+
value: d.date in data ? data[d.date] : 0
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
grid.map((d) => {
|
|
42
|
+
const month = format(endOfWeek(new Date(d.date)), 'MMM')
|
|
43
|
+
// console.log(month)
|
|
44
|
+
if (!(month in months)) {
|
|
45
|
+
months[month] = d.x
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
return {
|
|
49
|
+
grid,
|
|
50
|
+
months,
|
|
51
|
+
weekdays,
|
|
52
|
+
numberOfWeeks: differenceInWeeks(today, firstDay) + 1
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getFirstDay(months, lastDay = new Date()) {
|
|
57
|
+
const firstDay = subMonths(startOfMonth(lastDay), months)
|
|
58
|
+
return isSunday(firstDay) ? firstDay : previousSunday(firstDay)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function generateGrid(firstDay, lastDay) {
|
|
62
|
+
const days = differenceInDays(lastDay, firstDay) + 1
|
|
63
|
+
|
|
64
|
+
const grid = [...Array(days).keys()].map((day) => ({
|
|
65
|
+
x: Math.floor(day / 7),
|
|
66
|
+
y: day % 7,
|
|
67
|
+
date: format(addDays(firstDay, day), DATE_FORMAT)
|
|
68
|
+
}))
|
|
69
|
+
return grid
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as Box } from './BoxPlot.svelte'
|
|
2
|
+
export { default as BoxPlot } from './BoxPlot.svelte'
|
|
3
|
+
export { default as Bar } from './BarPlot.svelte'
|
|
4
|
+
export { default as BarPlot } from './BarPlot.svelte'
|
|
5
|
+
export { default as Line } from './LinePlot.svelte'
|
|
6
|
+
export { default as LinePlot } from './LinePlot.svelte'
|
|
7
|
+
export { default as Violin } from './ViolinPlot.svelte'
|
|
8
|
+
export { default as ViolinPlot } from './ViolinPlot.svelte'
|
|
9
|
+
export { default as Scatter } from './ScatterPlot.svelte'
|
|
10
|
+
export { default as ScatterPlot } from './ScatterPlot.svelte'
|