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.
Files changed (131) hide show
  1. package/dist/BohrAtom.svelte +105 -0
  2. package/dist/BohrAtom.svelte.d.ts +21 -0
  3. package/dist/ControlPanel.svelte +158 -0
  4. package/dist/ControlPanel.svelte.d.ts +18 -0
  5. package/dist/Icon.svelte +23 -0
  6. package/dist/Icon.svelte.d.ts +8 -0
  7. package/dist/InfoCard.svelte +79 -0
  8. package/dist/InfoCard.svelte.d.ts +23 -0
  9. package/dist/Nucleus.svelte +64 -0
  10. package/dist/Nucleus.svelte.d.ts +16 -0
  11. package/dist/Spinner.svelte +44 -0
  12. package/dist/Spinner.svelte.d.ts +7 -0
  13. package/dist/api.d.ts +6 -0
  14. package/dist/api.js +30 -0
  15. package/dist/colors/alloy-colors.json +111 -0
  16. package/dist/colors/dark-mode-colors.json +111 -0
  17. package/dist/colors/index.d.ts +26 -0
  18. package/dist/colors/index.js +72 -0
  19. package/dist/colors/jmol-colors.json +111 -0
  20. package/dist/colors/muted-colors.json +111 -0
  21. package/dist/colors/pastel-colors.json +111 -0
  22. package/dist/colors/vesta-colors.json +111 -0
  23. package/dist/composition/BarChart.svelte +260 -0
  24. package/dist/composition/BarChart.svelte.d.ts +33 -0
  25. package/dist/composition/BubbleChart.svelte +166 -0
  26. package/dist/composition/BubbleChart.svelte.d.ts +30 -0
  27. package/dist/composition/Composition.svelte +73 -0
  28. package/dist/composition/Composition.svelte.d.ts +27 -0
  29. package/dist/composition/PieChart.svelte +236 -0
  30. package/dist/composition/PieChart.svelte.d.ts +36 -0
  31. package/dist/composition/index.d.ts +5 -0
  32. package/dist/composition/index.js +5 -0
  33. package/dist/composition/parse.d.ts +14 -0
  34. package/dist/composition/parse.js +307 -0
  35. package/dist/element/ElementHeading.svelte +21 -0
  36. package/dist/element/ElementHeading.svelte.d.ts +8 -0
  37. package/dist/element/ElementPhoto.svelte +56 -0
  38. package/dist/element/ElementPhoto.svelte.d.ts +9 -0
  39. package/dist/element/ElementStats.svelte +73 -0
  40. package/dist/element/ElementStats.svelte.d.ts +8 -0
  41. package/dist/element/ElementTile.svelte +449 -0
  42. package/dist/element/ElementTile.svelte.d.ts +25 -0
  43. package/dist/element/data.d.ts +4958 -0
  44. package/dist/element/data.js +5628 -0
  45. package/dist/element/index.d.ts +4 -0
  46. package/dist/element/index.js +4 -0
  47. package/dist/icons.d.ts +435 -0
  48. package/dist/icons.js +435 -0
  49. package/dist/index.d.ts +82 -0
  50. package/dist/index.js +43 -0
  51. package/dist/io/decompress.d.ts +16 -0
  52. package/dist/io/decompress.js +78 -0
  53. package/dist/io/export.d.ts +9 -0
  54. package/dist/io/export.js +205 -0
  55. package/dist/io/parse.d.ts +53 -0
  56. package/dist/io/parse.js +747 -0
  57. package/dist/labels.d.ts +31 -0
  58. package/dist/labels.js +209 -0
  59. package/dist/material/MaterialCard.svelte +135 -0
  60. package/dist/material/MaterialCard.svelte.d.ts +10 -0
  61. package/dist/material/SymmetryCard.svelte +23 -0
  62. package/dist/material/SymmetryCard.svelte.d.ts +9 -0
  63. package/dist/material/index.d.ts +2 -0
  64. package/dist/material/index.js +2 -0
  65. package/dist/math.d.ts +24 -0
  66. package/dist/math.js +216 -0
  67. package/dist/periodic-table/PeriodicTable.svelte +284 -0
  68. package/dist/periodic-table/PeriodicTable.svelte.d.ts +50 -0
  69. package/dist/periodic-table/PropertySelect.svelte +20 -0
  70. package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
  71. package/dist/periodic-table/TableInset.svelte +18 -0
  72. package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
  73. package/dist/periodic-table/index.d.ts +9 -0
  74. package/dist/periodic-table/index.js +3 -0
  75. package/dist/plot/ColorBar.svelte +414 -0
  76. package/dist/plot/ColorBar.svelte.d.ts +22 -0
  77. package/dist/plot/ColorScaleSelect.svelte +31 -0
  78. package/dist/plot/ColorScaleSelect.svelte.d.ts +15 -0
  79. package/dist/plot/ElementScatter.svelte +38 -0
  80. package/dist/plot/ElementScatter.svelte.d.ts +14 -0
  81. package/dist/plot/Line.svelte +42 -0
  82. package/dist/plot/Line.svelte.d.ts +15 -0
  83. package/dist/plot/PlotLegend.svelte +206 -0
  84. package/dist/plot/PlotLegend.svelte.d.ts +18 -0
  85. package/dist/plot/ScatterPlot.svelte +1753 -0
  86. package/dist/plot/ScatterPlot.svelte.d.ts +114 -0
  87. package/dist/plot/ScatterPlotControls.svelte +505 -0
  88. package/dist/plot/ScatterPlotControls.svelte.d.ts +33 -0
  89. package/dist/plot/ScatterPoint.svelte +72 -0
  90. package/dist/plot/ScatterPoint.svelte.d.ts +17 -0
  91. package/dist/plot/index.d.ts +168 -0
  92. package/dist/plot/index.js +46 -0
  93. package/dist/state.svelte.d.ts +12 -0
  94. package/dist/state.svelte.js +11 -0
  95. package/dist/structure/Bond.svelte +68 -0
  96. package/dist/structure/Bond.svelte.d.ts +13 -0
  97. package/dist/structure/Lattice.svelte +115 -0
  98. package/dist/structure/Lattice.svelte.d.ts +15 -0
  99. package/dist/structure/Structure.svelte +298 -0
  100. package/dist/structure/Structure.svelte.d.ts +28 -0
  101. package/dist/structure/StructureCard.svelte +26 -0
  102. package/dist/structure/StructureCard.svelte.d.ts +9 -0
  103. package/dist/structure/StructureControls.svelte +383 -0
  104. package/dist/structure/StructureControls.svelte.d.ts +23 -0
  105. package/dist/structure/StructureLegend.svelte +130 -0
  106. package/dist/structure/StructureLegend.svelte.d.ts +17 -0
  107. package/dist/structure/StructureScene.svelte +331 -0
  108. package/dist/structure/StructureScene.svelte.d.ts +47 -0
  109. package/dist/structure/bonding.d.ts +16 -0
  110. package/dist/structure/bonding.js +150 -0
  111. package/dist/structure/index.d.ts +98 -0
  112. package/dist/structure/index.js +114 -0
  113. package/dist/structure/pbc.d.ts +6 -0
  114. package/dist/structure/pbc.js +72 -0
  115. package/dist/trajectory/Sidebar.svelte +412 -0
  116. package/dist/trajectory/Sidebar.svelte.d.ts +14 -0
  117. package/dist/trajectory/Trajectory.svelte +1084 -0
  118. package/dist/trajectory/Trajectory.svelte.d.ts +49 -0
  119. package/dist/trajectory/TrajectoryError.svelte +120 -0
  120. package/dist/trajectory/TrajectoryError.svelte.d.ts +12 -0
  121. package/dist/trajectory/extract.d.ts +5 -0
  122. package/dist/trajectory/extract.js +157 -0
  123. package/dist/trajectory/index.d.ts +16 -0
  124. package/dist/trajectory/index.js +49 -0
  125. package/dist/trajectory/parse.d.ts +13 -0
  126. package/dist/trajectory/parse.js +1093 -0
  127. package/dist/trajectory/plotting.d.ts +12 -0
  128. package/dist/trajectory/plotting.js +148 -0
  129. package/license +21 -0
  130. package/package.json +131 -0
  131. 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;