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