@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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/package.json +63 -0
  4. package/src/chart/FacetGrid.svelte +51 -0
  5. package/src/chart/Grid.svelte +34 -0
  6. package/src/chart/Legend.svelte +16 -0
  7. package/src/chart/PatternDefs.svelte +13 -0
  8. package/src/chart/Swatch.svelte +93 -0
  9. package/src/chart/SwatchButton.svelte +29 -0
  10. package/src/chart/SwatchGrid.svelte +55 -0
  11. package/src/chart/Symbol.svelte +37 -0
  12. package/src/chart/Texture.svelte +16 -0
  13. package/src/chart/TexturedShape.svelte +27 -0
  14. package/src/chart/TimelapseChart.svelte +97 -0
  15. package/src/chart/Timer.svelte +27 -0
  16. package/src/chart.js +9 -0
  17. package/src/components/charts/Axis.svelte +66 -0
  18. package/src/components/charts/Chart.svelte +35 -0
  19. package/src/components/index.js +23 -0
  20. package/src/components/lib/axis.js +0 -0
  21. package/src/components/lib/chart.js +187 -0
  22. package/src/components/lib/color.js +327 -0
  23. package/src/components/lib/funnel.js +204 -0
  24. package/src/components/lib/index.js +19 -0
  25. package/src/components/lib/pattern.js +190 -0
  26. package/src/components/lib/rollup.js +55 -0
  27. package/src/components/lib/shape.js +199 -0
  28. package/src/components/lib/summary.js +145 -0
  29. package/src/components/lib/theme.js +23 -0
  30. package/src/components/lib/timer.js +41 -0
  31. package/src/components/lib/utils.js +165 -0
  32. package/src/components/plots/BarPlot.svelte +36 -0
  33. package/src/components/plots/BoxPlot.svelte +54 -0
  34. package/src/components/plots/ScatterPlot.svelte +30 -0
  35. package/src/components/store.js +70 -0
  36. package/src/constants.js +66 -0
  37. package/src/elements/Bar.svelte +35 -0
  38. package/src/elements/ColorRamp.svelte +51 -0
  39. package/src/elements/ContinuousLegend.svelte +46 -0
  40. package/src/elements/DiscreteLegend.svelte +41 -0
  41. package/src/elements/Label.svelte +12 -0
  42. package/src/elements/PatternDefs.svelte +13 -0
  43. package/src/elements/PatternMask.svelte +20 -0
  44. package/src/elements/Symbol.svelte +38 -0
  45. package/src/elements/Tooltip.svelte +23 -0
  46. package/src/funnel.svelte +35 -0
  47. package/src/geom.js +105 -0
  48. package/src/index.js +16 -0
  49. package/src/lib/axis.js +75 -0
  50. package/src/lib/colors.js +32 -0
  51. package/src/lib/geom.js +4 -0
  52. package/src/lib/shapes.js +144 -0
  53. package/src/lib/timer.js +44 -0
  54. package/src/lib/utils.js +157 -0
  55. package/src/lookup.js +29 -0
  56. package/src/plots/BarPlot.svelte +55 -0
  57. package/src/plots/BoxPlot.svelte +0 -0
  58. package/src/plots/FunnelPlot.svelte +33 -0
  59. package/src/plots/HeatMap.svelte +5 -0
  60. package/src/plots/HeatMapCalendar.svelte +129 -0
  61. package/src/plots/LinePlot.svelte +55 -0
  62. package/src/plots/Plot.svelte +25 -0
  63. package/src/plots/RankBarPlot.svelte +38 -0
  64. package/src/plots/ScatterPlot.svelte +20 -0
  65. package/src/plots/ViolinPlot.svelte +11 -0
  66. package/src/plots/heatmap.js +70 -0
  67. package/src/plots/index.js +10 -0
  68. package/src/swatch.js +11 -0
@@ -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 }
@@ -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,5 @@
1
+ <script>
2
+ let y // axis (day of week)
3
+ let x // week Number (week starts sunday)
4
+ let weeks //weeks
5
+ </script>
@@ -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'