matterviz 0.1.0
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/BohrAtom.svelte +105 -0
- package/dist/BohrAtom.svelte.d.ts +21 -0
- package/dist/ControlPanel.svelte +158 -0
- package/dist/ControlPanel.svelte.d.ts +18 -0
- package/dist/Icon.svelte +23 -0
- package/dist/Icon.svelte.d.ts +8 -0
- package/dist/InfoCard.svelte +79 -0
- package/dist/InfoCard.svelte.d.ts +23 -0
- package/dist/Nucleus.svelte +64 -0
- package/dist/Nucleus.svelte.d.ts +16 -0
- package/dist/Spinner.svelte +44 -0
- package/dist/Spinner.svelte.d.ts +7 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.js +30 -0
- package/dist/colors/alloy-colors.json +111 -0
- package/dist/colors/dark-mode-colors.json +111 -0
- package/dist/colors/index.d.ts +26 -0
- package/dist/colors/index.js +72 -0
- package/dist/colors/jmol-colors.json +111 -0
- package/dist/colors/muted-colors.json +111 -0
- package/dist/colors/pastel-colors.json +111 -0
- package/dist/colors/vesta-colors.json +111 -0
- package/dist/composition/BarChart.svelte +260 -0
- package/dist/composition/BarChart.svelte.d.ts +33 -0
- package/dist/composition/BubbleChart.svelte +166 -0
- package/dist/composition/BubbleChart.svelte.d.ts +30 -0
- package/dist/composition/Composition.svelte +73 -0
- package/dist/composition/Composition.svelte.d.ts +27 -0
- package/dist/composition/PieChart.svelte +236 -0
- package/dist/composition/PieChart.svelte.d.ts +36 -0
- package/dist/composition/index.d.ts +5 -0
- package/dist/composition/index.js +5 -0
- package/dist/composition/parse.d.ts +14 -0
- package/dist/composition/parse.js +307 -0
- package/dist/element/ElementHeading.svelte +21 -0
- package/dist/element/ElementHeading.svelte.d.ts +8 -0
- package/dist/element/ElementPhoto.svelte +56 -0
- package/dist/element/ElementPhoto.svelte.d.ts +9 -0
- package/dist/element/ElementStats.svelte +73 -0
- package/dist/element/ElementStats.svelte.d.ts +8 -0
- package/dist/element/ElementTile.svelte +449 -0
- package/dist/element/ElementTile.svelte.d.ts +25 -0
- package/dist/element/data.d.ts +4958 -0
- package/dist/element/data.js +5628 -0
- package/dist/element/index.d.ts +4 -0
- package/dist/element/index.js +4 -0
- package/dist/icons.d.ts +435 -0
- package/dist/icons.js +435 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +43 -0
- package/dist/io/decompress.d.ts +16 -0
- package/dist/io/decompress.js +78 -0
- package/dist/io/export.d.ts +9 -0
- package/dist/io/export.js +205 -0
- package/dist/io/parse.d.ts +53 -0
- package/dist/io/parse.js +747 -0
- package/dist/labels.d.ts +31 -0
- package/dist/labels.js +209 -0
- package/dist/material/MaterialCard.svelte +135 -0
- package/dist/material/MaterialCard.svelte.d.ts +10 -0
- package/dist/material/SymmetryCard.svelte +23 -0
- package/dist/material/SymmetryCard.svelte.d.ts +9 -0
- package/dist/material/index.d.ts +2 -0
- package/dist/material/index.js +2 -0
- package/dist/math.d.ts +24 -0
- package/dist/math.js +216 -0
- package/dist/periodic-table/PeriodicTable.svelte +284 -0
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +50 -0
- package/dist/periodic-table/PropertySelect.svelte +20 -0
- package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
- package/dist/periodic-table/TableInset.svelte +18 -0
- package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
- package/dist/periodic-table/index.d.ts +9 -0
- package/dist/periodic-table/index.js +3 -0
- package/dist/plot/ColorBar.svelte +414 -0
- package/dist/plot/ColorBar.svelte.d.ts +22 -0
- package/dist/plot/ColorScaleSelect.svelte +31 -0
- package/dist/plot/ColorScaleSelect.svelte.d.ts +15 -0
- package/dist/plot/ElementScatter.svelte +38 -0
- package/dist/plot/ElementScatter.svelte.d.ts +14 -0
- package/dist/plot/Line.svelte +42 -0
- package/dist/plot/Line.svelte.d.ts +15 -0
- package/dist/plot/PlotLegend.svelte +206 -0
- package/dist/plot/PlotLegend.svelte.d.ts +18 -0
- package/dist/plot/ScatterPlot.svelte +1753 -0
- package/dist/plot/ScatterPlot.svelte.d.ts +114 -0
- package/dist/plot/ScatterPlotControls.svelte +505 -0
- package/dist/plot/ScatterPlotControls.svelte.d.ts +33 -0
- package/dist/plot/ScatterPoint.svelte +72 -0
- package/dist/plot/ScatterPoint.svelte.d.ts +17 -0
- package/dist/plot/index.d.ts +168 -0
- package/dist/plot/index.js +46 -0
- package/dist/state.svelte.d.ts +12 -0
- package/dist/state.svelte.js +11 -0
- package/dist/structure/Bond.svelte +68 -0
- package/dist/structure/Bond.svelte.d.ts +13 -0
- package/dist/structure/Lattice.svelte +115 -0
- package/dist/structure/Lattice.svelte.d.ts +15 -0
- package/dist/structure/Structure.svelte +298 -0
- package/dist/structure/Structure.svelte.d.ts +28 -0
- package/dist/structure/StructureCard.svelte +26 -0
- package/dist/structure/StructureCard.svelte.d.ts +9 -0
- package/dist/structure/StructureControls.svelte +383 -0
- package/dist/structure/StructureControls.svelte.d.ts +23 -0
- package/dist/structure/StructureLegend.svelte +130 -0
- package/dist/structure/StructureLegend.svelte.d.ts +17 -0
- package/dist/structure/StructureScene.svelte +331 -0
- package/dist/structure/StructureScene.svelte.d.ts +47 -0
- package/dist/structure/bonding.d.ts +16 -0
- package/dist/structure/bonding.js +150 -0
- package/dist/structure/index.d.ts +98 -0
- package/dist/structure/index.js +114 -0
- package/dist/structure/pbc.d.ts +6 -0
- package/dist/structure/pbc.js +72 -0
- package/dist/trajectory/Sidebar.svelte +412 -0
- package/dist/trajectory/Sidebar.svelte.d.ts +14 -0
- package/dist/trajectory/Trajectory.svelte +1084 -0
- package/dist/trajectory/Trajectory.svelte.d.ts +49 -0
- package/dist/trajectory/TrajectoryError.svelte +120 -0
- package/dist/trajectory/TrajectoryError.svelte.d.ts +12 -0
- package/dist/trajectory/extract.d.ts +5 -0
- package/dist/trajectory/extract.js +157 -0
- package/dist/trajectory/index.d.ts +16 -0
- package/dist/trajectory/index.js +49 -0
- package/dist/trajectory/parse.d.ts +13 -0
- package/dist/trajectory/parse.js +1093 -0
- package/dist/trajectory/plotting.d.ts +12 -0
- package/dist/trajectory/plotting.js +148 -0
- package/license +21 -0
- package/package.json +131 -0
- package/readme.md +95 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
<script lang="ts">import { format_num, LOG_MIN_EPS } from '..';
|
|
2
|
+
import { luminance } from '../labels';
|
|
3
|
+
import { format } from 'd3-format';
|
|
4
|
+
import * as d3 from 'd3-scale';
|
|
5
|
+
import * as d3_sc from 'd3-scale-chromatic';
|
|
6
|
+
import { timeFormat } from 'd3-time-format';
|
|
7
|
+
let { title = null, color_scale = $bindable(`interpolateViridis`), style = null, title_style = null, wrapper_style = null, tick_labels = $bindable(4), tick_format = undefined, range = [0, 1], orientation = `horizontal`, snap_ticks = true, steps = 50, nice_range = $bindable(range), title_side = undefined, // no default here, depends on orientation and tick_side
|
|
8
|
+
tick_side = `primary`, scale_type = `linear`, color_scale_fn = undefined, color_scale_domain = undefined, } = $props();
|
|
9
|
+
// Derive actual title_side, applying default logic if user didn't provide one
|
|
10
|
+
let actual_title_side = $derived.by(() => {
|
|
11
|
+
if (title_side !== undefined)
|
|
12
|
+
return title_side; // Use user-provided value if available
|
|
13
|
+
// Calculate default based on orientation and tick_side
|
|
14
|
+
if (tick_side === `inside`)
|
|
15
|
+
return `left`; // Default to left if ticks are inside
|
|
16
|
+
// If ticks are primary (bottom), default label to top
|
|
17
|
+
// If ticks are secondary (top), default label to bottom
|
|
18
|
+
if (orientation === `horizontal`) {
|
|
19
|
+
return tick_side === `primary` ? `top` : `bottom`;
|
|
20
|
+
}
|
|
21
|
+
else { // orientation === `vertical`
|
|
22
|
+
// If ticks are primary (right), default label to left
|
|
23
|
+
// If ticks are secondary (left), default label to right
|
|
24
|
+
return tick_side === `primary` ? `left` : `right`;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
// Calculate originally requested number of ticks
|
|
28
|
+
let requested_n_ticks = $derived(Array.isArray(tick_labels)
|
|
29
|
+
? tick_labels.length
|
|
30
|
+
: typeof tick_labels === `number`
|
|
31
|
+
? tick_labels
|
|
32
|
+
: 5);
|
|
33
|
+
// Determine actual number of ticks to generate
|
|
34
|
+
let actual_n_ticks = $derived(Array.isArray(tick_labels)
|
|
35
|
+
? tick_labels.length
|
|
36
|
+
: typeof tick_labels === `number`
|
|
37
|
+
? tick_labels
|
|
38
|
+
: 5);
|
|
39
|
+
// Scale for ticks - based *only* on 'range' prop and 'scale_type' for ticks
|
|
40
|
+
let scale_for_ticks = $derived.by(() => {
|
|
41
|
+
let use_log_for_ticks = scale_type === `log`;
|
|
42
|
+
let [scale_min, scale_max] = range;
|
|
43
|
+
// Validate range for log scale ticks and apply epsilon if needed
|
|
44
|
+
if (use_log_for_ticks) {
|
|
45
|
+
if (scale_max <= 0) {
|
|
46
|
+
console.warn(`Log scale requires a positive max value for ticks. Received max=${scale_max}. Using linear scale for ticks instead.`);
|
|
47
|
+
use_log_for_ticks = false;
|
|
48
|
+
}
|
|
49
|
+
else if (scale_min <= 0) {
|
|
50
|
+
console.warn(`Log scale received non-positive min value (${scale_min}) for ticks. Using epsilon=${LOG_MIN_EPS} instead.`);
|
|
51
|
+
scale_min = LOG_MIN_EPS; // Substitute with epsilon
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const scale = use_log_for_ticks ? d3.scaleLog() : d3.scaleLinear();
|
|
55
|
+
// Use potentially adjusted min/max for domain
|
|
56
|
+
scale.domain([scale_min, scale_max]);
|
|
57
|
+
// Set range based on orientation for positioning (0-100 for percent)
|
|
58
|
+
scale.range(orientation === `vertical` ? [100, 0] : [0, 100]);
|
|
59
|
+
// Apply scale.nice() only if snapping is enabled and not an explicit array.
|
|
60
|
+
if (snap_ticks && !Array.isArray(tick_labels)) {
|
|
61
|
+
scale.nice(actual_n_ticks);
|
|
62
|
+
}
|
|
63
|
+
return scale;
|
|
64
|
+
});
|
|
65
|
+
let ticks_array = $derived.by(() => {
|
|
66
|
+
const num_ticks_to_generate = Array.isArray(tick_labels)
|
|
67
|
+
? requested_n_ticks
|
|
68
|
+
: actual_n_ticks;
|
|
69
|
+
if (Array.isArray(tick_labels)) {
|
|
70
|
+
// Use user-provided ticks directly
|
|
71
|
+
return tick_labels.map(Number).filter((n) => !isNaN(n));
|
|
72
|
+
}
|
|
73
|
+
// Handle edge cases for number of ticks
|
|
74
|
+
if (num_ticks_to_generate <= 0)
|
|
75
|
+
return [];
|
|
76
|
+
if (num_ticks_to_generate === 1)
|
|
77
|
+
return [scale_for_ticks.domain()[0]];
|
|
78
|
+
const scale = scale_for_ticks; // Use derived scale (which handles log validation for ticks)
|
|
79
|
+
const [scale_min, scale_max] = scale.domain();
|
|
80
|
+
// check scale_type prop for log tick generation
|
|
81
|
+
const use_log_ticks = scale_type === `log` && scale_min > 0 && scale_max > 0;
|
|
82
|
+
if (use_log_ticks) {
|
|
83
|
+
// Use D3's ticks for log scale if snapping is enabled
|
|
84
|
+
if (snap_ticks) {
|
|
85
|
+
// For snapped log ticks, manually generate integer powers of 10 within niced domain.
|
|
86
|
+
const [nice_min, nice_max] = scale.domain();
|
|
87
|
+
const start_exp = Math.ceil(Math.log10(nice_min));
|
|
88
|
+
const end_exp = Math.floor(Math.log10(nice_max));
|
|
89
|
+
const power_of_10_ticks = [];
|
|
90
|
+
for (let exp = start_exp; exp <= end_exp; exp++) {
|
|
91
|
+
power_of_10_ticks.push(Math.pow(10, exp));
|
|
92
|
+
}
|
|
93
|
+
// Ensure domain endpoints are included if they are powers of 10 and missed by loop
|
|
94
|
+
if (Math.abs(Math.log10(nice_min) % 1) < LOG_MIN_EPS &&
|
|
95
|
+
!power_of_10_ticks.includes(nice_min)) {
|
|
96
|
+
power_of_10_ticks.unshift(nice_min);
|
|
97
|
+
}
|
|
98
|
+
if (Math.abs(Math.log10(nice_max) % 1) < LOG_MIN_EPS &&
|
|
99
|
+
!power_of_10_ticks.includes(nice_max)) {
|
|
100
|
+
power_of_10_ticks.push(nice_max);
|
|
101
|
+
}
|
|
102
|
+
// If no powers of 10 are within range (e.g., [0.1, 0.9]), fall back to D3 ticks?
|
|
103
|
+
// Or just return filtered list which might be empty?
|
|
104
|
+
// For now, let's stick with only powers of 10.
|
|
105
|
+
// If list is empty maybe return domain ends?
|
|
106
|
+
if (power_of_10_ticks.length === 0) {
|
|
107
|
+
// If domain is very small, e.g. [1e-9, 1e-8], no powers of 10.
|
|
108
|
+
// Return exact domain ends as ticks in this edge case.
|
|
109
|
+
return [nice_min, nice_max];
|
|
110
|
+
}
|
|
111
|
+
return power_of_10_ticks;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Generate exactly num_ticks_to_generate manually for log scale if not snapping
|
|
115
|
+
const log_min = Math.log10(scale_min);
|
|
116
|
+
const log_max = Math.log10(scale_max);
|
|
117
|
+
return [...Array(num_ticks_to_generate).keys()].map((idx) => {
|
|
118
|
+
const t = idx / (num_ticks_to_generate - 1);
|
|
119
|
+
const log_val = log_min + t * (log_max - log_min);
|
|
120
|
+
return Math.pow(10, log_val);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Linear scale logic
|
|
126
|
+
if (snap_ticks) {
|
|
127
|
+
// Use D3's default nice ticks for linear scale
|
|
128
|
+
return scale.ticks(num_ticks_to_generate);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Generate exactly num_ticks_to_generate evenly spaced linear ticks
|
|
132
|
+
return [...Array(num_ticks_to_generate).keys()].map((idx) => {
|
|
133
|
+
const t = idx / (num_ticks_to_generate - 1);
|
|
134
|
+
return scale_min + t * (scale_max - scale_min);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// Update nice_range binding when snapping ticks
|
|
140
|
+
$effect.pre(() => {
|
|
141
|
+
if (snap_ticks && !Array.isArray(tick_labels)) {
|
|
142
|
+
// Use derived scale to get niced domain
|
|
143
|
+
const domain = scale_for_ticks.domain();
|
|
144
|
+
// Ensure domain has two elements before assigning
|
|
145
|
+
if (domain.length === 2)
|
|
146
|
+
nice_range = domain;
|
|
147
|
+
else
|
|
148
|
+
nice_range = range; // Fallback
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
nice_range = range; // Use original range if not snapping or labels provided
|
|
152
|
+
});
|
|
153
|
+
// Determine effective color scale function to use
|
|
154
|
+
let actual_color_scale_fn = $derived.by(() => {
|
|
155
|
+
if (color_scale_fn)
|
|
156
|
+
return color_scale_fn; // Prioritize passed function
|
|
157
|
+
// Fallback: create function from scheme name/function in 'color_scale' prop
|
|
158
|
+
let interpolator = d3_sc.interpolateViridis; // Default interpolator
|
|
159
|
+
if (typeof color_scale === `string`) {
|
|
160
|
+
const func_name = color_scale.startsWith(`interpolate`)
|
|
161
|
+
? color_scale
|
|
162
|
+
: `interpolate${color_scale}`;
|
|
163
|
+
if (func_name in d3_sc) {
|
|
164
|
+
interpolator = d3_sc[func_name];
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error(`Color scale '${color_scale}' not found. Falling back on 'Viridis'.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (typeof color_scale === `function`) {
|
|
171
|
+
// User passed a function (assumed interpolator [0,1] -> color)
|
|
172
|
+
interpolator = color_scale;
|
|
173
|
+
}
|
|
174
|
+
// Need a domain for this fallback scale! Use 'range' prop.
|
|
175
|
+
let [min_val, max_val] = range;
|
|
176
|
+
// Use scale_type for fallback scale creation too. Validate domain for log.
|
|
177
|
+
let use_log_fallback = scale_type === `log`;
|
|
178
|
+
if (use_log_fallback) {
|
|
179
|
+
if (max_val <= 0) {
|
|
180
|
+
console.warn(`Log scale requires a positive max value for fallback scale. Received max=${max_val}. Using linear scale for colors.`);
|
|
181
|
+
use_log_fallback = false;
|
|
182
|
+
}
|
|
183
|
+
else if (min_val <= 0) {
|
|
184
|
+
console.warn(`Log scale received non-positive min value (${min_val}) for fallback scale. Using epsilon=${LOG_MIN_EPS} instead.`);
|
|
185
|
+
min_val = LOG_MIN_EPS; // Substitute with epsilon
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Use potentially adjusted min/max for domain
|
|
189
|
+
const domain_for_scale = [min_val, max_val];
|
|
190
|
+
return use_log_fallback
|
|
191
|
+
? d3.scaleSequentialLog(interpolator).domain(domain_for_scale)
|
|
192
|
+
: d3.scaleSequential(interpolator).domain(domain_for_scale);
|
|
193
|
+
});
|
|
194
|
+
// Determine effective domain for color ramp interpolation *steps*
|
|
195
|
+
// Prioritize color_scale_domain if provided, otherwise use general 'range' prop.
|
|
196
|
+
let color_interp_domain = $derived(color_scale_domain ?? range);
|
|
197
|
+
let grad_dir = $derived(orientation === `horizontal` ? `to right` : `to top`);
|
|
198
|
+
// Generate color stops for gradient background using effective scale and domain
|
|
199
|
+
let ramped = $derived.by(() => {
|
|
200
|
+
const [min_ramp_domain, max_ramp_domain] = color_interp_domain;
|
|
201
|
+
// Validate domain for log interpolation and apply epsilon if needed
|
|
202
|
+
let use_log_interp = scale_type === `log`;
|
|
203
|
+
let adjusted_min_ramp = min_ramp_domain;
|
|
204
|
+
let adjusted_max_ramp = max_ramp_domain;
|
|
205
|
+
if (use_log_interp) {
|
|
206
|
+
if (max_ramp_domain <= 0) {
|
|
207
|
+
console.warn(`Log scale specified for gradient, but max domain value (${max_ramp_domain}) is not positive. Using linear interpolation.`);
|
|
208
|
+
use_log_interp = false;
|
|
209
|
+
}
|
|
210
|
+
else if (min_ramp_domain <= 0) {
|
|
211
|
+
console.warn(`Log scale specified for gradient, but min domain value (${min_ramp_domain}) is not positive. Using epsilon=${LOG_MIN_EPS} instead.`);
|
|
212
|
+
adjusted_min_ramp = LOG_MIN_EPS; // Substitute with epsilon
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return [...Array(steps).keys()].map((_, idx) => {
|
|
216
|
+
const t = idx / (steps - 1); // Normalized position 0 to 1
|
|
217
|
+
let data_value;
|
|
218
|
+
if (use_log_interp) {
|
|
219
|
+
// Interpolate logarithmically within (potentially adjusted) ramp domain
|
|
220
|
+
const log_min = Math.log10(adjusted_min_ramp);
|
|
221
|
+
const log_max = Math.log10(adjusted_max_ramp); // Already checked max > 0
|
|
222
|
+
if (log_min === log_max) {
|
|
223
|
+
data_value = adjusted_min_ramp; // Avoid division by zero / NaN
|
|
224
|
+
}
|
|
225
|
+
else
|
|
226
|
+
data_value = Math.pow(10, log_min + t * (log_max - log_min));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Interpolate linearly within original ramp domain
|
|
230
|
+
data_value = min_ramp_domain + t * (max_ramp_domain - min_ramp_domain);
|
|
231
|
+
}
|
|
232
|
+
// Apply effective color scale function
|
|
233
|
+
return actual_color_scale_fn(data_value) ?? `transparent`;
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
// Determine wrapper flex-direction based on actual title_side
|
|
237
|
+
let wrapper_flex_dir = $derived({ left: `row`, right: `row-reverse`, top: `column`, bottom: `column-reverse` }[actual_title_side]);
|
|
238
|
+
// CSS variables for bar width/height based on orientation
|
|
239
|
+
let bar_dynamic_style = $derived(`--cbar-width: ${orientation === `horizontal` ? `100%` : `var(--cbar-thickness, 14px)`};
|
|
240
|
+
--cbar-height: ${orientation === `vertical` ? `100%` : `var(--cbar-thickness, 14px)`};
|
|
241
|
+
background: linear-gradient(${grad_dir}, ${ramped.join(`, `)});` + (style ?? ``));
|
|
242
|
+
// Calculate additional margin for main label if it overlaps with ticks
|
|
243
|
+
let label_overlap_margin_style = $derived.by(() => {
|
|
244
|
+
// Overlap only possible if ticks are outside and on same side as label
|
|
245
|
+
if (tick_side === `inside`)
|
|
246
|
+
return ``;
|
|
247
|
+
// Determine concrete side outside ticks are on
|
|
248
|
+
const concrete_outside_tick_side = orientation === `horizontal`
|
|
249
|
+
? tick_side === `primary` ? `bottom` : `top`
|
|
250
|
+
: tick_side === `primary`
|
|
251
|
+
? `right`
|
|
252
|
+
: `left`;
|
|
253
|
+
if (actual_title_side !== concrete_outside_tick_side)
|
|
254
|
+
return ``;
|
|
255
|
+
const offset = `var(--cbar-label-overlap-offset, 1em)`;
|
|
256
|
+
const side_map = { top: `bottom`, bottom: `top`, left: `right`, right: `left` };
|
|
257
|
+
const margin_side = side_map[actual_title_side];
|
|
258
|
+
return `margin-${margin_side}: ${offset};`;
|
|
259
|
+
});
|
|
260
|
+
let actual_title_style = $derived.by(() => {
|
|
261
|
+
let rotate_style = ``;
|
|
262
|
+
let size_constraint = ``; // for width constraint
|
|
263
|
+
if (orientation === `vertical` &&
|
|
264
|
+
(actual_title_side === `left` || actual_title_side === `right`)) {
|
|
265
|
+
if (actual_title_side === `right`) {
|
|
266
|
+
rotate_style =
|
|
267
|
+
`transform: rotate(90deg); transform-origin: center; white-space: nowrap;`; // Apply title rotation
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
rotate_style =
|
|
271
|
+
`transform: rotate(-90deg); transform-origin: center; white-space: nowrap;`;
|
|
272
|
+
}
|
|
273
|
+
size_constraint = `max-width: var(--cbar-label-max-width, 2em);`; // max-width constraint for rotated labels
|
|
274
|
+
}
|
|
275
|
+
return `${rotate_style} ${size_constraint} ${label_overlap_margin_style} ${title_style ?? ``}`.trim();
|
|
276
|
+
});
|
|
277
|
+
function get_tick_text_color(tick_value) {
|
|
278
|
+
// Only apply dynamic color if ticks are inside bar
|
|
279
|
+
if (tick_side !== `inside`)
|
|
280
|
+
return null;
|
|
281
|
+
const bg_color = actual_color_scale_fn(tick_value);
|
|
282
|
+
// Default to black if luminance calculation fails or color is invalid
|
|
283
|
+
try {
|
|
284
|
+
return luminance(bg_color) > 0.5 ? `black` : `white`;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
console.error(`Error calculating luminance for tick ${tick_value}:`, error);
|
|
288
|
+
return `black`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Align items based on orientation and title position
|
|
292
|
+
let div_style = $derived(`
|
|
293
|
+
--cbar-wrapper-align-items: ${orientation === `vertical` &&
|
|
294
|
+
(actual_title_side === `left` || actual_title_side === `right`)
|
|
295
|
+
? `stretch`
|
|
296
|
+
: `center`};
|
|
297
|
+
--cbar-label-display: ${orientation === `vertical` &&
|
|
298
|
+
(actual_title_side === `left` || actual_title_side === `right`)
|
|
299
|
+
? `flex`
|
|
300
|
+
: `inline-block`};
|
|
301
|
+
height: ${orientation === `vertical`
|
|
302
|
+
? `var(--cbar-height, 100%)`
|
|
303
|
+
: `var(--cbar-height, auto)`};
|
|
304
|
+
min-height: ${orientation === `vertical` ? `var(--cbar-min-height, 150px)` : `auto`};
|
|
305
|
+
max-height: ${orientation === `vertical` ? `var(--cbar-max-height, 1000px)` : `none`}; ${wrapper_style ?? ``}`);
|
|
306
|
+
</script>
|
|
307
|
+
|
|
308
|
+
<div style:flex-direction={wrapper_flex_dir} style={div_style} class="colorbar">
|
|
309
|
+
{#if title}<span style={actual_title_style} class="label">{@html title}</span>{/if}
|
|
310
|
+
<div style={bar_dynamic_style} class="bar">
|
|
311
|
+
{#each tick_side === `inside` ? ticks_array.slice(1, -1) : ticks_array as
|
|
312
|
+
tick_label
|
|
313
|
+
(tick_label)
|
|
314
|
+
}
|
|
315
|
+
{@const position_percent =
|
|
316
|
+
// Use derived scale's mapping function to get position percent
|
|
317
|
+
scale_for_ticks(tick_label)}
|
|
318
|
+
{@const tick_inline_style = `
|
|
319
|
+
position: absolute;
|
|
320
|
+
${orientation === `horizontal` ? `left` : `top`}: ${position_percent}%;
|
|
321
|
+
color: ${get_tick_text_color(tick_label) ?? `inherit`};
|
|
322
|
+
`}
|
|
323
|
+
<span style={tick_inline_style} class="tick-label {orientation} tick-{tick_side}">
|
|
324
|
+
{#if tick_format}
|
|
325
|
+
{#if tick_format.startsWith(`%`)}
|
|
326
|
+
{timeFormat(tick_format)(new Date(tick_label))}
|
|
327
|
+
{:else}
|
|
328
|
+
{format(tick_format)(tick_label)}
|
|
329
|
+
{/if}
|
|
330
|
+
{:else}
|
|
331
|
+
{format_num(tick_label)}
|
|
332
|
+
{/if}
|
|
333
|
+
</span>
|
|
334
|
+
{/each}
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<style>
|
|
339
|
+
div.colorbar {
|
|
340
|
+
display: flex;
|
|
341
|
+
box-sizing: border-box;
|
|
342
|
+
place-items: center;
|
|
343
|
+
/* Reduced default gap */
|
|
344
|
+
gap: var(--cbar-gap, 0);
|
|
345
|
+
margin: var(--cbar-margin);
|
|
346
|
+
padding: var(--cbar-padding);
|
|
347
|
+
width: var(--cbar-width, auto);
|
|
348
|
+
font-size: var(--cbar-font-size, 9pt);
|
|
349
|
+
/* align-items based on title side for vertical layout */
|
|
350
|
+
align-items: var(--cbar-wrapper-align-items);
|
|
351
|
+
}
|
|
352
|
+
/* color gradient bar */
|
|
353
|
+
div.bar {
|
|
354
|
+
position: relative;
|
|
355
|
+
border-radius: var(--cbar-border-radius, 2pt);
|
|
356
|
+
/* Use CSS variables set inline */
|
|
357
|
+
width: var(--cbar-width);
|
|
358
|
+
height: var(--cbar-height);
|
|
359
|
+
}
|
|
360
|
+
/* label text */
|
|
361
|
+
span.label {
|
|
362
|
+
text-align: center;
|
|
363
|
+
padding: var(--cbar-label-padding, 0 5px);
|
|
364
|
+
transform: var(--cbar-label-transform);
|
|
365
|
+
/* Ensure vertical labels are centered within their allocated space */
|
|
366
|
+
display: var(--cbar-label-display);
|
|
367
|
+
align-items: center;
|
|
368
|
+
justify-content: center;
|
|
369
|
+
}
|
|
370
|
+
span.tick-label {
|
|
371
|
+
position: absolute;
|
|
372
|
+
font-weight: var(--cbar-tick-label-font-weight, lighter);
|
|
373
|
+
font-size: var(--cbar-tick-label-font-size, --cbar-font-size);
|
|
374
|
+
/* text color is set dynamically/inline for inside ticks */
|
|
375
|
+
color: var(--cbar-tick-label-color, initial);
|
|
376
|
+
background: var(--cbar-tick-label-bg);
|
|
377
|
+
padding: var(--cbar-tick-label-padding, 0 2px);
|
|
378
|
+
white-space: nowrap;
|
|
379
|
+
}
|
|
380
|
+
/* --- Horizontal Ticks --- */
|
|
381
|
+
.tick-label.horizontal {
|
|
382
|
+
transform: translateX(-50%); /* Center horizontally by default */
|
|
383
|
+
}
|
|
384
|
+
.tick-label.horizontal.tick-primary {
|
|
385
|
+
top: 100%; /* Position below bar */
|
|
386
|
+
margin-top: var(--cbar-tick-offset, 0);
|
|
387
|
+
}
|
|
388
|
+
.tick-label.horizontal.tick-secondary {
|
|
389
|
+
bottom: 100%; /* Position above bar */
|
|
390
|
+
margin-bottom: var(--cbar-tick-offset, 0);
|
|
391
|
+
}
|
|
392
|
+
.tick-label.horizontal.tick-inside {
|
|
393
|
+
top: 50%; /* Center vertically */
|
|
394
|
+
transform: translate(-50%, -50%); /* Center horizontally and vertically */
|
|
395
|
+
margin: 0; /* No extra margin for inside */
|
|
396
|
+
}
|
|
397
|
+
/* --- Vertical Ticks --- */
|
|
398
|
+
.tick-label.vertical {
|
|
399
|
+
transform: translateY(-50%); /* Center vertically by default */
|
|
400
|
+
}
|
|
401
|
+
.tick-label.vertical.tick-primary {
|
|
402
|
+
left: 100%; /* Position right of bar */
|
|
403
|
+
padding-left: var(--cbar-tick-offset, 0);
|
|
404
|
+
}
|
|
405
|
+
.tick-label.vertical.tick-secondary {
|
|
406
|
+
right: 100%; /* Position left of bar */
|
|
407
|
+
padding-right: var(--cbar-tick-offset, 0);
|
|
408
|
+
}
|
|
409
|
+
.tick-label.vertical.tick-inside {
|
|
410
|
+
left: 50%; /* Center horizontally */
|
|
411
|
+
transform: translate(-50%, -50%); /* Center horizontally and vertically */
|
|
412
|
+
padding: 0; /* No extra padding for inside */
|
|
413
|
+
}
|
|
414
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
title?: string | null;
|
|
3
|
+
color_scale?: ((x: number) => string) | string | null;
|
|
4
|
+
title_side?: `left` | `right` | `top` | `bottom`;
|
|
5
|
+
style?: string | null;
|
|
6
|
+
title_style?: string | null;
|
|
7
|
+
wrapper_style?: string | null;
|
|
8
|
+
tick_labels?: (string | number)[] | number;
|
|
9
|
+
tick_format?: string;
|
|
10
|
+
range?: [number, number];
|
|
11
|
+
tick_side?: `primary` | `secondary` | `inside`;
|
|
12
|
+
orientation?: `horizontal` | `vertical`;
|
|
13
|
+
snap_ticks?: boolean;
|
|
14
|
+
steps?: number;
|
|
15
|
+
nice_range?: [number, number];
|
|
16
|
+
scale_type?: `linear` | `log`;
|
|
17
|
+
color_scale_fn?: (value: number) => string;
|
|
18
|
+
color_scale_domain?: [number, number];
|
|
19
|
+
}
|
|
20
|
+
declare const ColorBar: import("svelte").Component<Props, {}, "color_scale" | "tick_labels" | "nice_range">;
|
|
21
|
+
type ColorBar = ReturnType<typeof ColorBar>;
|
|
22
|
+
export default ColorBar;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">import { ColorBar } from '..';
|
|
2
|
+
import * as d3_sc from 'd3-scale-chromatic';
|
|
3
|
+
import Select from 'svelte-multiselect';
|
|
4
|
+
let { options = Object.keys(d3_sc).filter((key) => key.startsWith(`interpolate`)), value = $bindable(``), selected = $bindable([``]), minSelect = 0, placeholder = `Select a color scale`, colorbar = {}, ...rest } = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<Select
|
|
8
|
+
{options}
|
|
9
|
+
maxSelect={1}
|
|
10
|
+
{minSelect}
|
|
11
|
+
bind:value
|
|
12
|
+
bind:selected
|
|
13
|
+
{placeholder}
|
|
14
|
+
liOptionStyle="padding: 3pt 6pt;"
|
|
15
|
+
liSelectedStyle="width: 100%;"
|
|
16
|
+
ulSelectedStyle="display: contents;"
|
|
17
|
+
inputStyle="min-width: 0;"
|
|
18
|
+
{...rest}
|
|
19
|
+
>
|
|
20
|
+
{#snippet children({ option }: { option: D3InterpolateName })}
|
|
21
|
+
<ColorBar
|
|
22
|
+
title={option.replace(/^interpolate/, ``)}
|
|
23
|
+
color_scale={option}
|
|
24
|
+
tick_labels={0}
|
|
25
|
+
title_side="left"
|
|
26
|
+
wrapper_style="width: 100%;"
|
|
27
|
+
title_style="width: 6em; font-size: 1.2em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left;"
|
|
28
|
+
{...colorbar}
|
|
29
|
+
/>
|
|
30
|
+
{/snippet}
|
|
31
|
+
</Select>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ColorBar } from '..';
|
|
2
|
+
import type { ComponentProps } from 'svelte';
|
|
3
|
+
import type { D3InterpolateName } from '../colors';
|
|
4
|
+
interface Props {
|
|
5
|
+
options?: D3InterpolateName[];
|
|
6
|
+
value?: string | null;
|
|
7
|
+
selected?: string[];
|
|
8
|
+
minSelect?: number;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
colorbar?: ComponentProps<typeof ColorBar>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
declare const ColorScaleSelect: import("svelte").Component<Props, {}, "value" | "selected">;
|
|
14
|
+
type ColorScaleSelect = ReturnType<typeof ColorScaleSelect>;
|
|
15
|
+
export default ColorScaleSelect;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">import { element_data, format_num, ScatterPlot } from '..';
|
|
2
|
+
import { selected } from '../state.svelte';
|
|
3
|
+
let { y, x_label = `Atomic Number`, y_label = ``, y_unit = ``, tooltip_point = $bindable(null), hovered = $bindable(false), y_format = `~s`, ...rest } = $props();
|
|
4
|
+
// update tooltip on hover element tile
|
|
5
|
+
$effect.pre(() => {
|
|
6
|
+
if (selected.element?.number && !hovered) {
|
|
7
|
+
tooltip_point = {
|
|
8
|
+
x: selected.element.number,
|
|
9
|
+
y: y[selected.element.number - 1],
|
|
10
|
+
series_idx: 0,
|
|
11
|
+
point_idx: selected.element.number - 1,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<ScatterPlot
|
|
18
|
+
series={[
|
|
19
|
+
{
|
|
20
|
+
x: [...Array(y.length + 1).keys()].slice(1),
|
|
21
|
+
y,
|
|
22
|
+
color_values: y,
|
|
23
|
+
point_style: { radius: 4 },
|
|
24
|
+
},
|
|
25
|
+
]}
|
|
26
|
+
bind:tooltip_point
|
|
27
|
+
bind:hovered
|
|
28
|
+
{x_label}
|
|
29
|
+
{y_label}
|
|
30
|
+
{y_format}
|
|
31
|
+
color_bar={null}
|
|
32
|
+
{...rest}
|
|
33
|
+
>
|
|
34
|
+
{#snippet tooltip({ x, y })}
|
|
35
|
+
<strong>{x} - {element_data[x - 1]?.name}</strong><br />
|
|
36
|
+
{y_label} = {format_num(y, y_format)}{y_unit ?? ``}
|
|
37
|
+
{/snippet}
|
|
38
|
+
</ScatterPlot>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type InternalPoint } from '..';
|
|
2
|
+
interface Props {
|
|
3
|
+
y: number[];
|
|
4
|
+
x_label?: string;
|
|
5
|
+
y_label?: string;
|
|
6
|
+
y_unit?: string | null;
|
|
7
|
+
tooltip_point?: InternalPoint | null;
|
|
8
|
+
hovered?: boolean;
|
|
9
|
+
y_format?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
declare const ElementScatter: import("svelte").Component<Props, {}, "hovered" | "tooltip_point">;
|
|
13
|
+
type ElementScatter = ReturnType<typeof ElementScatter>;
|
|
14
|
+
export default ElementScatter;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">import { extent, min } from 'd3-array';
|
|
2
|
+
import { interpolatePath } from 'd3-interpolate-path';
|
|
3
|
+
import { curveMonotoneX, line } from 'd3-shape';
|
|
4
|
+
import { linear } from 'svelte/easing';
|
|
5
|
+
import { Tween } from 'svelte/motion';
|
|
6
|
+
let { points, origin = [0, 0], line_color = `rgba(255, 255, 255, 0.5)`, line_width = 2, area_color = `rgba(255, 255, 255, 0.1)`, area_stroke = null, line_tween = {}, line_dash = null, ...rest } = $props();
|
|
7
|
+
const lineGenerator = line()
|
|
8
|
+
.x((point) => point[0])
|
|
9
|
+
.y((point) => point[1])
|
|
10
|
+
.curve(curveMonotoneX);
|
|
11
|
+
let [x_min, x_max] = $derived(extent(points.map((p) => p[0])));
|
|
12
|
+
let line_path = $derived(lineGenerator(points) ?? ``);
|
|
13
|
+
let ymin = $derived(origin[1] ?? min(points.map((p) => p[1])));
|
|
14
|
+
let area_path = $derived(line_path ? `${line_path}L${x_max},${ymin}L${x_min},${ymin}Z` : ``);
|
|
15
|
+
const default_tween = {
|
|
16
|
+
duration: 300,
|
|
17
|
+
easing: linear,
|
|
18
|
+
interpolate: interpolatePath,
|
|
19
|
+
};
|
|
20
|
+
const tweened_line = new Tween(``, { ...default_tween, ...line_tween });
|
|
21
|
+
const tweened_area = new Tween(``, { ...default_tween, ...line_tween });
|
|
22
|
+
$effect.pre(() => {
|
|
23
|
+
tweened_line.target = line_path;
|
|
24
|
+
tweened_area.target = area_path;
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<path
|
|
29
|
+
d={tweened_line.current}
|
|
30
|
+
stroke={line_color}
|
|
31
|
+
stroke-width={line_width}
|
|
32
|
+
stroke-dasharray={line_dash}
|
|
33
|
+
fill="none"
|
|
34
|
+
{...rest}
|
|
35
|
+
/>
|
|
36
|
+
<path d={tweened_area.current} fill={area_color} stroke={area_stroke} {...rest} />
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
path {
|
|
40
|
+
transition: var(--line-transition, all 0.2s);
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type TweenedOptions } from 'svelte/motion';
|
|
2
|
+
interface Props {
|
|
3
|
+
points: readonly [number, number][];
|
|
4
|
+
origin: [number, number];
|
|
5
|
+
line_color?: string;
|
|
6
|
+
line_width?: number;
|
|
7
|
+
area_color?: string;
|
|
8
|
+
area_stroke?: string | null;
|
|
9
|
+
line_tween?: TweenedOptions<string>;
|
|
10
|
+
line_dash?: string | null;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
declare const Line: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Line = ReturnType<typeof Line>;
|
|
15
|
+
export default Line;
|