svelteplot 0.0.1-alpha.1 → 0.0.1-alpha.3
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/dist/Plot.svelte +171 -0
- package/dist/Plot.svelte.d.ts +15 -0
- package/dist/classes/Channel.svelte.js +72 -0
- package/dist/classes/Mark.svelte.js +17 -0
- package/dist/classes/Plot.svelte.js +99 -0
- package/dist/contants.d.ts +3 -0
- package/dist/contants.js +40 -0
- package/dist/helpers/GroupMultiple.svelte +8 -0
- package/dist/helpers/GroupMultiple.svelte.d.ts +19 -0
- package/dist/helpers/autoTimeFormat.d.ts +2 -0
- package/dist/helpers/autoTimeFormat.js +10 -0
- package/dist/helpers/colors.d.ts +13 -0
- package/dist/helpers/colors.js +200 -0
- package/dist/helpers/createScale.d.ts +4 -0
- package/dist/helpers/createScale.js +47 -0
- package/dist/helpers/getBaseStyles.d.ts +2 -0
- package/dist/helpers/getBaseStyles.js +18 -0
- package/dist/helpers/getLogTicks.d.ts +1 -0
- package/dist/helpers/getLogTicks.js +57 -0
- package/dist/helpers/isDataRecord.d.ts +2 -0
- package/dist/helpers/isDataRecord.js +13 -0
- package/dist/helpers/mergeDeep.d.ts +5 -0
- package/dist/helpers/mergeDeep.js +26 -0
- package/dist/helpers/removeIdenticalLines.d.ts +1 -0
- package/dist/helpers/removeIdenticalLines.js +16 -0
- package/dist/helpers/resolveChannel.d.ts +2 -0
- package/dist/helpers/resolveChannel.js +28 -0
- package/dist/helpers/symbols.d.ts +5 -0
- package/dist/helpers/symbols.js +51 -0
- package/dist/helpers/typeChecks.d.ts +7 -0
- package/dist/helpers/typeChecks.js +21 -0
- package/dist/helpers/wrapArray.d.ts +2 -0
- package/dist/helpers/wrapArray.js +4 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +13 -0
- package/dist/marks/AxisX.svelte +101 -0
- package/dist/marks/AxisX.svelte.d.ts +17 -0
- package/dist/marks/AxisY.svelte +69 -0
- package/dist/marks/AxisY.svelte.d.ts +15 -0
- package/dist/marks/BaseMark.svelte +22 -0
- package/dist/marks/BaseMark.svelte.d.ts +19 -0
- package/dist/marks/ColorLegend.svelte +52 -0
- package/dist/marks/ColorLegend.svelte.d.ts +14 -0
- package/dist/marks/Dot.svelte +83 -0
- package/dist/marks/Dot.svelte.d.ts +15 -0
- package/dist/marks/DotX.svelte +5 -0
- package/dist/marks/DotX.svelte.d.ts +17 -0
- package/dist/marks/DotY.svelte +5 -0
- package/dist/marks/DotY.svelte.d.ts +17 -0
- package/dist/marks/Frame.svelte +37 -0
- package/dist/marks/Frame.svelte.d.ts +15 -0
- package/dist/marks/GridX.svelte +42 -0
- package/dist/marks/GridX.svelte.d.ts +19 -0
- package/dist/marks/GridY.svelte +31 -0
- package/dist/marks/GridY.svelte.d.ts +15 -0
- package/dist/marks/Line.svelte +49 -0
- package/dist/marks/Line.svelte.d.ts +15 -0
- package/dist/marks/LineX.svelte +10 -0
- package/dist/marks/LineX.svelte.d.ts +17 -0
- package/dist/marks/LineY.svelte +10 -0
- package/dist/marks/LineY.svelte.d.ts +17 -0
- package/dist/marks/RuleX.svelte +30 -0
- package/dist/marks/RuleX.svelte.d.ts +15 -0
- package/dist/marks/RuleY.svelte +31 -0
- package/dist/marks/RuleY.svelte.d.ts +15 -0
- package/dist/marks/SymbolLegend.svelte +50 -0
- package/dist/marks/SymbolLegend.svelte.d.ts +14 -0
- package/dist/types.d.ts +188 -0
- package/dist/types.js +1 -0
- package/package.json +4 -2
package/dist/Plot.svelte
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script>import { setContext } from "svelte";
|
|
2
|
+
import { Frame, GridX, GridY } from "./";
|
|
3
|
+
import { DEFAULT_PLOT_OPTIONS, Plot } from "./classes/Plot.svelte";
|
|
4
|
+
import mergeDeep from "./helpers/mergeDeep";
|
|
5
|
+
import AxisX from "./marks/AxisX.svelte";
|
|
6
|
+
import AxisY from "./marks/AxisY.svelte";
|
|
7
|
+
import ColorLegend from "./marks/ColorLegend.svelte";
|
|
8
|
+
import SymbolLegend from "./marks/SymbolLegend.svelte";
|
|
9
|
+
let {
|
|
10
|
+
// snippets
|
|
11
|
+
header,
|
|
12
|
+
footer,
|
|
13
|
+
overlay,
|
|
14
|
+
children,
|
|
15
|
+
// props
|
|
16
|
+
height = "auto",
|
|
17
|
+
marginLeft = 30,
|
|
18
|
+
marginRight = 10,
|
|
19
|
+
marginTop = 20,
|
|
20
|
+
marginBottom = 40,
|
|
21
|
+
inset = null,
|
|
22
|
+
grid = false,
|
|
23
|
+
frame = false,
|
|
24
|
+
maxWidth = null,
|
|
25
|
+
title = "",
|
|
26
|
+
subtitle = "",
|
|
27
|
+
caption = "",
|
|
28
|
+
// scales
|
|
29
|
+
radius = null,
|
|
30
|
+
color = null,
|
|
31
|
+
symbol = null,
|
|
32
|
+
x = null,
|
|
33
|
+
y = null,
|
|
34
|
+
onmousemove = null
|
|
35
|
+
} = $props();
|
|
36
|
+
let width = $state(400);
|
|
37
|
+
const plot = new Plot(600, height || defaultPlotHeight, {
|
|
38
|
+
marginTop,
|
|
39
|
+
marginLeft,
|
|
40
|
+
marginRight,
|
|
41
|
+
marginBottom,
|
|
42
|
+
// scales
|
|
43
|
+
symbol,
|
|
44
|
+
radius,
|
|
45
|
+
x,
|
|
46
|
+
y,
|
|
47
|
+
color,
|
|
48
|
+
// other
|
|
49
|
+
title,
|
|
50
|
+
subtitle,
|
|
51
|
+
caption
|
|
52
|
+
});
|
|
53
|
+
setContext("svelteplot", plot);
|
|
54
|
+
$effect(() => {
|
|
55
|
+
plot.width = width;
|
|
56
|
+
plot._height = height;
|
|
57
|
+
plot.options = mergeDeep({}, DEFAULT_PLOT_OPTIONS, {
|
|
58
|
+
marginBottom,
|
|
59
|
+
marginLeft,
|
|
60
|
+
marginRight,
|
|
61
|
+
marginTop,
|
|
62
|
+
inset,
|
|
63
|
+
symbol,
|
|
64
|
+
radius,
|
|
65
|
+
color,
|
|
66
|
+
x,
|
|
67
|
+
y,
|
|
68
|
+
title,
|
|
69
|
+
subtitle,
|
|
70
|
+
caption
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
function onMouseMove(evt) {
|
|
74
|
+
if (onmousemove)
|
|
75
|
+
onmousemove({ ...evt, plot });
|
|
76
|
+
}
|
|
77
|
+
let hasLegend = $derived(color?.legend || symbol?.legend);
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<figure class="svelteplot" bind:clientWidth={width} style:max-width={maxWidth}>
|
|
81
|
+
<svg
|
|
82
|
+
role="document"
|
|
83
|
+
{width}
|
|
84
|
+
height={plot.height}
|
|
85
|
+
onmousemove={onmousemove ? onMouseMove : null}
|
|
86
|
+
>
|
|
87
|
+
<!-- automatic grids -->
|
|
88
|
+
{#if grid || x?.grid}<GridX automatic />{/if}
|
|
89
|
+
{#if grid || y?.grid}<GridY automatic />{/if}
|
|
90
|
+
|
|
91
|
+
{#if !plot.hasAxisXMark && plot.hasChannelX}
|
|
92
|
+
<!-- automatic x axis -->
|
|
93
|
+
{#if plot.options.x.axis === 'bottom' || plot.options.x.axis === 'both'}
|
|
94
|
+
<AxisX anchor="bottom" automatic />
|
|
95
|
+
{/if}
|
|
96
|
+
{#if plot.options.x.axis === 'top' || plot.options.x.axis === 'both'}
|
|
97
|
+
<AxisX anchor="top" automatic />
|
|
98
|
+
{/if}
|
|
99
|
+
{/if}
|
|
100
|
+
{#if !plot.hasAxisYMark && plot.hasChannelY}
|
|
101
|
+
<!-- automatic y axis -->
|
|
102
|
+
{#if plot.options.y.axis === 'left' || plot.options.y.axis === 'both'}
|
|
103
|
+
<AxisY anchor="left" automatic />
|
|
104
|
+
{/if}
|
|
105
|
+
{#if plot.options.y.axis === 'right' || plot.options.y.axis === 'both'}
|
|
106
|
+
<AxisY anchor="right" automatic />
|
|
107
|
+
{/if}
|
|
108
|
+
{/if}
|
|
109
|
+
<!-- automatic frame -->
|
|
110
|
+
{#if frame}<Frame />{/if}
|
|
111
|
+
{#if children}{@render children(plot)}{/if}
|
|
112
|
+
</svg>
|
|
113
|
+
|
|
114
|
+
{#if plot.options.title || plot.options.subtitle || header || hasLegend}
|
|
115
|
+
<div class="plot-header">
|
|
116
|
+
{#if plot.options.title}
|
|
117
|
+
<h2>{@html plot.options.title}</h2>
|
|
118
|
+
{/if}
|
|
119
|
+
{#if plot.options.subtitle}
|
|
120
|
+
<h3>{@html plot.options.subtitle}</h3>
|
|
121
|
+
{/if}
|
|
122
|
+
{#if color?.legend}
|
|
123
|
+
<ColorLegend />
|
|
124
|
+
{/if}
|
|
125
|
+
{#if symbol?.legend}
|
|
126
|
+
<SymbolLegend />
|
|
127
|
+
{/if}
|
|
128
|
+
{#if header}{@render header(plot)}{/if}
|
|
129
|
+
</div>
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
{#if footer || plot.options.caption}
|
|
133
|
+
<div class="plot-footer">
|
|
134
|
+
{#if plot.options.caption}
|
|
135
|
+
<figcaption>{@html plot.options.caption}</figcaption>
|
|
136
|
+
{/if}
|
|
137
|
+
{#if footer}{@render footer(plot)}{/if}
|
|
138
|
+
</div>
|
|
139
|
+
{/if}
|
|
140
|
+
|
|
141
|
+
<div class="overlay">
|
|
142
|
+
{#if overlay}{@render overlay(plot)}{/if}
|
|
143
|
+
</div>
|
|
144
|
+
</figure>
|
|
145
|
+
|
|
146
|
+
<style>
|
|
147
|
+
figure {
|
|
148
|
+
margin: 1em 0;
|
|
149
|
+
position: relative;
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
}
|
|
153
|
+
figure .plot-header {
|
|
154
|
+
order: 1;
|
|
155
|
+
}
|
|
156
|
+
svg {
|
|
157
|
+
order: 2;
|
|
158
|
+
overflow: visible;
|
|
159
|
+
}
|
|
160
|
+
figure .plot-footer {
|
|
161
|
+
order: 3;
|
|
162
|
+
}
|
|
163
|
+
.overlay {
|
|
164
|
+
position: absolute;
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
top: 0;
|
|
167
|
+
left: 0;
|
|
168
|
+
bottom: 0;
|
|
169
|
+
right: 0;
|
|
170
|
+
}
|
|
171
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { PlotProps } from './types';
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: PlotProps;
|
|
5
|
+
events: {
|
|
6
|
+
[evt: string]: CustomEvent<any>;
|
|
7
|
+
};
|
|
8
|
+
slots: {};
|
|
9
|
+
};
|
|
10
|
+
type PlotProps_ = typeof __propDef.props;
|
|
11
|
+
export { PlotProps_ as PlotProps };
|
|
12
|
+
export type PlotEvents = typeof __propDef.events;
|
|
13
|
+
export type PlotSlots = typeof __propDef.slots;
|
|
14
|
+
export default class Plot extends SvelteComponent<PlotProps, PlotEvents, PlotSlots> {
|
|
15
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import resolveChannel from '../helpers/resolveChannel';
|
|
2
|
+
import { extent } from 'd3-array';
|
|
3
|
+
import { MARK_PROP_CHANNEL, CHANNEL_TYPES } from '../contants';
|
|
4
|
+
import { isBooleanOrNull, isColorOrNull, isDateOrNull, isNumberOrNull, isStringOrNull } from '../helpers/typeChecks';
|
|
5
|
+
import { uniq } from 'underscore';
|
|
6
|
+
export class Channel {
|
|
7
|
+
name = undefined;
|
|
8
|
+
plot = undefined;
|
|
9
|
+
constructor(name, plot) {
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.plot = plot;
|
|
12
|
+
}
|
|
13
|
+
// readonly type: ChannelType = CHANNEL_TYPES.position;
|
|
14
|
+
// all marks that have this channel
|
|
15
|
+
marks = $derived(this.plot?.marks ?? []);
|
|
16
|
+
forceDomain = $derived(this.plot && (this.name === 'x' || this.name === 'y')
|
|
17
|
+
? this.plot.options[this.name]?.domain || null
|
|
18
|
+
: null);
|
|
19
|
+
possibleProps = $derived(Object.entries(MARK_PROP_CHANNEL)
|
|
20
|
+
.filter(([, channel]) => channel === this.name)
|
|
21
|
+
.map(([prop]) => prop));
|
|
22
|
+
activeMarks = $derived(this.marks.filter((mark) => mark.channels.has(this.name) && this.possibleProps.find((prop) => mark.props[prop])));
|
|
23
|
+
manualActiveMarks = $derived(this.activeMarks.filter((mark) => !mark.automatic));
|
|
24
|
+
autoTitle = $derived(this.manualActiveMarks.length === 1 &&
|
|
25
|
+
typeof this.manualActiveMarks[0].props?.[this.name] === 'string'
|
|
26
|
+
? this.manualActiveMarks[0].props?.[this.name]
|
|
27
|
+
: null);
|
|
28
|
+
uniqueMarkProps = $derived(uniq(this.manualActiveMarks
|
|
29
|
+
.map((mark) => this.possibleProps
|
|
30
|
+
.filter((prop) => mark.props[prop])
|
|
31
|
+
.map((prop) => mark.props[prop]))
|
|
32
|
+
.flat(2)));
|
|
33
|
+
dataValues = $derived([
|
|
34
|
+
...this.activeMarks
|
|
35
|
+
// only check marks with data
|
|
36
|
+
.filter((mark) => mark.props.data.length)
|
|
37
|
+
.map((mark) => this.possibleProps.map((prop) => mark.props.data.map((row) => resolveChannel(this.name, row, mark.props[prop]))))
|
|
38
|
+
.flat(3)
|
|
39
|
+
.filter((d) => d != null),
|
|
40
|
+
...(this.forceDomain || [])
|
|
41
|
+
]);
|
|
42
|
+
valueType = $derived(this.dataValues.every((v) => v == null)
|
|
43
|
+
? 'null'
|
|
44
|
+
: this.dataValues.every(isColorOrNull)
|
|
45
|
+
? 'color'
|
|
46
|
+
: this.dataValues.every(isBooleanOrNull)
|
|
47
|
+
? 'boolean'
|
|
48
|
+
: this.dataValues.every(isStringOrNull)
|
|
49
|
+
? 'text'
|
|
50
|
+
: this.dataValues.every(isNumberOrNull)
|
|
51
|
+
? 'number'
|
|
52
|
+
: this.dataValues.every(isDateOrNull)
|
|
53
|
+
? 'date'
|
|
54
|
+
: 'mixed');
|
|
55
|
+
domain = $derived(!this.dataValues.length
|
|
56
|
+
? [0, 1]
|
|
57
|
+
: this.valueType === 'boolean' ||
|
|
58
|
+
this.valueType === 'text' ||
|
|
59
|
+
this.valueType === 'color'
|
|
60
|
+
? uniq(this.dataValues)
|
|
61
|
+
: extent(this.dataValues));
|
|
62
|
+
scaleType = $derived(this.name === 'radius'
|
|
63
|
+
? 'sqrt'
|
|
64
|
+
: this.valueType === 'date'
|
|
65
|
+
? 'time'
|
|
66
|
+
: this.valueType === 'number'
|
|
67
|
+
? 'linear'
|
|
68
|
+
: this.valueType === 'text'
|
|
69
|
+
? 'band'
|
|
70
|
+
: 'linear');
|
|
71
|
+
}
|
|
72
|
+
// opacity: typeof === 'number' && between [0,1]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class Mark {
|
|
2
|
+
id;
|
|
3
|
+
type;
|
|
4
|
+
automatic;
|
|
5
|
+
channels = $state(new Set());
|
|
6
|
+
props = $state();
|
|
7
|
+
constructor(type, channels, automatic, props) {
|
|
8
|
+
this.id = Symbol();
|
|
9
|
+
this.type = type;
|
|
10
|
+
this.automatic = automatic;
|
|
11
|
+
this.channels = new Set(channels);
|
|
12
|
+
this.props = props;
|
|
13
|
+
}
|
|
14
|
+
toString() {
|
|
15
|
+
return `Mark[${this.type}]`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createScale, createColorScale } from '../helpers/createScale';
|
|
2
|
+
import mergeDeep from '../helpers/mergeDeep';
|
|
3
|
+
import { Channel } from './Channel.svelte';
|
|
4
|
+
import { uniq } from 'underscore';
|
|
5
|
+
export const DEFAULT_PLOT_OPTIONS = {
|
|
6
|
+
title: '',
|
|
7
|
+
subtitle: '',
|
|
8
|
+
caption: '',
|
|
9
|
+
marginLeft: 0,
|
|
10
|
+
marginRight: 0,
|
|
11
|
+
marginTop: 30,
|
|
12
|
+
marginBottom: 0,
|
|
13
|
+
radius: { range: [1, 10] },
|
|
14
|
+
symbol: {},
|
|
15
|
+
color: {},
|
|
16
|
+
x: {
|
|
17
|
+
domain: undefined,
|
|
18
|
+
grid: false,
|
|
19
|
+
ticks: undefined,
|
|
20
|
+
tickSpacing: 80,
|
|
21
|
+
axis: 'bottom',
|
|
22
|
+
log: false,
|
|
23
|
+
reverse: false
|
|
24
|
+
},
|
|
25
|
+
y: {
|
|
26
|
+
domain: undefined,
|
|
27
|
+
grid: false,
|
|
28
|
+
ticks: undefined,
|
|
29
|
+
tickSpacing: 60,
|
|
30
|
+
axis: 'left',
|
|
31
|
+
log: false,
|
|
32
|
+
reverse: false
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
export class Plot {
|
|
36
|
+
width = $state(600);
|
|
37
|
+
_height = $state(400);
|
|
38
|
+
options = $state(DEFAULT_PLOT_OPTIONS);
|
|
39
|
+
marks = $state([]);
|
|
40
|
+
hasChannelX = $derived(!!this.marks.find((mark) => mark.channels.has('x')));
|
|
41
|
+
hasChannelY = $derived(!!this.marks.find((mark) => mark.channels.has('y')));
|
|
42
|
+
hasFilledDotMarks = $derived(!!this.marks.find((d) => d.type === 'dot' && d.props?.fill));
|
|
43
|
+
manualMarks = $derived(this.marks.filter((mark) => !mark.automatic));
|
|
44
|
+
singlePosChannelMark = $derived(this.manualMarks.length === 1 &&
|
|
45
|
+
(!this.manualMarks[0].channels.has('x') || !this.manualMarks[0].channels.has('y')));
|
|
46
|
+
height = $derived(this._height === 'auto' ? (this.hasChannelY ? 400 : 90) : this._height);
|
|
47
|
+
inset = $derived(typeof this.options.inset === 'number'
|
|
48
|
+
? this.options.inset
|
|
49
|
+
: this.singlePosChannelMark
|
|
50
|
+
? 10
|
|
51
|
+
: 0);
|
|
52
|
+
// derived props
|
|
53
|
+
margins = $derived({
|
|
54
|
+
top: this.options.marginTop,
|
|
55
|
+
left: this.options.marginLeft,
|
|
56
|
+
bottom: this.options.marginBottom,
|
|
57
|
+
right: this.options.marginRight
|
|
58
|
+
});
|
|
59
|
+
// margins = $state<Margins>({ left: 0, right: 0, top: 0, bottom: 0 });
|
|
60
|
+
plotWidth = $derived(this.width - this.margins.left - this.margins.right);
|
|
61
|
+
plotHeight = $derived(this.height - this.margins.top - this.margins.bottom);
|
|
62
|
+
x = new Channel('x', this);
|
|
63
|
+
y = new Channel('y', this);
|
|
64
|
+
radius = new Channel('radius', this);
|
|
65
|
+
color = new Channel('color', this);
|
|
66
|
+
symbol = new Channel('symbol', this);
|
|
67
|
+
colorSymbolRedundant = $derived(this.color.uniqueMarkProps.length === 1 &&
|
|
68
|
+
this.symbol.uniqueMarkProps.length === 1 &&
|
|
69
|
+
this.color.uniqueMarkProps[0] === this.symbol.uniqueMarkProps[0]);
|
|
70
|
+
xScale = $derived(createScale(this.x.scaleType === 'linear' && this.options.x.log ? 'log' : this.x.scaleType, this.options?.x?.domain || this.x.domain, this.options?.x?.reverse
|
|
71
|
+
? [this.margins.left + this.plotWidth - this.inset, this.margins.left + this.inset]
|
|
72
|
+
: [this.margins.left + this.inset, this.margins.left + this.plotWidth - this.inset], this.x.scaleType === 'linear' && this.options.x.log ? { base: 10 } : {}));
|
|
73
|
+
yScale = $derived(createScale(this.y.scaleType === 'linear' && this.options.y.log ? 'log' : this.y.scaleType, this.options.y?.domain || this.y.domain, this.options.y?.reverse
|
|
74
|
+
? [this.margins.top + this.inset, this.height - this.margins.bottom - this.inset]
|
|
75
|
+
: [this.height - this.margins.bottom - this.inset, this.margins.top + this.inset], this.y.scaleType === 'linear' && this.options.y.log ? { base: '10' } : {}));
|
|
76
|
+
radiusScale = $derived(createScale(this.radius.scaleType, [0, Math.max(this.radius.domain[0], this.radius.domain[1])], this.options.radius.range));
|
|
77
|
+
symbolScale = $derived(createScale('ordinal', this.symbol.domain, this.options.symbol?.range || this.hasFilledDotMarks
|
|
78
|
+
? ['circle', 'cross', 'diamond', 'square', 'star', 'triangle', 'wye']
|
|
79
|
+
: ['circle', 'plus', 'times', 'triangle2', 'asterisk', 'square2', 'diamond2']));
|
|
80
|
+
colorScale = $derived(createColorScale(this.color.scaleType, this.color.domain, this.options.color.scheme));
|
|
81
|
+
hasAxisXMark = $derived(!!this.marks.find((mark) => mark.type === 'axis-x' && !mark.automatic));
|
|
82
|
+
hasAxisYMark = $derived(!!this.marks.find((mark) => mark.type === 'axis-y' && !mark.automatic));
|
|
83
|
+
constructor(width, height, options) {
|
|
84
|
+
const opts = mergeDeep({}, DEFAULT_PLOT_OPTIONS, options);
|
|
85
|
+
this.width = width;
|
|
86
|
+
this._height = height;
|
|
87
|
+
this.options = opts;
|
|
88
|
+
}
|
|
89
|
+
addMark(mark) {
|
|
90
|
+
// console.log('addMark: ' + mark);
|
|
91
|
+
this.marks = [...this.marks, mark];
|
|
92
|
+
// add mark to respective channels
|
|
93
|
+
if (mark.channels.has('color'))
|
|
94
|
+
console.log(this.color.uniqueMarkProps);
|
|
95
|
+
}
|
|
96
|
+
removeMark(removeMark) {
|
|
97
|
+
this.marks = this.marks.filter((mark) => mark.id !== removeMark.id);
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/contants.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const CHANNEL_TYPES = {
|
|
2
|
+
opacity: Symbol('opacity'),
|
|
3
|
+
color: Symbol('color'),
|
|
4
|
+
x: Symbol('position'),
|
|
5
|
+
y: Symbol('position'),
|
|
6
|
+
angle: Symbol('angle'),
|
|
7
|
+
symbol: Symbol('symbol'),
|
|
8
|
+
radius: Symbol('radius'),
|
|
9
|
+
width: Symbol('width')
|
|
10
|
+
};
|
|
11
|
+
export const MARK_PROP_CHANNEL = {
|
|
12
|
+
x: 'x',
|
|
13
|
+
x1: 'x',
|
|
14
|
+
x2: 'x',
|
|
15
|
+
y: 'y',
|
|
16
|
+
y1: 'y',
|
|
17
|
+
y2: 'y',
|
|
18
|
+
rotate: 'angle',
|
|
19
|
+
r: 'radius',
|
|
20
|
+
symbol: 'symbol',
|
|
21
|
+
fill: 'color',
|
|
22
|
+
stroke: 'color',
|
|
23
|
+
opacity: 'opacity',
|
|
24
|
+
fillOpacity: 'opacity',
|
|
25
|
+
strokeOpacity: 'opacity',
|
|
26
|
+
strokeWidth: 'width'
|
|
27
|
+
};
|
|
28
|
+
// export const CHANNEL_MAP: Record<ChannelName, ValueOf<typeof CHANNEL_TYPES>> = {
|
|
29
|
+
// x: CHANNEL_TYPES.x,
|
|
30
|
+
// y: CHANNEL_TYPES.y,
|
|
31
|
+
// opacity: CHANNEL_TYPES.opacity,
|
|
32
|
+
// strokeOpacity: CHANNEL_TYPES.opacity,
|
|
33
|
+
// strokeWidth: CHANNEL_TYPES.width,
|
|
34
|
+
// fillOpacity: CHANNEL_TYPES.opacity,
|
|
35
|
+
// stroke: CHANNEL_TYPES.color,
|
|
36
|
+
// fill: CHANNEL_TYPES.color,
|
|
37
|
+
// r: CHANNEL_TYPES.radius,
|
|
38
|
+
// rotate: CHANNEL_TYPES.angle,
|
|
39
|
+
// symbol: CHANNEL_TYPES.symbol
|
|
40
|
+
// };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { DataRow } from '../types';
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
|
+
data: DataRow[];
|
|
6
|
+
};
|
|
7
|
+
events: {
|
|
8
|
+
[evt: string]: CustomEvent<any>;
|
|
9
|
+
};
|
|
10
|
+
slots: {
|
|
11
|
+
default: {};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type GroupMultipleProps = typeof __propDef.props;
|
|
15
|
+
export type GroupMultipleEvents = typeof __propDef.events;
|
|
16
|
+
export type GroupMultipleSlots = typeof __propDef.slots;
|
|
17
|
+
export default class GroupMultiple extends SvelteComponent<GroupMultipleProps, GroupMultipleEvents, GroupMultipleSlots> {
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { isDate } from 'underscore';
|
|
3
|
+
export default function autoTimeFormat(x, plotWidth) {
|
|
4
|
+
const daysPer100Px = ((toNumber(x.domain[1]) - toNumber(x.domain[0])) / plotWidth / 864e5) * 100;
|
|
5
|
+
const format = daysPer100Px < 1 ? 'HH:mm\nMMM DD' : daysPer100Px < 30 ? 'DD\nMMM' : 'MMM\nYYYY';
|
|
6
|
+
return (date) => dayjs(date).format(format).split('\n');
|
|
7
|
+
}
|
|
8
|
+
function toNumber(d) {
|
|
9
|
+
return isDate(d) ? d.getTime() : +d;
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { interpolateBrBG } from 'd3-scale-chromatic';
|
|
2
|
+
import type { ColorScheme } from '../types';
|
|
3
|
+
export declare const categoricalSchemes: Map<string, readonly string[]>;
|
|
4
|
+
export declare function isCategoricalScheme(scheme: string): boolean;
|
|
5
|
+
type SchemeGetter = (n: number) => readonly string[];
|
|
6
|
+
export declare function isOrdinalScheme(scheme: ColorScheme): boolean;
|
|
7
|
+
export declare function ordinalScheme(scheme: string): SchemeGetter | undefined;
|
|
8
|
+
export declare function ordinalRange(scheme: string, length: number): readonly string[] | undefined;
|
|
9
|
+
export declare function maybeBooleanRange(domain: boolean[], scheme?: string): unknown[] | undefined;
|
|
10
|
+
export declare function isQuantitativeScheme(scheme: string): boolean;
|
|
11
|
+
export declare function quantitativeScheme(scheme: string): typeof interpolateBrBG | undefined;
|
|
12
|
+
export declare function isDivergingScheme(scheme: string): boolean;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { interpolateBlues, interpolateBrBG, interpolateBuGn, interpolateBuPu, interpolateGnBu, interpolateGreens, interpolateGreys, interpolateOranges, interpolateOrRd, interpolatePiYG, interpolatePRGn, interpolatePuBu, interpolatePuBuGn, interpolatePuOr, interpolatePuRd, interpolatePurples, interpolateRdBu, interpolateRdGy, interpolateRdPu, interpolateRdYlBu, interpolateRdYlGn, interpolateReds, interpolateSpectral, interpolateYlGn, interpolateYlGnBu, interpolateYlOrBr, interpolateYlOrRd, interpolateTurbo, interpolateViridis, interpolateMagma, interpolateInferno, interpolatePlasma, interpolateCividis, interpolateCubehelixDefault, interpolateWarm, interpolateCool, interpolateRainbow, interpolateSinebow, schemeAccent, schemeBlues, schemeBrBG, schemeBuGn, schemeBuPu, schemeCategory10, schemeDark2, schemeGnBu, schemeGreens, schemeGreys, schemeOranges, schemeOrRd, schemePaired, schemePastel1, schemePastel2, schemePiYG, schemePRGn, schemePuBu, schemePuBuGn, schemePuOr, schemePuRd, schemePurples, schemeRdBu, schemeRdGy, schemeRdPu, schemeRdYlBu, schemeRdYlGn, schemeReds, schemeSet1, schemeSet2, schemeSet3, schemeSpectral, schemeTableau10, schemeYlGn, schemeYlGnBu, schemeYlOrBr, schemeYlOrRd } from 'd3-scale-chromatic';
|
|
2
|
+
import { quantize } from 'd3-interpolate';
|
|
3
|
+
export const categoricalSchemes = new Map([
|
|
4
|
+
['accent', schemeAccent],
|
|
5
|
+
['category10', schemeCategory10],
|
|
6
|
+
['dark2', schemeDark2],
|
|
7
|
+
['paired', schemePaired],
|
|
8
|
+
['pastel1', schemePastel1],
|
|
9
|
+
['pastel2', schemePastel2],
|
|
10
|
+
['set1', schemeSet1],
|
|
11
|
+
['set2', schemeSet2],
|
|
12
|
+
['set3', schemeSet3],
|
|
13
|
+
['tableau10', schemeTableau10]
|
|
14
|
+
]);
|
|
15
|
+
export function isCategoricalScheme(scheme) {
|
|
16
|
+
return scheme != null && categoricalSchemes.has(`${scheme}`.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
const ordinalSchemes = new Map([
|
|
19
|
+
// diverging
|
|
20
|
+
['brbg', scheme11(schemeBrBG, interpolateBrBG)],
|
|
21
|
+
['prgn', scheme11(schemePRGn, interpolatePRGn)],
|
|
22
|
+
['piyg', scheme11(schemePiYG, interpolatePiYG)],
|
|
23
|
+
['puor', scheme11(schemePuOr, interpolatePuOr)],
|
|
24
|
+
['rdbu', scheme11(schemeRdBu, interpolateRdBu)],
|
|
25
|
+
['rdgy', scheme11(schemeRdGy, interpolateRdGy)],
|
|
26
|
+
['rdylbu', scheme11(schemeRdYlBu, interpolateRdYlBu)],
|
|
27
|
+
['rdylgn', scheme11(schemeRdYlGn, interpolateRdYlGn)],
|
|
28
|
+
['spectral', scheme11(schemeSpectral, interpolateSpectral)],
|
|
29
|
+
// reversed diverging (for temperature data)
|
|
30
|
+
['burd', scheme11r(schemeRdBu, interpolateRdBu)],
|
|
31
|
+
['buylrd', scheme11r(schemeRdYlBu, interpolateRdYlBu)],
|
|
32
|
+
// sequential (single-hue)
|
|
33
|
+
['blues', scheme9(schemeBlues, interpolateBlues)],
|
|
34
|
+
['greens', scheme9(schemeGreens, interpolateGreens)],
|
|
35
|
+
['greys', scheme9(schemeGreys, interpolateGreys)],
|
|
36
|
+
['oranges', scheme9(schemeOranges, interpolateOranges)],
|
|
37
|
+
['purples', scheme9(schemePurples, interpolatePurples)],
|
|
38
|
+
['reds', scheme9(schemeReds, interpolateReds)],
|
|
39
|
+
// sequential (multi-hue)
|
|
40
|
+
['turbo', schemei(interpolateTurbo)],
|
|
41
|
+
['viridis', schemei(interpolateViridis)],
|
|
42
|
+
['magma', schemei(interpolateMagma)],
|
|
43
|
+
['inferno', schemei(interpolateInferno)],
|
|
44
|
+
['plasma', schemei(interpolatePlasma)],
|
|
45
|
+
['cividis', schemei(interpolateCividis)],
|
|
46
|
+
['cubehelix', schemei(interpolateCubehelixDefault)],
|
|
47
|
+
['warm', schemei(interpolateWarm)],
|
|
48
|
+
['cool', schemei(interpolateCool)],
|
|
49
|
+
['bugn', scheme9(schemeBuGn, interpolateBuGn)],
|
|
50
|
+
['bupu', scheme9(schemeBuPu, interpolateBuPu)],
|
|
51
|
+
['gnbu', scheme9(schemeGnBu, interpolateGnBu)],
|
|
52
|
+
['orrd', scheme9(schemeOrRd, interpolateOrRd)],
|
|
53
|
+
['pubu', scheme9(schemePuBu, interpolatePuBu)],
|
|
54
|
+
['pubugn', scheme9(schemePuBuGn, interpolatePuBuGn)],
|
|
55
|
+
['purd', scheme9(schemePuRd, interpolatePuRd)],
|
|
56
|
+
['rdpu', scheme9(schemeRdPu, interpolateRdPu)],
|
|
57
|
+
['ylgn', scheme9(schemeYlGn, interpolateYlGn)],
|
|
58
|
+
['ylgnbu', scheme9(schemeYlGnBu, interpolateYlGnBu)],
|
|
59
|
+
['ylorbr', scheme9(schemeYlOrBr, interpolateYlOrBr)],
|
|
60
|
+
['ylorrd', scheme9(schemeYlOrRd, interpolateYlOrRd)],
|
|
61
|
+
// cyclical
|
|
62
|
+
['rainbow', schemeicyclical(interpolateRainbow)],
|
|
63
|
+
['sinebow', schemeicyclical(interpolateSinebow)]
|
|
64
|
+
]);
|
|
65
|
+
export function isOrdinalScheme(scheme) {
|
|
66
|
+
return ordinalSchemes.has(scheme);
|
|
67
|
+
}
|
|
68
|
+
function scheme9(scheme, interpolate) {
|
|
69
|
+
return (n) => {
|
|
70
|
+
if (n === 1)
|
|
71
|
+
return [scheme[3][1]]; // favor midpoint
|
|
72
|
+
if (n === 2)
|
|
73
|
+
return [scheme[3][1], scheme[3][2]]; // favor darker
|
|
74
|
+
n = Math.max(3, Math.floor(n));
|
|
75
|
+
return n > 9 ? quantize(interpolate, n) : scheme[n];
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function scheme11(scheme, interpolate) {
|
|
79
|
+
return (n) => {
|
|
80
|
+
if (n === 2)
|
|
81
|
+
return [scheme[3][0], scheme[3][2]]; // favor diverging extrema
|
|
82
|
+
n = Math.max(3, Math.floor(n));
|
|
83
|
+
return n > 11 ? quantize(interpolate, n) : scheme[n];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function scheme11r(scheme, interpolate) {
|
|
87
|
+
return (n) => {
|
|
88
|
+
if (n === 2)
|
|
89
|
+
return [scheme[3][2], scheme[3][0]]; // favor diverging extrema
|
|
90
|
+
n = Math.max(3, Math.floor(n));
|
|
91
|
+
return n > 11 ? quantize((t) => interpolate(1 - t), n) : scheme[n].slice().reverse();
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function schemei(interpolate) {
|
|
95
|
+
return (n) => quantize(interpolate, Math.max(2, Math.floor(n)));
|
|
96
|
+
}
|
|
97
|
+
function schemeicyclical(interpolate) {
|
|
98
|
+
return (n) => quantize(interpolate, Math.floor(n) + 1).slice(0, -1);
|
|
99
|
+
}
|
|
100
|
+
export function ordinalScheme(scheme) {
|
|
101
|
+
const s = `${scheme}`.toLowerCase();
|
|
102
|
+
if (!ordinalSchemes.has(s))
|
|
103
|
+
throw new Error(`unknown ordinal scheme: ${s}`);
|
|
104
|
+
return ordinalSchemes.get(s);
|
|
105
|
+
}
|
|
106
|
+
export function ordinalRange(scheme, length) {
|
|
107
|
+
const s = ordinalScheme(scheme);
|
|
108
|
+
const r = typeof s === 'function' ? s({ length }) : s;
|
|
109
|
+
return r.length !== length ? r.slice(0, length) : r;
|
|
110
|
+
}
|
|
111
|
+
// If the specified domain contains only booleans (ignoring null and undefined),
|
|
112
|
+
// returns a corresponding range where false is mapped to the low color and true
|
|
113
|
+
// is mapped to the high color of the specified scheme.
|
|
114
|
+
export function maybeBooleanRange(domain, scheme = 'greys') {
|
|
115
|
+
const range = new Set();
|
|
116
|
+
const [f, t] = ordinalRange(scheme, 2);
|
|
117
|
+
for (const value of domain) {
|
|
118
|
+
if (value == null)
|
|
119
|
+
continue;
|
|
120
|
+
if (value === true)
|
|
121
|
+
range.add(t);
|
|
122
|
+
else if (value === false)
|
|
123
|
+
range.add(f);
|
|
124
|
+
else
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
return [...range];
|
|
128
|
+
}
|
|
129
|
+
const quantitativeSchemes = new Map([
|
|
130
|
+
// diverging
|
|
131
|
+
['brbg', interpolateBrBG],
|
|
132
|
+
['prgn', interpolatePRGn],
|
|
133
|
+
['piyg', interpolatePiYG],
|
|
134
|
+
['puor', interpolatePuOr],
|
|
135
|
+
['rdbu', interpolateRdBu],
|
|
136
|
+
['rdgy', interpolateRdGy],
|
|
137
|
+
['rdylbu', interpolateRdYlBu],
|
|
138
|
+
['rdylgn', interpolateRdYlGn],
|
|
139
|
+
['spectral', interpolateSpectral],
|
|
140
|
+
// reversed diverging (for temperature data)
|
|
141
|
+
['burd', (t) => interpolateRdBu(1 - t)],
|
|
142
|
+
['buylrd', (t) => interpolateRdYlBu(1 - t)],
|
|
143
|
+
// sequential (single-hue)
|
|
144
|
+
['blues', interpolateBlues],
|
|
145
|
+
['greens', interpolateGreens],
|
|
146
|
+
['greys', interpolateGreys],
|
|
147
|
+
['purples', interpolatePurples],
|
|
148
|
+
['reds', interpolateReds],
|
|
149
|
+
['oranges', interpolateOranges],
|
|
150
|
+
// sequential (multi-hue)
|
|
151
|
+
['turbo', interpolateTurbo],
|
|
152
|
+
['viridis', interpolateViridis],
|
|
153
|
+
['magma', interpolateMagma],
|
|
154
|
+
['inferno', interpolateInferno],
|
|
155
|
+
['plasma', interpolatePlasma],
|
|
156
|
+
['cividis', interpolateCividis],
|
|
157
|
+
['cubehelix', interpolateCubehelixDefault],
|
|
158
|
+
['warm', interpolateWarm],
|
|
159
|
+
['cool', interpolateCool],
|
|
160
|
+
['bugn', interpolateBuGn],
|
|
161
|
+
['bupu', interpolateBuPu],
|
|
162
|
+
['gnbu', interpolateGnBu],
|
|
163
|
+
['orrd', interpolateOrRd],
|
|
164
|
+
['pubugn', interpolatePuBuGn],
|
|
165
|
+
['pubu', interpolatePuBu],
|
|
166
|
+
['purd', interpolatePuRd],
|
|
167
|
+
['rdpu', interpolateRdPu],
|
|
168
|
+
['ylgnbu', interpolateYlGnBu],
|
|
169
|
+
['ylgn', interpolateYlGn],
|
|
170
|
+
['ylorbr', interpolateYlOrBr],
|
|
171
|
+
['ylorrd', interpolateYlOrRd],
|
|
172
|
+
// cyclical
|
|
173
|
+
['rainbow', interpolateRainbow],
|
|
174
|
+
['sinebow', interpolateSinebow]
|
|
175
|
+
]);
|
|
176
|
+
export function isQuantitativeScheme(scheme) {
|
|
177
|
+
return quantitativeSchemes.has(scheme);
|
|
178
|
+
}
|
|
179
|
+
export function quantitativeScheme(scheme) {
|
|
180
|
+
const s = `${scheme}`.toLowerCase();
|
|
181
|
+
if (!quantitativeSchemes.has(s))
|
|
182
|
+
throw new Error(`unknown quantitative scheme: ${s}`);
|
|
183
|
+
return quantitativeSchemes.get(s);
|
|
184
|
+
}
|
|
185
|
+
const divergingSchemes = new Set([
|
|
186
|
+
'brbg',
|
|
187
|
+
'prgn',
|
|
188
|
+
'piyg',
|
|
189
|
+
'puor',
|
|
190
|
+
'rdbu',
|
|
191
|
+
'rdgy',
|
|
192
|
+
'rdylbu',
|
|
193
|
+
'rdylgn',
|
|
194
|
+
'spectral',
|
|
195
|
+
'burd',
|
|
196
|
+
'buylrd'
|
|
197
|
+
]);
|
|
198
|
+
export function isDivergingScheme(scheme) {
|
|
199
|
+
return scheme != null && divergingSchemes.has(`${scheme}`.toLowerCase());
|
|
200
|
+
}
|