matterviz 0.1.12 → 0.1.13
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/ContextMenu.svelte +1 -1
- package/dist/ContextMenu.svelte.d.ts +3 -3
- package/dist/DraggablePane.svelte +10 -5
- package/dist/DraggablePane.svelte.d.ts +3 -3
- package/dist/FilePicker.svelte.d.ts +3 -3
- package/dist/Icon.svelte.d.ts +3 -3
- package/dist/Nav.svelte +76 -31
- package/dist/SettingsSection.svelte.d.ts +3 -3
- package/dist/Spinner.svelte +18 -29
- package/dist/Spinner.svelte.d.ts +3 -3
- package/dist/app.css +4 -0
- package/dist/bands/Bands.svelte +216 -0
- package/dist/bands/Bands.svelte.d.ts +16 -0
- package/dist/bands/BandsAndDos.svelte +26 -0
- package/dist/bands/BandsAndDos.svelte.d.ts +16 -0
- package/dist/bands/Dos.svelte +124 -0
- package/dist/bands/Dos.svelte.d.ts +18 -0
- package/dist/bands/helpers.d.ts +11 -0
- package/dist/bands/helpers.js +195 -0
- package/dist/bands/index.d.ts +5 -0
- package/dist/bands/index.js +6 -0
- package/dist/bands/types.d.ts +54 -0
- package/dist/bands/types.js +2 -0
- package/dist/brillouin/BrillouinZone.svelte +373 -0
- package/dist/brillouin/BrillouinZone.svelte.d.ts +57 -0
- package/dist/brillouin/BrillouinZoneControls.svelte +88 -0
- package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +14 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte +129 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +115 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
- package/dist/brillouin/BrillouinZoneScene.svelte +180 -0
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +34 -0
- package/dist/brillouin/compute.d.ts +7 -0
- package/dist/brillouin/compute.js +215 -0
- package/dist/brillouin/index.d.ts +7 -0
- package/dist/brillouin/index.js +7 -0
- package/dist/brillouin/types.d.ts +23 -0
- package/dist/brillouin/types.js +1 -0
- package/dist/composition/BarChart.svelte.d.ts +3 -3
- package/dist/composition/BubbleChart.svelte +7 -6
- package/dist/composition/BubbleChart.svelte.d.ts +3 -3
- package/dist/composition/Composition.svelte.d.ts +3 -3
- package/dist/composition/PieChart.svelte +1 -1
- package/dist/composition/PieChart.svelte.d.ts +3 -3
- package/dist/composition/parse.d.ts +3 -3
- package/dist/coordination/CoordinationBarPlot.svelte +241 -0
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +29 -0
- package/dist/coordination/calc-coordination.d.ts +15 -0
- package/dist/coordination/calc-coordination.js +66 -0
- package/dist/coordination/index.d.ts +8 -0
- package/dist/coordination/index.js +7 -0
- package/dist/element/BohrAtom.svelte.d.ts +3 -3
- package/dist/element/ElementHeading.svelte.d.ts +3 -3
- package/dist/element/ElementPhoto.svelte.d.ts +3 -3
- package/dist/element/ElementStats.svelte.d.ts +3 -3
- package/dist/element/ElementTile.svelte +1 -1
- package/dist/element/ElementTile.svelte.d.ts +4 -4
- package/dist/element/Nucleus.svelte.d.ts +3 -3
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/io/export.d.ts +1 -6
- package/dist/io/export.js +12 -12
- package/dist/labels.d.ts +19 -0
- package/dist/labels.js +59 -0
- package/dist/math.d.ts +7 -2
- package/dist/math.js +24 -12
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +3 -3
- package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +3 -3
- package/dist/periodic-table/PropertySelect.svelte +1 -1
- package/dist/periodic-table/PropertySelect.svelte.d.ts +5 -4
- package/dist/periodic-table/TableInset.svelte.d.ts +3 -3
- package/dist/phase-diagram/PhaseDiagram2D.svelte +44 -107
- package/dist/phase-diagram/PhaseDiagram2D.svelte.d.ts +7 -27
- package/dist/phase-diagram/PhaseDiagram3D.svelte +67 -170
- package/dist/phase-diagram/PhaseDiagram3D.svelte.d.ts +7 -31
- package/dist/phase-diagram/PhaseDiagram4D.svelte +87 -145
- package/dist/phase-diagram/PhaseDiagram4D.svelte.d.ts +5 -28
- package/dist/phase-diagram/PhaseDiagramControls.svelte +9 -9
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +6 -6
- package/dist/phase-diagram/PhaseDiagramInfoPane.svelte +4 -4
- package/dist/phase-diagram/PhaseDiagramInfoPane.svelte.d.ts +5 -5
- package/dist/phase-diagram/PhaseDiagramStats.svelte +9 -16
- package/dist/phase-diagram/PhaseDiagramStats.svelte.d.ts +3 -3
- package/dist/phase-diagram/StructurePopup.svelte.d.ts +3 -3
- package/dist/phase-diagram/barycentric-coords.js +13 -24
- package/dist/phase-diagram/helpers.d.ts +12 -14
- package/dist/phase-diagram/helpers.js +70 -23
- package/dist/phase-diagram/index.d.ts +58 -0
- package/dist/phase-diagram/index.js +44 -0
- package/dist/phase-diagram/thermodynamics.js +2 -3
- package/dist/phase-diagram/types.d.ts +3 -8
- package/dist/plot/BarPlot.svelte +474 -240
- package/dist/plot/BarPlot.svelte.d.ts +16 -37
- package/dist/plot/BarPlotControls.svelte +33 -201
- package/dist/plot/BarPlotControls.svelte.d.ts +11 -22
- package/dist/plot/ColorBar.svelte.d.ts +3 -3
- package/dist/plot/ColorScaleSelect.svelte +1 -1
- package/dist/plot/ColorScaleSelect.svelte.d.ts +3 -3
- package/dist/plot/ElementScatter.svelte +5 -5
- package/dist/plot/ElementScatter.svelte.d.ts +8 -8
- package/dist/plot/Histogram.svelte +318 -170
- package/dist/plot/Histogram.svelte.d.ts +21 -41
- package/dist/plot/HistogramControls.svelte +115 -347
- package/dist/plot/HistogramControls.svelte.d.ts +13 -28
- package/dist/plot/Line.svelte +1 -1
- package/dist/plot/Line.svelte.d.ts +4 -4
- package/dist/plot/PlotControls.svelte +280 -0
- package/dist/plot/PlotControls.svelte.d.ts +4 -0
- package/dist/plot/PlotLegend.svelte +9 -24
- package/dist/plot/PlotLegend.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot.svelte +536 -571
- package/dist/plot/ScatterPlot.svelte.d.ts +18 -90
- package/dist/plot/ScatterPlotControls.svelte +108 -316
- package/dist/plot/ScatterPlotControls.svelte.d.ts +11 -38
- package/dist/plot/ScatterPoint.svelte +1 -1
- package/dist/plot/ScatterPoint.svelte.d.ts +5 -5
- package/dist/plot/SpacegroupBarPlot.svelte +235 -0
- package/dist/plot/SpacegroupBarPlot.svelte.d.ts +9 -0
- package/dist/plot/data-transform.d.ts +2 -2
- package/dist/plot/data-transform.js +1 -5
- package/dist/plot/index.d.ts +12 -200
- package/dist/plot/index.js +14 -6
- package/dist/plot/layout.d.ts +10 -16
- package/dist/plot/layout.js +40 -25
- package/dist/plot/scales.d.ts +7 -8
- package/dist/plot/scales.js +22 -7
- package/dist/plot/types.d.ts +303 -0
- package/dist/plot/types.js +1 -0
- package/dist/rdf/RdfPlot.svelte +156 -0
- package/dist/rdf/RdfPlot.svelte.d.ts +20 -0
- package/dist/rdf/calc-rdf.d.ts +4 -0
- package/dist/rdf/calc-rdf.js +107 -0
- package/dist/rdf/index.d.ts +22 -0
- package/dist/rdf/index.js +2 -0
- package/dist/settings.d.ts +86 -75
- package/dist/settings.js +257 -155
- package/dist/structure/Bond.svelte +126 -110
- package/dist/structure/Bond.svelte.d.ts +7 -20
- package/dist/structure/CanvasTooltip.svelte.d.ts +4 -4
- package/dist/structure/Cylinder.svelte +29 -0
- package/dist/structure/Cylinder.svelte.d.ts +10 -0
- package/dist/structure/Lattice.svelte +1 -2
- package/dist/structure/Lattice.svelte.d.ts +3 -3
- package/dist/structure/Structure.svelte +23 -8
- package/dist/structure/Structure.svelte.d.ts +10 -7
- package/dist/structure/StructureControls.svelte +25 -134
- package/dist/structure/StructureControls.svelte.d.ts +4 -9
- package/dist/structure/StructureExportPane.svelte +188 -0
- package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
- package/dist/structure/StructureInfoPane.svelte.d.ts +3 -3
- package/dist/structure/StructureLegend.svelte +91 -27
- package/dist/structure/StructureLegend.svelte.d.ts +5 -4
- package/dist/structure/StructureScene.svelte +93 -100
- package/dist/structure/StructureScene.svelte.d.ts +15 -12
- package/dist/structure/Vector.svelte +19 -25
- package/dist/structure/Vector.svelte.d.ts +3 -3
- package/dist/structure/bonding.d.ts +16 -14
- package/dist/structure/bonding.js +207 -216
- package/dist/structure/export.d.ts +3 -0
- package/dist/structure/export.js +120 -0
- package/dist/structure/index.d.ts +15 -1
- package/dist/structure/index.js +2 -0
- package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -3
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +1 -0
- package/dist/symmetry/spacegroups.d.ts +9 -0
- package/dist/symmetry/spacegroups.js +397 -0
- package/dist/theme/ThemeControl.svelte.d.ts +3 -3
- package/dist/theme/themes.js +2 -16
- package/dist/trajectory/Trajectory.svelte +52 -78
- package/dist/trajectory/Trajectory.svelte.d.ts +5 -5
- package/dist/trajectory/TrajectoryError.svelte.d.ts +3 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +1 -1
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +5 -5
- package/dist/trajectory/TrajectoryInfoPane.svelte +1 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -3
- package/dist/trajectory/extract.js +2 -1
- package/dist/trajectory/index.d.ts +1 -1
- package/dist/trajectory/index.js +1 -1
- package/dist/trajectory/parse.js +21 -14
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -0
- package/dist/xrd/XrdPlot.svelte +14 -11
- package/dist/xrd/XrdPlot.svelte.d.ts +6 -5
- package/package.json +20 -12
- package/readme.md +1 -1
- package/dist/material/MaterialCard.svelte +0 -123
- package/dist/material/MaterialCard.svelte.d.ts +0 -9
- package/dist/material/index.d.ts +0 -1
- package/dist/material/index.js +0 -1
- package/dist/plot/formatting.d.ts +0 -7
- package/dist/plot/formatting.js +0 -53
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<script lang="ts">import { DraggablePane, format_num, Lattice, SettingsSection } from '..';
|
|
2
2
|
import { axis_colors, element_color_schemes } from '../colors';
|
|
3
|
-
import { export_canvas_as_png } from '../io/export';
|
|
4
3
|
import { to_degrees, to_radians } from '../math';
|
|
5
4
|
import { DEFAULTS, SETTINGS_CONFIG } from '../settings';
|
|
6
5
|
import { StructureScene } from './';
|
|
7
|
-
import * as exports from './export';
|
|
8
6
|
import { is_valid_supercell_input } from './supercell';
|
|
9
7
|
import Select from 'svelte-multiselect';
|
|
10
8
|
import { tooltip } from 'svelte-multiselect/attachments';
|
|
@@ -15,7 +13,7 @@ let { controls_open = $bindable(false), scene_props = $bindable({}), lattice_pro
|
|
|
15
13
|
cell_surface_color: DEFAULTS.structure.cell_surface_color,
|
|
16
14
|
cell_surface_opacity: DEFAULTS.structure.cell_surface_opacity,
|
|
17
15
|
cell_edge_width: DEFAULTS.structure.cell_edge_width,
|
|
18
|
-
}), show_image_atoms = $bindable(DEFAULTS.structure.show_image_atoms), supercell_scaling = $bindable(`1x1x1`), background_color = $bindable(undefined), background_opacity = $bindable(DEFAULTS.background_opacity), color_scheme = $bindable(DEFAULTS.color_scheme), structure = undefined,
|
|
16
|
+
}), show_image_atoms = $bindable(DEFAULTS.structure.show_image_atoms), supercell_scaling = $bindable(`1x1x1`), background_color = $bindable(undefined), background_opacity = $bindable(DEFAULTS.background_opacity), color_scheme = $bindable(DEFAULTS.color_scheme), structure = undefined, pane_props = $bindable({}), toggle_props = $bindable({}), ...rest } = $props();
|
|
19
17
|
// Color scheme selection state
|
|
20
18
|
let color_scheme_selected = $state([color_scheme]);
|
|
21
19
|
$effect(() => {
|
|
@@ -34,16 +32,6 @@ $effect(() => {
|
|
|
34
32
|
});
|
|
35
33
|
// Ensure site_label_offset is always available
|
|
36
34
|
scene_props.site_label_offset ??= [...DEFAULTS.structure.site_label_offset];
|
|
37
|
-
// Copy button feedback state
|
|
38
|
-
let copy_status = $state({ json: false, xyz: false, cif: false, poscar: false });
|
|
39
|
-
// Dynamic button text based on copy status
|
|
40
|
-
const copy_confirm = `✅`;
|
|
41
|
-
const export_formats = [
|
|
42
|
-
{ label: `JSON`, format: `json` },
|
|
43
|
-
{ label: `XYZ`, format: `xyz` },
|
|
44
|
-
{ label: `CIF`, format: `cif` },
|
|
45
|
-
{ label: `POSCAR`, format: `poscar` },
|
|
46
|
-
];
|
|
47
35
|
// Detect if structure has force data
|
|
48
36
|
let has_forces = $derived(structure?.sites?.some((site) => site.properties?.force && Array.isArray(site.properties.force)) ?? false);
|
|
49
37
|
// Detect if structure has lattice (can create supercells)
|
|
@@ -80,55 +68,13 @@ function get_representative_colors(scheme_name) {
|
|
|
80
68
|
.map((el) => scheme[el] || scheme.H || `#cccccc`)
|
|
81
69
|
.filter(Boolean);
|
|
82
70
|
}
|
|
83
|
-
// Helper function to export structure to file
|
|
84
|
-
function export_structure(format) {
|
|
85
|
-
if (!structure)
|
|
86
|
-
return;
|
|
87
|
-
const export_fns = {
|
|
88
|
-
json: exports.export_structure_as_json,
|
|
89
|
-
xyz: exports.export_structure_as_xyz,
|
|
90
|
-
cif: exports.export_structure_as_cif,
|
|
91
|
-
poscar: exports.export_structure_as_poscar,
|
|
92
|
-
};
|
|
93
|
-
export_fns[format](structure);
|
|
94
|
-
}
|
|
95
|
-
// Handle clipboard copy with user feedback
|
|
96
|
-
async function handle_copy(format) {
|
|
97
|
-
if (!structure) {
|
|
98
|
-
console.warn(`No structure available for copying`);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
let content;
|
|
103
|
-
if (format === `json`)
|
|
104
|
-
content = exports.structure_to_json_str(structure);
|
|
105
|
-
else if (format === `xyz`)
|
|
106
|
-
content = exports.structure_to_xyz_str(structure);
|
|
107
|
-
else if (format === `cif`)
|
|
108
|
-
content = exports.structure_to_cif_str(structure);
|
|
109
|
-
else if (format === `poscar`) {
|
|
110
|
-
content = exports.structure_to_poscar_str(structure);
|
|
111
|
-
}
|
|
112
|
-
else
|
|
113
|
-
throw new Error(`Invalid format: ${format}`);
|
|
114
|
-
await navigator.clipboard.writeText(content);
|
|
115
|
-
// Show temporary feedback in button text
|
|
116
|
-
copy_status[format] = true;
|
|
117
|
-
setTimeout(() => {
|
|
118
|
-
copy_status[format] = false;
|
|
119
|
-
}, 1000);
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
console.error(`Failed to copy ${format.toUpperCase()} to clipboard`, error);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
71
|
</script>
|
|
126
72
|
|
|
127
73
|
<DraggablePane
|
|
128
74
|
bind:show={controls_open}
|
|
129
75
|
pane_props={{ ...pane_props, class: `controls-pane ${pane_props?.class ?? ``}` }}
|
|
130
76
|
toggle_props={{
|
|
131
|
-
title:
|
|
77
|
+
title: controls_open ? `` : `Structure controls`,
|
|
132
78
|
...toggle_props,
|
|
133
79
|
class: `structure-controls-toggle ${toggle_props?.class ?? ``}`,
|
|
134
80
|
}}
|
|
@@ -207,66 +153,16 @@ async function handle_copy(format) {
|
|
|
207
153
|
>
|
|
208
154
|
Bonds:
|
|
209
155
|
<select bind:value={scene_props.show_bonds}>
|
|
210
|
-
{#each SETTINGS_CONFIG.structure.show_bonds.enum ??
|
|
211
|
-
|
|
156
|
+
{#each Object.entries(SETTINGS_CONFIG.structure.show_bonds.enum ?? {}) as
|
|
157
|
+
[value, label]
|
|
158
|
+
(value)
|
|
159
|
+
}
|
|
160
|
+
<option {value}>{label}</option>
|
|
212
161
|
{/each}
|
|
213
162
|
</select>
|
|
214
163
|
</label>
|
|
215
164
|
</SettingsSection>
|
|
216
165
|
|
|
217
|
-
<h4>Export</h4>
|
|
218
|
-
<div class="export-buttons">
|
|
219
|
-
{#each export_formats as { label, format } (format)}
|
|
220
|
-
<div style="display: flex; align-items: center; gap: 4pt">
|
|
221
|
-
{label}
|
|
222
|
-
<button
|
|
223
|
-
type="button"
|
|
224
|
-
onclick={() => export_structure(format)}
|
|
225
|
-
title="Download {label}"
|
|
226
|
-
>
|
|
227
|
-
⬇
|
|
228
|
-
</button>
|
|
229
|
-
<button
|
|
230
|
-
type="button"
|
|
231
|
-
onclick={() => handle_copy(format)}
|
|
232
|
-
title="Copy {label} to clipboard"
|
|
233
|
-
>
|
|
234
|
-
{copy_status[format] ? copy_confirm : `📋`}
|
|
235
|
-
</button>
|
|
236
|
-
</div>
|
|
237
|
-
{/each}
|
|
238
|
-
<label>
|
|
239
|
-
PNG
|
|
240
|
-
<button
|
|
241
|
-
type="button"
|
|
242
|
-
disabled={!wrapper?.querySelector(`canvas`)}
|
|
243
|
-
onclick={() => {
|
|
244
|
-
const canvas = wrapper?.querySelector(`canvas`) as HTMLCanvasElement
|
|
245
|
-
if (canvas) {
|
|
246
|
-
export_canvas_as_png(
|
|
247
|
-
canvas,
|
|
248
|
-
structure,
|
|
249
|
-
png_dpi,
|
|
250
|
-
scene,
|
|
251
|
-
camera,
|
|
252
|
-
)
|
|
253
|
-
} else console.warn(`Canvas element not found for PNG export`)
|
|
254
|
-
}}
|
|
255
|
-
title="PNG ({png_dpi} DPI)"
|
|
256
|
-
>
|
|
257
|
-
⬇
|
|
258
|
-
</button>
|
|
259
|
-
(DPI: <input
|
|
260
|
-
type="number"
|
|
261
|
-
min={50}
|
|
262
|
-
max={500}
|
|
263
|
-
bind:value={png_dpi}
|
|
264
|
-
title="Export resolution in dots per inch"
|
|
265
|
-
style="margin: 0 0 0 2pt"
|
|
266
|
-
/>)
|
|
267
|
-
</label>
|
|
268
|
-
</div>
|
|
269
|
-
|
|
270
166
|
<SettingsSection
|
|
271
167
|
title="Camera"
|
|
272
168
|
current_values={{
|
|
@@ -297,8 +193,13 @@ async function handle_copy(format) {
|
|
|
297
193
|
Projection
|
|
298
194
|
</span>
|
|
299
195
|
<select bind:value={scene_props.camera_projection}>
|
|
300
|
-
{#each
|
|
301
|
-
|
|
196
|
+
{#each Object.entries(
|
|
197
|
+
SETTINGS_CONFIG.structure.camera_projection.enum ?? {},
|
|
198
|
+
) as
|
|
199
|
+
[value, label]
|
|
200
|
+
(value)
|
|
201
|
+
}
|
|
202
|
+
<option {value}>{label}</option>
|
|
302
203
|
{/each}
|
|
303
204
|
</select>
|
|
304
205
|
</label>
|
|
@@ -830,19 +731,22 @@ async function handle_copy(format) {
|
|
|
830
731
|
}}
|
|
831
732
|
>
|
|
832
733
|
<label>
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
734
|
+
Strategy <select bind:value={scene_props.bonding_strategy}>
|
|
735
|
+
{#each Object.entries(
|
|
736
|
+
SETTINGS_CONFIG.structure.bonding_strategy.enum ?? {},
|
|
737
|
+
) as
|
|
738
|
+
[value, label]
|
|
739
|
+
(value)
|
|
740
|
+
}
|
|
741
|
+
<option {value}>{label}</option>
|
|
742
|
+
{/each}
|
|
838
743
|
</select>
|
|
839
744
|
</label>
|
|
840
745
|
<label>
|
|
841
|
-
|
|
842
|
-
<input type="color" bind:value={scene_props.bond_color} />
|
|
746
|
+
Color <input type="color" bind:value={scene_props.bond_color} />
|
|
843
747
|
</label>
|
|
844
748
|
<label>
|
|
845
|
-
|
|
749
|
+
Thickness
|
|
846
750
|
<input
|
|
847
751
|
type="number"
|
|
848
752
|
min={0.05}
|
|
@@ -863,19 +767,6 @@ async function handle_copy(format) {
|
|
|
863
767
|
</DraggablePane>
|
|
864
768
|
|
|
865
769
|
<style>
|
|
866
|
-
.export-buttons {
|
|
867
|
-
display: flex;
|
|
868
|
-
flex-wrap: wrap;
|
|
869
|
-
gap: 1ex;
|
|
870
|
-
font-size: 0.95em;
|
|
871
|
-
}
|
|
872
|
-
.export-buttons button {
|
|
873
|
-
width: 1.6em;
|
|
874
|
-
height: 1.6em;
|
|
875
|
-
display: grid;
|
|
876
|
-
place-items: center;
|
|
877
|
-
padding: 0;
|
|
878
|
-
}
|
|
879
770
|
.rotation-axes {
|
|
880
771
|
display: flex;
|
|
881
772
|
gap: 9pt;
|
|
@@ -2,8 +2,7 @@ import type { AnyStructure } from '..';
|
|
|
2
2
|
import { DraggablePane, Lattice } from '..';
|
|
3
3
|
import { StructureScene } from './';
|
|
4
4
|
import type { ComponentProps } from 'svelte';
|
|
5
|
-
|
|
6
|
-
export interface Props extends Omit<ComponentProps<typeof DraggablePane>, `children`> {
|
|
5
|
+
type $$ComponentProps = Omit<ComponentProps<typeof DraggablePane>, `children`> & {
|
|
7
6
|
controls_open?: boolean;
|
|
8
7
|
scene_props?: ComponentProps<typeof StructureScene>;
|
|
9
8
|
lattice_props?: ComponentProps<typeof Lattice>;
|
|
@@ -12,14 +11,10 @@ export interface Props extends Omit<ComponentProps<typeof DraggablePane>, `child
|
|
|
12
11
|
background_color?: string;
|
|
13
12
|
background_opacity?: number;
|
|
14
13
|
color_scheme?: string;
|
|
15
|
-
structure?: AnyStructure
|
|
16
|
-
wrapper?: HTMLDivElement;
|
|
17
|
-
png_dpi?: number;
|
|
18
|
-
scene?: Scene;
|
|
19
|
-
camera?: Camera;
|
|
14
|
+
structure?: AnyStructure;
|
|
20
15
|
pane_props?: ComponentProps<typeof DraggablePane>[`pane_props`];
|
|
21
16
|
toggle_props?: ComponentProps<typeof DraggablePane>[`toggle_props`];
|
|
22
|
-
}
|
|
23
|
-
declare const StructureControls: import("svelte").Component
|
|
17
|
+
};
|
|
18
|
+
declare const StructureControls: import("svelte").Component<$$ComponentProps, {}, "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "controls_open" | "toggle_props" | "pane_props" | "scene_props" | "lattice_props" | "supercell_scaling">;
|
|
24
19
|
type StructureControls = ReturnType<typeof StructureControls>;
|
|
25
20
|
export default StructureControls;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script lang="ts">import { DraggablePane } from '..';
|
|
2
|
+
import { export_canvas_as_png } from '../io/export';
|
|
3
|
+
import * as exports from './export';
|
|
4
|
+
import { tooltip } from 'svelte-multiselect/attachments';
|
|
5
|
+
let { export_pane_open = $bindable(false), structure = undefined, wrapper = undefined, scene = undefined, camera = undefined, png_dpi = $bindable(150), pane_props = $bindable({}), toggle_props = $bindable({}), ...rest } = $props();
|
|
6
|
+
// Copy button feedback state
|
|
7
|
+
let copy_status = $state({ json: false, xyz: false, cif: false, poscar: false });
|
|
8
|
+
// Dynamic button text based on copy status
|
|
9
|
+
const copy_confirm = `✅`;
|
|
10
|
+
const text_export_formats = [
|
|
11
|
+
{ label: `JSON`, format: `json` },
|
|
12
|
+
{ label: `XYZ`, format: `xyz` },
|
|
13
|
+
{ label: `CIF`, format: `cif` },
|
|
14
|
+
{ label: `POSCAR`, format: `poscar` },
|
|
15
|
+
];
|
|
16
|
+
const model_3d_formats = [
|
|
17
|
+
{
|
|
18
|
+
label: `GLB`,
|
|
19
|
+
format: `glb`,
|
|
20
|
+
hint: `Export as GLB (binary GLTF) - preserves element colors and materials, ideal for visualization in Blender, Unity, web viewers`,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: `OBJ`,
|
|
24
|
+
format: `obj`,
|
|
25
|
+
hint: `Export as OBJ (Wavefront Object) - widely supported 3D format with material references, works in most 3D applications`,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
// Helper function to export structure to file
|
|
29
|
+
function export_structure(format) {
|
|
30
|
+
if (!structure)
|
|
31
|
+
return;
|
|
32
|
+
const export_fns = {
|
|
33
|
+
json: exports.export_structure_as_json,
|
|
34
|
+
xyz: exports.export_structure_as_xyz,
|
|
35
|
+
cif: exports.export_structure_as_cif,
|
|
36
|
+
poscar: exports.export_structure_as_poscar,
|
|
37
|
+
};
|
|
38
|
+
export_fns[format](structure);
|
|
39
|
+
}
|
|
40
|
+
// Handle clipboard copy with user feedback
|
|
41
|
+
async function handle_copy(format) {
|
|
42
|
+
if (!structure) {
|
|
43
|
+
console.warn(`No structure available for copying`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
let content;
|
|
48
|
+
if (format === `json`)
|
|
49
|
+
content = exports.structure_to_json_str(structure);
|
|
50
|
+
else if (format === `xyz`)
|
|
51
|
+
content = exports.structure_to_xyz_str(structure);
|
|
52
|
+
else if (format === `cif`)
|
|
53
|
+
content = exports.structure_to_cif_str(structure);
|
|
54
|
+
else if (format === `poscar`) {
|
|
55
|
+
content = exports.structure_to_poscar_str(structure);
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
throw new Error(`Invalid format: ${format}`);
|
|
59
|
+
await navigator.clipboard.writeText(content);
|
|
60
|
+
// Show temporary feedback in button text
|
|
61
|
+
copy_status[format] = true;
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
copy_status[format] = false;
|
|
64
|
+
}, 1000);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error(`Failed to copy ${format.toUpperCase()} to clipboard`, error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function handle_3d_export(format) {
|
|
71
|
+
if (!scene) {
|
|
72
|
+
console.warn(`No scene available for ${format.toUpperCase()} export`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (format === `glb`)
|
|
77
|
+
exports.export_structure_as_glb(scene, structure);
|
|
78
|
+
else if (format === `obj`)
|
|
79
|
+
exports.export_structure_as_obj(scene, structure);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`Failed to export ${format.toUpperCase()}:`, error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<DraggablePane
|
|
88
|
+
bind:show={export_pane_open}
|
|
89
|
+
open_icon="Cross"
|
|
90
|
+
closed_icon="Export"
|
|
91
|
+
pane_props={{ ...pane_props, class: `export-pane ${pane_props?.class ?? ``}` }}
|
|
92
|
+
toggle_props={{
|
|
93
|
+
title: export_pane_open ? `` : `Export Structure`,
|
|
94
|
+
...toggle_props,
|
|
95
|
+
class: `structure-export-toggle ${toggle_props?.class ?? ``}`,
|
|
96
|
+
}}
|
|
97
|
+
{...rest}
|
|
98
|
+
>
|
|
99
|
+
<h4>Export as text</h4>
|
|
100
|
+
<div class="export-buttons">
|
|
101
|
+
{#each text_export_formats as { label, format } (format)}
|
|
102
|
+
<div style="display: flex; align-items: center; gap: 4pt">
|
|
103
|
+
{label}
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
onclick={() => export_structure(format)}
|
|
107
|
+
title="Download {label}"
|
|
108
|
+
>
|
|
109
|
+
⬇
|
|
110
|
+
</button>
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
onclick={() => handle_copy(format)}
|
|
114
|
+
title="Copy {label} to clipboard"
|
|
115
|
+
>
|
|
116
|
+
{copy_status[format] ? copy_confirm : `📋`}
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
{/each}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<h4>Export as image</h4>
|
|
123
|
+
<div class="export-buttons">
|
|
124
|
+
<label>
|
|
125
|
+
PNG
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
disabled={!wrapper?.querySelector(`canvas`)}
|
|
129
|
+
onclick={() => {
|
|
130
|
+
const canvas = wrapper?.querySelector(`canvas`) as HTMLCanvasElement
|
|
131
|
+
if (canvas) {
|
|
132
|
+
export_canvas_as_png(
|
|
133
|
+
canvas,
|
|
134
|
+
structure,
|
|
135
|
+
png_dpi,
|
|
136
|
+
scene,
|
|
137
|
+
camera,
|
|
138
|
+
)
|
|
139
|
+
} else console.warn(`Canvas element not found for PNG export`)
|
|
140
|
+
}}
|
|
141
|
+
title="PNG ({png_dpi} DPI)"
|
|
142
|
+
>
|
|
143
|
+
⬇
|
|
144
|
+
</button>
|
|
145
|
+
(DPI: <input
|
|
146
|
+
type="number"
|
|
147
|
+
min={50}
|
|
148
|
+
max={500}
|
|
149
|
+
bind:value={png_dpi}
|
|
150
|
+
title="Export resolution in dots per inch"
|
|
151
|
+
style="margin: 0 0 0 2pt"
|
|
152
|
+
/>)
|
|
153
|
+
</label>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<h4>Export as 3D model</h4>
|
|
157
|
+
<div class="export-buttons">
|
|
158
|
+
{#each model_3d_formats as { label, format, hint } (format)}
|
|
159
|
+
<div style="display: flex; align-items: center; gap: 4pt">
|
|
160
|
+
{label}
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
onclick={() => handle_3d_export(format)}
|
|
164
|
+
disabled={!scene}
|
|
165
|
+
{@attach tooltip({ content: hint })}
|
|
166
|
+
>
|
|
167
|
+
⬇
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
{/each}
|
|
171
|
+
</div>
|
|
172
|
+
</DraggablePane>
|
|
173
|
+
|
|
174
|
+
<style>
|
|
175
|
+
.export-buttons {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-wrap: wrap;
|
|
178
|
+
gap: 9pt;
|
|
179
|
+
font-size: 0.95em;
|
|
180
|
+
}
|
|
181
|
+
.export-buttons button {
|
|
182
|
+
width: 1.9em;
|
|
183
|
+
height: 1.6em;
|
|
184
|
+
padding: 0;
|
|
185
|
+
margin: 0 0 0 4pt;
|
|
186
|
+
box-sizing: border-box;
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AnyStructure } from '..';
|
|
2
|
+
import { DraggablePane } from '..';
|
|
3
|
+
import type { ComponentProps } from 'svelte';
|
|
4
|
+
import type { Camera, Scene } from 'three';
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
export_pane_open?: boolean;
|
|
7
|
+
structure?: AnyStructure;
|
|
8
|
+
wrapper?: HTMLDivElement;
|
|
9
|
+
scene?: Scene;
|
|
10
|
+
camera?: Camera;
|
|
11
|
+
png_dpi?: number;
|
|
12
|
+
pane_props?: ComponentProps<typeof DraggablePane>[`pane_props`];
|
|
13
|
+
toggle_props?: ComponentProps<typeof DraggablePane>[`toggle_props`];
|
|
14
|
+
};
|
|
15
|
+
declare const StructureExportPane: import("svelte").Component<$$ComponentProps, {}, "toggle_props" | "pane_props" | "png_dpi" | "export_pane_open">;
|
|
16
|
+
type StructureExportPane = ReturnType<typeof StructureExportPane>;
|
|
17
|
+
export default StructureExportPane;
|
|
@@ -2,7 +2,7 @@ import type { AnyStructure } from '..';
|
|
|
2
2
|
import { DraggablePane } from '..';
|
|
3
3
|
import type { ComponentProps } from 'svelte';
|
|
4
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
-
|
|
5
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, `onclose`> & {
|
|
6
6
|
structure: AnyStructure;
|
|
7
7
|
pane_open?: boolean;
|
|
8
8
|
atom_count_thresholds?: [number, number];
|
|
@@ -10,7 +10,7 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, `onclose`> {
|
|
|
10
10
|
pane_props?: ComponentProps<typeof DraggablePane>[`pane_props`];
|
|
11
11
|
highlighted_sites?: number[];
|
|
12
12
|
selected_sites?: number[];
|
|
13
|
-
}
|
|
14
|
-
declare const StructureInfoPane: import("svelte").Component
|
|
13
|
+
};
|
|
14
|
+
declare const StructureInfoPane: import("svelte").Component<$$ComponentProps, {}, "toggle_props" | "pane_props" | "selected_sites" | "pane_open" | "highlighted_sites">;
|
|
15
15
|
type StructureInfoPane = ReturnType<typeof StructureInfoPane>;
|
|
16
16
|
export default StructureInfoPane;
|
|
@@ -2,36 +2,62 @@
|
|
|
2
2
|
import { contrast_color, default_element_colors } from '../colors';
|
|
3
3
|
import { colors } from '../state.svelte';
|
|
4
4
|
import { tooltip } from 'svelte-multiselect/attachments';
|
|
5
|
-
|
|
5
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
6
|
+
let { elements, elem_color_picker_title = `Double click to reset color`, labels = $bindable([]), amount_format = `.3~f`, show_amounts = true, get_element_label, hidden_elements = $bindable(new Set()), ...rest } = $props();
|
|
7
|
+
function toggle_element_visibility(element, event) {
|
|
8
|
+
event.preventDefault();
|
|
9
|
+
event.stopPropagation();
|
|
10
|
+
event.stopImmediatePropagation();
|
|
11
|
+
const new_hidden = new SvelteSet(hidden_elements);
|
|
12
|
+
if (new_hidden.has(element))
|
|
13
|
+
new_hidden.delete(element);
|
|
14
|
+
else
|
|
15
|
+
new_hidden.add(element);
|
|
16
|
+
hidden_elements = new_hidden;
|
|
17
|
+
}
|
|
6
18
|
</script>
|
|
7
19
|
|
|
8
20
|
<div {...rest} class="structure-legend {rest.class ?? ``}">
|
|
9
21
|
{#each Object.entries(elements) as [elem, amt], idx (elem + amt)}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
{@const is_hidden = hidden_elements.has(elem as ElementSymbol)}
|
|
23
|
+
<div class="legend-item">
|
|
24
|
+
<label
|
|
25
|
+
bind:this={labels[idx]}
|
|
26
|
+
title={element_data.find((el) => el.symbol == elem)?.name}
|
|
27
|
+
{@attach tooltip()}
|
|
28
|
+
style:background-color={colors.element[elem]}
|
|
29
|
+
class:hidden={is_hidden}
|
|
30
|
+
ondblclick={(event) => {
|
|
31
|
+
event.preventDefault()
|
|
32
|
+
colors.element[elem] = default_element_colors[elem]
|
|
33
|
+
}}
|
|
34
|
+
{@attach contrast_color()}
|
|
35
|
+
>
|
|
36
|
+
{#if get_element_label}
|
|
37
|
+
{get_element_label(elem, amt)}
|
|
38
|
+
{:else}
|
|
39
|
+
{elem}
|
|
40
|
+
{#if show_amounts}
|
|
41
|
+
<sub>{format_num(amt, amount_format)}</sub>
|
|
42
|
+
{/if}
|
|
27
43
|
{/if}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
<input
|
|
45
|
+
type="color"
|
|
46
|
+
bind:value={colors.element[elem]}
|
|
47
|
+
title={elem_color_picker_title}
|
|
48
|
+
/>
|
|
49
|
+
</label>
|
|
50
|
+
<button
|
|
51
|
+
class="toggle-visibility"
|
|
52
|
+
class:visible={is_hidden}
|
|
53
|
+
onclick={(event) => toggle_element_visibility(elem as ElementSymbol, event)}
|
|
54
|
+
title={is_hidden ? `Show ${elem} atoms` : `Hide ${elem} atoms`}
|
|
55
|
+
{@attach tooltip({ placement: `top` })}
|
|
56
|
+
type="button"
|
|
57
|
+
>
|
|
58
|
+
×
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
35
61
|
{/each}
|
|
36
62
|
</div>
|
|
37
63
|
|
|
@@ -41,22 +67,29 @@ let { elements, elem_color_picker_title = `Double click to reset color`, labels
|
|
|
41
67
|
position: absolute;
|
|
42
68
|
bottom: var(--struct-legend-bottom, clamp(4pt, 3cqmin, 8pt));
|
|
43
69
|
right: var(--struct-legend-right, clamp(4pt, 3cqmin, 8pt));
|
|
44
|
-
gap: var(--struct-legend-gap, clamp(
|
|
70
|
+
gap: var(--struct-legend-gap, clamp(3pt, 2cqmin, 7pt));
|
|
45
71
|
font-size: var(--struct-legend-font, clamp(8pt, 3cqmin, 14pt));
|
|
46
72
|
filter: var(--legend-filter, grayscale(10%) brightness(0.95) saturate(0.9));
|
|
47
73
|
z-index: var(--struct-legend-z-index, 1);
|
|
48
74
|
pointer-events: auto;
|
|
49
75
|
visibility: visible;
|
|
50
76
|
}
|
|
77
|
+
.structure-legend .legend-item {
|
|
78
|
+
position: relative;
|
|
79
|
+
display: inline-block;
|
|
80
|
+
}
|
|
51
81
|
.structure-legend label {
|
|
52
82
|
padding: var(--struct-legend-padding, 0 4pt);
|
|
53
83
|
border-radius: var(--struct-legend-radius, 3pt);
|
|
54
84
|
line-height: var(--struct-legend-line-height, 1.3);
|
|
55
|
-
position: relative;
|
|
56
85
|
display: inline-block;
|
|
57
86
|
cursor: pointer;
|
|
58
87
|
visibility: visible;
|
|
59
88
|
white-space: nowrap;
|
|
89
|
+
transition: opacity 0.2s ease;
|
|
90
|
+
}
|
|
91
|
+
.structure-legend label.hidden {
|
|
92
|
+
opacity: 0.4;
|
|
60
93
|
}
|
|
61
94
|
.structure-legend label input[type='color'] {
|
|
62
95
|
z-index: var(--struct-legend-input-z, 1);
|
|
@@ -66,6 +99,37 @@ let { elements, elem_color_picker_title = `Double click to reset color`, labels
|
|
|
66
99
|
top: 7pt;
|
|
67
100
|
left: 0;
|
|
68
101
|
}
|
|
102
|
+
.structure-legend button.toggle-visibility {
|
|
103
|
+
position: absolute;
|
|
104
|
+
top: -3px;
|
|
105
|
+
right: -7px;
|
|
106
|
+
width: 1em;
|
|
107
|
+
height: 1em;
|
|
108
|
+
padding: 0;
|
|
109
|
+
margin: 0;
|
|
110
|
+
border: none;
|
|
111
|
+
background: rgba(0, 0, 0, 0.5);
|
|
112
|
+
color: white;
|
|
113
|
+
border-radius: 50%;
|
|
114
|
+
font-size: 0.9em;
|
|
115
|
+
line-height: 0.9;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
opacity: 0;
|
|
121
|
+
transition: opacity 0.2s ease, background 0.2s ease, transform 0.1s ease;
|
|
122
|
+
z-index: 2;
|
|
123
|
+
pointer-events: auto;
|
|
124
|
+
}
|
|
125
|
+
.structure-legend button.toggle-visibility.visible,
|
|
126
|
+
.structure-legend .legend-item:hover button.toggle-visibility {
|
|
127
|
+
opacity: 1;
|
|
128
|
+
}
|
|
129
|
+
.structure-legend button.toggle-visibility:hover {
|
|
130
|
+
background: rgba(0, 0, 0, 0.8);
|
|
131
|
+
transform: scale(1.15);
|
|
132
|
+
}
|
|
69
133
|
.structure-legend sub {
|
|
70
134
|
font-size: 0.85em;
|
|
71
135
|
margin: 0 0 0 -4px;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type { CompositionType } from '..';
|
|
1
|
+
import type { CompositionType, ElementSymbol } from '..';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
|
|
3
|
+
type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
4
|
elements: CompositionType;
|
|
5
5
|
elem_color_picker_title?: string;
|
|
6
6
|
labels?: HTMLLabelElement[];
|
|
7
7
|
amount_format?: string;
|
|
8
8
|
show_amounts?: boolean;
|
|
9
9
|
get_element_label?: (element: string, amount: number) => string;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
hidden_elements?: Set<ElementSymbol>;
|
|
11
|
+
};
|
|
12
|
+
declare const StructureLegend: import("svelte").Component<$$ComponentProps, {}, "labels" | "hidden_elements">;
|
|
12
13
|
type StructureLegend = ReturnType<typeof StructureLegend>;
|
|
13
14
|
export default StructureLegend;
|