matterviz 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EmptyState.svelte +10 -4
- package/dist/FilePicker.svelte +48 -60
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +68 -0
- package/dist/brillouin/BrillouinZone.svelte +90 -91
- package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
- package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +13 -14
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- package/dist/composition/BarChart.svelte +30 -26
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +10 -19
- package/dist/composition/Formula.svelte +20 -27
- package/dist/composition/FormulaFilter.svelte +115 -184
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +7 -9
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -2
- package/dist/composition/parse.js +61 -115
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +17 -26
- package/dist/convex-hull/ConvexHull.svelte +12 -15
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +93 -94
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +127 -186
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +131 -180
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
- package/dist/convex-hull/ConvexHullControls.svelte +46 -48
- package/dist/convex-hull/ConvexHullStats.svelte +193 -195
- package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +18 -22
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +37 -43
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +37 -44
- package/dist/coordination/CoordinationBarPlot.svelte +49 -53
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/coordination/calc-coordination.d.ts +1 -1
- package/dist/coordination/calc-coordination.js +28 -25
- package/dist/element/BohrAtom.svelte +5 -9
- package/dist/element/ElementHeading.svelte +4 -1
- package/dist/element/ElementPhoto.svelte +11 -9
- package/dist/element/ElementStats.svelte +12 -10
- package/dist/element/ElementTile.svelte +44 -67
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +3 -2
- package/dist/element/data.js +5 -1
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +2 -5
- package/dist/element/index.js +4 -19
- package/dist/feedback/DragOverlay.svelte +5 -5
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
- package/dist/feedback/StatusMessage.svelte +7 -4
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +35 -44
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +25 -58
- package/dist/fermi-surface/parse.js +54 -88
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/index.d.ts +5 -0
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +11 -7
- package/dist/io/decompress.d.ts +4 -1
- package/dist/io/decompress.js +24 -36
- package/dist/io/export.d.ts +11 -4
- package/dist/io/export.js +75 -19
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.js +60 -35
- package/dist/io/index.d.ts +0 -1
- package/dist/io/index.js +2 -9
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +40 -40
- package/dist/isosurface/Isosurface.svelte +700 -215
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +590 -116
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +6 -6
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- package/dist/layout/InfoCard.svelte +8 -21
- package/dist/layout/InfoTag.svelte +1 -3
- package/dist/layout/NumberRangeInput.svelte +46 -0
- package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
- package/dist/layout/PropertyFilter.svelte +2 -6
- package/dist/layout/SettingsSection.svelte +57 -62
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +16 -11
- package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.js +1 -2
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +22 -59
- package/dist/layout/json-tree/JsonTree.svelte +101 -172
- package/dist/layout/json-tree/JsonValue.svelte +9 -32
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -188
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +5 -2
- package/dist/math.js +48 -38
- package/dist/overlays/ContextMenu.svelte +19 -34
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +110 -74
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
- package/dist/overlays/GlassChip.svelte +32 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +26 -0
- package/dist/overlays/portal.d.ts +2 -0
- package/dist/overlays/portal.js +14 -0
- package/dist/periodic-table/PeriodicTable.svelte +113 -145
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +4 -9
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +28 -27
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +461 -426
- package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
- package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +322 -226
- package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
- package/dist/plot/box/Violin.svelte.d.ts +1 -0
- package/dist/plot/box/box-plot.js +1 -1
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +2 -2
- package/dist/plot/core/axis-utils.d.ts +12 -7
- package/dist/plot/core/axis-utils.js +48 -48
- package/dist/plot/core/components/AxisLabel.svelte +82 -20
- package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
- package/dist/plot/core/components/ColorBar.svelte +101 -106
- package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
- package/dist/plot/core/components/FillArea.svelte +10 -17
- package/dist/plot/core/components/HierarchyControls.svelte +249 -0
- package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/core/components/Line.svelte +18 -9
- package/dist/plot/core/components/Line.svelte.d.ts +2 -0
- package/dist/plot/core/components/PlotAxis.svelte +40 -17
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotLegend.svelte +31 -35
- package/dist/plot/core/components/PlotMarginals.svelte +616 -0
- package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotTooltip.svelte +1 -1
- package/dist/plot/core/components/PortalSelect.svelte +4 -8
- package/dist/plot/core/components/ReferenceLine.svelte +27 -24
- package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
- package/dist/plot/core/components/ReferencePlane.svelte +7 -8
- package/dist/plot/core/components/ZeroLines.svelte +4 -8
- package/dist/plot/core/components/ZoomRect.svelte +1 -2
- package/dist/plot/core/components/index.d.ts +1 -0
- package/dist/plot/core/components/index.js +1 -0
- package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
- package/dist/plot/core/data-cleaning-signal.js +467 -0
- package/dist/plot/core/data-cleaning.d.ts +2 -42
- package/dist/plot/core/data-cleaning.js +27 -474
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +9 -15
- package/dist/plot/core/fill-utils.d.ts +3 -1
- package/dist/plot/core/fill-utils.js +28 -6
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.js +0 -2
- package/dist/plot/core/layout.d.ts +15 -6
- package/dist/plot/core/layout.js +57 -38
- package/dist/plot/core/marginals.d.ts +170 -0
- package/dist/plot/core/marginals.js +436 -0
- package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/reference-line.js +1 -1
- package/dist/plot/core/scales.d.ts +23 -0
- package/dist/plot/core/scales.js +39 -13
- package/dist/plot/core/svg.js +0 -1
- package/dist/plot/core/types/fills.d.ts +101 -0
- package/dist/plot/core/types/fills.js +15 -0
- package/dist/plot/core/types/plot-3d.d.ts +87 -0
- package/dist/plot/core/types/plot-3d.js +4 -0
- package/dist/plot/core/types/reference-lines.d.ts +140 -0
- package/dist/plot/core/types/reference-lines.js +9 -0
- package/dist/plot/core/types.d.ts +29 -324
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
- package/dist/plot/core/utils/hierarchy-chart.js +191 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +100 -0
- package/dist/plot/core/utils/label-placement.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +314 -340
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/histogram.d.ts +31 -0
- package/dist/plot/histogram/histogram.js +70 -0
- package/dist/plot/histogram/index.d.ts +1 -0
- package/dist/plot/histogram/index.js +1 -0
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/sankey/Sankey.svelte +78 -69
- package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +517 -410
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/scatter/ScatterPoint.svelte +16 -9
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/scatter-data.d.ts +1 -1
- package/dist/plot/scatter/scatter-data.js +8 -7
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
- package/dist/plot/sunburst/Sunburst.svelte +269 -347
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
- package/dist/plot/sunburst/index.d.ts +1 -1
- package/dist/plot/sunburst/index.js +2 -1
- package/dist/plot/sunburst/render.d.ts +2 -2
- package/dist/plot/sunburst/render.js +47 -22
- package/dist/plot/sunburst/sunburst.d.ts +5 -1
- package/dist/plot/sunburst/sunburst.js +10 -3
- package/dist/plot/treemap/Treemap.svelte +1022 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
- package/dist/plot/treemap/index.d.ts +4 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +28 -0
- package/dist/plot/treemap/treemap.js +102 -0
- package/dist/rdf/RdfPlot.svelte +37 -59
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +63 -52
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +85 -27
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +229 -525
- package/dist/spectral/Bands.svelte +207 -221
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -42
- package/dist/spectral/Dos.svelte +92 -93
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +65 -140
- package/dist/state.svelte.js +5 -18
- package/dist/structure/Arrow.svelte +5 -18
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +120 -85
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +45 -35
- package/dist/structure/Cylinder.svelte +24 -40
- package/dist/structure/InstancedAtoms.svelte +114 -0
- package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
- package/dist/structure/Lattice.svelte +85 -149
- package/dist/structure/SiteLabels.svelte +122 -0
- package/dist/structure/SiteLabels.svelte.d.ts +18 -0
- package/dist/structure/Structure.svelte +636 -504
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +637 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +165 -425
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +128 -133
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +535 -578
- package/dist/structure/StructureScene.svelte.d.ts +6 -2
- package/dist/structure/StructureViewport.svelte +451 -0
- package/dist/structure/StructureViewport.svelte.d.ts +65 -0
- package/dist/structure/atom-label-placement.d.ts +16 -12
- package/dist/structure/atom-label-placement.js +70 -37
- package/dist/structure/atom-properties.d.ts +2 -0
- package/dist/structure/atom-properties.js +105 -38
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +9 -3
- package/dist/structure/bonding.js +95 -46
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +1 -0
- package/dist/structure/export.js +89 -105
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +20 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +16 -1
- package/dist/structure/index.js +15 -10
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +272 -313
- package/dist/structure/partial-occupancy.js +10 -5
- package/dist/structure/pbc.js +85 -107
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +7 -21
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +7 -13
- package/dist/symmetry/SymmetryElements.svelte +11 -15
- package/dist/symmetry/SymmetryStats.svelte +55 -71
- package/dist/symmetry/WyckoffTable.svelte +10 -28
- package/dist/symmetry/cell-transform.js +6 -14
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +13 -10
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1142 -447
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +168 -53
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- package/dist/table/index.d.ts +21 -6
- package/dist/table/index.js +34 -21
- package/dist/theme/ThemeControl.svelte +16 -8
- package/dist/theme/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +5 -5
- package/dist/theme/index.js +5 -12
- package/dist/theme/themes.mjs +44 -66
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +3 -6
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +613 -386
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +12 -74
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
- package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +61 -49
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +17 -13
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +35 -32
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +34 -56
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +5 -8
- package/dist/trajectory/parse/ase.js +1 -1
- package/dist/trajectory/parse/h5-utils.d.ts +13 -0
- package/dist/trajectory/parse/h5-utils.js +109 -0
- package/dist/trajectory/parse/hdf5.d.ts +1 -1
- package/dist/trajectory/parse/hdf5.js +116 -106
- package/dist/trajectory/parse/index.d.ts +6 -6
- package/dist/trajectory/parse/index.js +25 -33
- package/dist/trajectory/parse/lammps.js +8 -12
- package/dist/trajectory/parse/vasp.js +10 -7
- package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
- package/dist/trajectory/parse/vaspout-electronic.js +207 -0
- package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
- package/dist/trajectory/parse/vaspout-h5.js +281 -0
- package/dist/trajectory/parse/xyz.js +59 -9
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +113 -64
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +79 -118
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +24 -62
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +69 -95
- package/package.json +94 -34
- package/readme.md +6 -5
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { StatusMessage } from '../feedback'
|
|
6
6
|
import Spinner from '../feedback/Spinner.svelte'
|
|
7
7
|
import Icon from '../Icon.svelte'
|
|
8
|
-
import
|
|
8
|
+
import * as io from '../io'
|
|
9
9
|
import { forward_window_keydown, handle_and_prevent } from '../keyboard'
|
|
10
|
-
import { format_num, trajectory_property_config } from '../labels'
|
|
10
|
+
import { format_num, trajectory_property_config, type TrajPropertyConfig } from '../labels'
|
|
11
11
|
import type { Vec2 } from '../math'
|
|
12
12
|
import { sanitize_html } from '../sanitize'
|
|
13
13
|
import { FullscreenButton, type FullscreenToggleProp, toggle_fullscreen } from '../layout'
|
|
@@ -17,21 +17,29 @@
|
|
|
17
17
|
import { Histogram, ScatterPlot } from '../plot'
|
|
18
18
|
import { toggle_series_visibility } from '../plot/core/utils/series-visibility'
|
|
19
19
|
import { DEFAULTS } from '../settings'
|
|
20
|
+
import type { AnyStructure } from '../structure'
|
|
20
21
|
import Structure from '../structure/Structure.svelte'
|
|
21
22
|
import { scaleLinear } from 'd3-scale'
|
|
22
23
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
23
|
-
import { untrack } from 'svelte'
|
|
24
|
+
import { onMount, untrack } from 'svelte'
|
|
24
25
|
import { tooltip } from 'svelte-multiselect/attachments'
|
|
25
26
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
27
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
26
28
|
import { full_data_extractor } from './extract'
|
|
27
29
|
import type {
|
|
28
30
|
ParseProgress,
|
|
29
31
|
TrajectoryDataExtractor,
|
|
30
32
|
TrajectoryFrame,
|
|
33
|
+
TrajectoryMetadata,
|
|
31
34
|
TrajectoryType,
|
|
32
35
|
TrajHandlerData,
|
|
33
36
|
} from './index'
|
|
34
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
FRAME_LOAD_DEBOUNCE_MS,
|
|
39
|
+
TrajectoryError,
|
|
40
|
+
TrajectoryExportPane,
|
|
41
|
+
TrajectoryInfoPane,
|
|
42
|
+
} from './index'
|
|
35
43
|
import type { AtomTypeMapping, LoadingOptions } from './parse'
|
|
36
44
|
import {
|
|
37
45
|
get_unsupported_format_message,
|
|
@@ -41,6 +49,7 @@
|
|
|
41
49
|
} from './parse'
|
|
42
50
|
import {
|
|
43
51
|
generate_axis_labels,
|
|
52
|
+
generate_axis_scale_types,
|
|
44
53
|
generate_plot_series,
|
|
45
54
|
generate_streaming_plot_series,
|
|
46
55
|
should_hide_plot,
|
|
@@ -64,6 +73,12 @@
|
|
|
64
73
|
total_frames: number
|
|
65
74
|
on_step_change: (idx: number) => void
|
|
66
75
|
}
|
|
76
|
+
type PlotMetadataStreamMessage = {
|
|
77
|
+
command?: string
|
|
78
|
+
file_path?: string
|
|
79
|
+
plot_metadata?: TrajectoryMetadata[]
|
|
80
|
+
is_complete?: boolean
|
|
81
|
+
}
|
|
67
82
|
|
|
68
83
|
let {
|
|
69
84
|
trajectory = $bindable(),
|
|
@@ -106,85 +121,86 @@
|
|
|
106
121
|
wrapper = $bindable(),
|
|
107
122
|
fullscreen = $bindable(false),
|
|
108
123
|
...rest
|
|
109
|
-
}: EventHandlers &
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
124
|
+
}: EventHandlers &
|
|
125
|
+
HTMLAttributes<HTMLDivElement> & {
|
|
126
|
+
// trajectory data - can be provided directly or loaded from file
|
|
127
|
+
trajectory?: TrajectoryType
|
|
128
|
+
// URL to load trajectory from (alternative to providing trajectory directly)
|
|
129
|
+
data_url?: string
|
|
130
|
+
// current step index being displayed
|
|
131
|
+
current_step_idx?: number
|
|
132
|
+
// custom function to extract plot data from trajectory frames
|
|
133
|
+
data_extractor?: TrajectoryDataExtractor
|
|
134
|
+
|
|
135
|
+
// file drop handlers
|
|
136
|
+
allow_file_drop?: boolean
|
|
137
|
+
// layout configuration - 'auto' (default) adapts to element size, 'horizontal'/'vertical' forces layout
|
|
138
|
+
layout?: `auto` | Orientation
|
|
139
|
+
// structure viewer props (passed to Structure component)
|
|
140
|
+
structure_props?: ComponentProps<typeof Structure>
|
|
141
|
+
// plot props (passed to ScatterPlot component)
|
|
142
|
+
scatter_props?: ComponentProps<typeof ScatterPlot>
|
|
143
|
+
// histogram props (passed to Histogram component, excluding series which is handled separately)
|
|
144
|
+
histogram_props?: Omit<ComponentProps<typeof Histogram>, `series`>
|
|
145
|
+
// spinner props (passed to Spinner component)
|
|
146
|
+
spinner_props?: ComponentProps<typeof Spinner>
|
|
147
|
+
// custom snippets for additional UI elements
|
|
148
|
+
trajectory_controls?: Snippet<[ControlsProps]>
|
|
149
|
+
// Custom error snippet for advanced error handling
|
|
150
|
+
error_snippet?: Snippet<[{ error_msg: string; on_dismiss: () => void }]>
|
|
151
|
+
// Controls visibility configuration.
|
|
152
|
+
// - 'always': controls always visible
|
|
153
|
+
// - 'hover': controls visible on component hover (default)
|
|
154
|
+
// - 'never': controls never visible
|
|
155
|
+
// - object: { mode, hidden, style } for fine-grained control
|
|
156
|
+
// Control names: 'filename', 'nav', 'step', 'fps', 'info-pane', 'export-pane', 'view-mode', 'fullscreen'
|
|
157
|
+
show_controls?: ShowControlsProp
|
|
158
|
+
// show/hide the fullscreen button
|
|
159
|
+
fullscreen_toggle?: FullscreenToggleProp
|
|
160
|
+
// automatically start playing when trajectory data is loaded
|
|
161
|
+
auto_play?: boolean
|
|
162
|
+
// display mode: 'structure+scatter' (default), 'structure' (only structure), 'scatter' (only scatter), 'histogram' (only histogram), 'structure+histogram' (structure with histogram)
|
|
163
|
+
display_mode?:
|
|
164
|
+
| `structure+scatter`
|
|
165
|
+
| `structure`
|
|
166
|
+
| `scatter`
|
|
167
|
+
| `histogram`
|
|
168
|
+
| `structure+histogram`
|
|
169
|
+
// step labels configuration for slider
|
|
170
|
+
// - positive number: number of evenly spaced ticks
|
|
171
|
+
// - negative number: spacing between ticks (e.g. -10 = every 10th step)
|
|
172
|
+
// - array: exact step indices to label
|
|
173
|
+
// - undefined: no labels
|
|
174
|
+
step_labels?: number | number[]
|
|
175
|
+
// visible properties - bindable array of property keys currently shown in the plot
|
|
176
|
+
// - controls which trajectory properties are plotted (e.g. ['energy', 'volume', 'force_max'])
|
|
177
|
+
// - bindable: reflects current visibility state and can be used for external control
|
|
178
|
+
// - if not provided, uses default visible properties (energy, force_max, stress_frobenius)
|
|
179
|
+
// - if specified properties don't exist in data, falls back to automatic selection
|
|
180
|
+
visible_properties?: string[]
|
|
181
|
+
// custom labels for trajectory properties - maps property keys to display labels
|
|
182
|
+
// - e.g. {energy: 'Total Energy', volume: 'Cell Volume', force_max: 'Max Force'}
|
|
183
|
+
// - merged with built-in trajectory_property_config
|
|
184
|
+
ELEM_PROPERTY_LABELS?: Record<string, string>
|
|
185
|
+
fps_range?: Vec2 // allowed FPS range [min_fps, max_fps]
|
|
186
|
+
fps?: number // frame rate for playback
|
|
187
|
+
// Loading options for large files
|
|
188
|
+
loading_options?: LoadingOptions
|
|
189
|
+
// Map LAMMPS atom types to element symbols (e.g. {1: 'Na', 2: 'Cl'})
|
|
190
|
+
atom_type_mapping?: AtomTypeMapping
|
|
191
|
+
// Disable plot skimming (mouse over plot doesn't update structure/step slider)
|
|
192
|
+
plot_skimming?: boolean
|
|
193
|
+
// bindable: true while the pointer is over the viewer (drives hover-scoped shortcuts)
|
|
194
|
+
hovered?: boolean
|
|
195
|
+
// bindable: whether the (structure) controls pane is currently open
|
|
196
|
+
controls_open?: boolean
|
|
197
|
+
// bindable: whether the trajectory info pane is currently open
|
|
198
|
+
info_pane_open?: boolean
|
|
199
|
+
// bindable: top-level wrapper element
|
|
200
|
+
wrapper?: HTMLDivElement
|
|
201
|
+
// bindable: fullscreen state
|
|
202
|
+
fullscreen?: boolean
|
|
203
|
+
} = $props()
|
|
188
204
|
|
|
189
205
|
let dragover = $state(false)
|
|
190
206
|
let loading = $state(false)
|
|
@@ -193,22 +209,22 @@
|
|
|
193
209
|
let parse_warning_msg = $state<string | undefined>(undefined)
|
|
194
210
|
$effect(() => {
|
|
195
211
|
const warnings = trajectory?.metadata?.parse_warnings
|
|
196
|
-
parse_warning_msg =
|
|
197
|
-
|
|
198
|
-
warnings.join(
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
parse_warning_msg =
|
|
213
|
+
Array.isArray(warnings) && warnings.length > 0
|
|
214
|
+
? `${warnings.length} parse warning${warnings.length > 1 ? `s` : ``}: ${warnings.join(
|
|
215
|
+
`; `,
|
|
216
|
+
)}`
|
|
217
|
+
: undefined
|
|
201
218
|
})
|
|
202
219
|
let is_playing = $state(false)
|
|
203
|
-
|
|
220
|
+
// requestAnimationFrame handle for the playback loop (rAF auto-pauses in background tabs and
|
|
221
|
+
// self-throttles, unlike setInterval which drifts and queues work when a frame render overruns)
|
|
222
|
+
let play_raf: number | undefined
|
|
204
223
|
|
|
205
224
|
// Ensure fps is within the allowed range
|
|
206
225
|
$effect(() => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} else if (fps > fps_range[1]) {
|
|
210
|
-
fps = fps_range[1]
|
|
211
|
-
}
|
|
226
|
+
const clamped = Math.max(fps_range[0], Math.min(fps_range[1], fps))
|
|
227
|
+
if (clamped !== fps) fps = clamped
|
|
212
228
|
})
|
|
213
229
|
let current_filename = $state<string | undefined>(undefined)
|
|
214
230
|
let current_file_path = $state<string | null>(null)
|
|
@@ -218,6 +234,9 @@
|
|
|
218
234
|
let element_size = $state({ width: 0, height: 0 })
|
|
219
235
|
let filename_copied = $state(false)
|
|
220
236
|
let orig_data = $state<string | ArrayBuffer | null>(null)
|
|
237
|
+
let data_url_load_id = 0
|
|
238
|
+
let loaded_data_url: string | undefined
|
|
239
|
+
let url_owned_trajectory: TrajectoryType | undefined
|
|
221
240
|
|
|
222
241
|
let controls_config = $derived(normalize_show_controls(show_controls))
|
|
223
242
|
|
|
@@ -232,13 +251,26 @@
|
|
|
232
251
|
})
|
|
233
252
|
|
|
234
253
|
// Get total frame count (supports both regular and indexed trajectories)
|
|
235
|
-
let total_frames = $derived(
|
|
236
|
-
|
|
237
|
-
|
|
254
|
+
let total_frames = $derived(trajectory?.total_frames || trajectory?.frames.length || 0)
|
|
255
|
+
|
|
256
|
+
// Clamp out-of-range step indices (e.g. an initial current_step_idx of
|
|
257
|
+
// Number.MAX_SAFE_INTEGER means "jump to the last frame", used by hosts
|
|
258
|
+
// restoring viewer position across trajectory reloads). Hosts are notified
|
|
259
|
+
// of the correction so their recorded position matches the shown frame.
|
|
260
|
+
$effect(() => {
|
|
261
|
+
if (total_frames > 0 && current_step_idx > total_frames - 1) {
|
|
262
|
+
current_step_idx = total_frames - 1
|
|
263
|
+
notify_step_change()
|
|
264
|
+
} else if (current_step_idx < 0) {
|
|
265
|
+
current_step_idx = 0
|
|
266
|
+
notify_step_change()
|
|
267
|
+
}
|
|
268
|
+
})
|
|
238
269
|
|
|
239
270
|
// Current frame - load on demand for indexed trajectories
|
|
240
271
|
let current_frame = $state<TrajectoryFrame | null>(null)
|
|
241
272
|
let frame_load_request_id = 0
|
|
273
|
+
let frame_load_timeout: ReturnType<typeof setTimeout> | undefined
|
|
242
274
|
|
|
243
275
|
// Auto-play when trajectory changes (handles both props and file loading)
|
|
244
276
|
$effect(() => {
|
|
@@ -252,8 +284,10 @@
|
|
|
252
284
|
if (trajectory && current_step_idx >= 0 && current_step_idx < total_frames) {
|
|
253
285
|
if (trajectory.frame_loader) {
|
|
254
286
|
// Load frame on demand (works for both indexed files and external streaming)
|
|
255
|
-
|
|
287
|
+
current_frame = null
|
|
288
|
+
schedule_frame_load_on_demand(current_step_idx)
|
|
256
289
|
} else {
|
|
290
|
+
clear_frame_load_timeout()
|
|
257
291
|
// Use in-memory frame for regular trajectories
|
|
258
292
|
current_frame = trajectory.frames[current_step_idx] || null
|
|
259
293
|
}
|
|
@@ -262,11 +296,175 @@
|
|
|
262
296
|
}
|
|
263
297
|
})
|
|
264
298
|
|
|
299
|
+
// LRU cache of decoded frames (keyed to the current trajectory) so scrub/playback over
|
|
300
|
+
// indexed/streaming trajectories avoids re-reading frames and prefetch can warm upcoming ones.
|
|
301
|
+
// Capped by frame count AND a total-atom budget (cache many tiny frames or few huge ones).
|
|
302
|
+
const FRAME_CACHE_MAX = 64
|
|
303
|
+
const FRAME_CACHE_MAX_ATOMS = 200_000
|
|
304
|
+
let frame_cache = new Map<number, TrajectoryFrame>()
|
|
305
|
+
let frame_cache_owner: TrajectoryType | undefined
|
|
306
|
+
// Frames currently being read (direct or prefetch) so we don't kick off duplicate loads
|
|
307
|
+
const inflight_frames = new Set<number>()
|
|
308
|
+
let streaming_file_path = $derived(
|
|
309
|
+
trajectory?.metadata?.streaming_file_path as string | undefined,
|
|
310
|
+
)
|
|
311
|
+
let plot_metadata_loading = $derived(trajectory?.metadata?.plot_metadata_loading === true)
|
|
312
|
+
|
|
313
|
+
const clear_frame_load_timeout = () => {
|
|
314
|
+
if (frame_load_timeout) clearTimeout(frame_load_timeout)
|
|
315
|
+
frame_load_timeout = undefined
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const skip_stale_url_stream = () =>
|
|
319
|
+
Boolean(data_url && loaded_data_url && data_url !== loaded_data_url)
|
|
320
|
+
|
|
321
|
+
// Replace the trajectory with an updated copy, keeping URL ownership if it applied.
|
|
322
|
+
// No-ops while a data_url switch is in flight so stale streams can't mutate the old model.
|
|
323
|
+
const update_trajectory = (updates: Partial<TrajectoryType>) => {
|
|
324
|
+
if (!trajectory || skip_stale_url_stream()) return
|
|
325
|
+
const preserves_url_ownership = trajectory === url_owned_trajectory
|
|
326
|
+
trajectory = { ...trajectory, ...updates }
|
|
327
|
+
if (preserves_url_ownership) url_owned_trajectory = trajectory
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const merge_plot_metadata = (batch: TrajectoryMetadata[]) => {
|
|
331
|
+
if (batch.length === 0) return
|
|
332
|
+
update_trajectory({ plot_metadata: [...(trajectory?.plot_metadata ?? []), ...batch] })
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const finish_plot_metadata_loading = () =>
|
|
336
|
+
update_trajectory({
|
|
337
|
+
metadata: { ...trajectory?.metadata, plot_metadata_loading: false },
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
onMount(() => {
|
|
341
|
+
const handle_plot_metadata_stream = (event: MessageEvent<PlotMetadataStreamMessage>) => {
|
|
342
|
+
// Global listener: other code posts arbitrary messages (including null data)
|
|
343
|
+
if (typeof event.data !== `object` || event.data === null) return
|
|
344
|
+
const { command, file_path, is_complete, plot_metadata } = event.data
|
|
345
|
+
if (command !== `plot_metadata_stream` || file_path !== streaming_file_path) return
|
|
346
|
+
if (Array.isArray(plot_metadata)) merge_plot_metadata(plot_metadata)
|
|
347
|
+
if (is_complete) finish_plot_metadata_loading()
|
|
348
|
+
}
|
|
349
|
+
globalThis.addEventListener(`message`, handle_plot_metadata_stream)
|
|
350
|
+
return () => {
|
|
351
|
+
globalThis.removeEventListener(`message`, handle_plot_metadata_stream)
|
|
352
|
+
clear_frame_load_timeout()
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
// Reset per-trajectory caches when the trajectory changes (frames belong to the old one)
|
|
357
|
+
function ensure_frame_cache_owner() {
|
|
358
|
+
if (frame_cache_owner !== trajectory) {
|
|
359
|
+
frame_cache = new Map()
|
|
360
|
+
inflight_frames.clear()
|
|
361
|
+
frame_cache_owner = trajectory
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Sync LRU read (delete + re-insert refreshes recency); undefined on miss
|
|
365
|
+
function cache_get(frame_idx: number): TrajectoryFrame | undefined {
|
|
366
|
+
const hit = frame_cache.get(frame_idx)
|
|
367
|
+
if (!hit) return undefined
|
|
368
|
+
frame_cache.delete(frame_idx)
|
|
369
|
+
frame_cache.set(frame_idx, hit)
|
|
370
|
+
return hit
|
|
371
|
+
}
|
|
372
|
+
// Sync LRU write, evicting oldest entries until under both the frame and atom budgets
|
|
373
|
+
function cache_put(frame_idx: number, frame: TrajectoryFrame) {
|
|
374
|
+
frame_cache.set(frame_idx, frame)
|
|
375
|
+
let total_atoms = 0
|
|
376
|
+
for (const cached of frame_cache.values())
|
|
377
|
+
total_atoms += cached.structure?.sites?.length ?? 0
|
|
378
|
+
while (
|
|
379
|
+
frame_cache.size > 1 &&
|
|
380
|
+
(frame_cache.size > FRAME_CACHE_MAX || total_atoms > FRAME_CACHE_MAX_ATOMS)
|
|
381
|
+
) {
|
|
382
|
+
const oldest = frame_cache.keys().next().value
|
|
383
|
+
if (oldest === undefined) break
|
|
384
|
+
total_atoms -= frame_cache.get(oldest)?.structure?.sites?.length ?? 0
|
|
385
|
+
frame_cache.delete(oldest)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Warm the next couple of frames in the background (fire-and-forget) so playback/forward-scrub
|
|
390
|
+
// doesn't stall on a serial per-frame read; skips frames already cached or in flight
|
|
391
|
+
function prefetch_frames(from_idx: number) {
|
|
392
|
+
const frame_loader = trajectory?.frame_loader
|
|
393
|
+
if (!frame_loader) return
|
|
394
|
+
if (trajectory?.indexed_frames && trajectory.indexed_frames.length < total_frames) return
|
|
395
|
+
const owner = trajectory
|
|
396
|
+
for (const ahead of [1, 2]) {
|
|
397
|
+
const idx = from_idx + ahead
|
|
398
|
+
if (idx >= total_frames || frame_cache.has(idx) || inflight_frames.has(idx)) continue
|
|
399
|
+
inflight_frames.add(idx)
|
|
400
|
+
frame_loader
|
|
401
|
+
.load_frame(orig_data || ``, idx)
|
|
402
|
+
.then((frame) => {
|
|
403
|
+
if (frame && frame_cache_owner === owner) cache_put(idx, frame)
|
|
404
|
+
})
|
|
405
|
+
.catch(() => {})
|
|
406
|
+
.finally(() => inflight_frames.delete(idx))
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function use_cached_or_in_memory_frame(
|
|
411
|
+
load_trajectory: TrajectoryType,
|
|
412
|
+
frame_idx: number,
|
|
413
|
+
): boolean {
|
|
414
|
+
const cached = cache_get(frame_idx)
|
|
415
|
+
if (cached) {
|
|
416
|
+
current_frame = cached
|
|
417
|
+
prefetch_frames(frame_idx)
|
|
418
|
+
return true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const in_memory_frame = load_trajectory.frames[frame_idx]
|
|
422
|
+
if (in_memory_frame) {
|
|
423
|
+
cache_put(frame_idx, in_memory_frame)
|
|
424
|
+
current_frame = in_memory_frame
|
|
425
|
+
prefetch_frames(frame_idx)
|
|
426
|
+
return true
|
|
427
|
+
}
|
|
428
|
+
return false
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function schedule_frame_load_on_demand(frame_idx: number) {
|
|
432
|
+
const load_trajectory = trajectory
|
|
433
|
+
if (!load_trajectory?.frame_loader) return
|
|
434
|
+
ensure_frame_cache_owner()
|
|
435
|
+
frame_load_request_id++
|
|
436
|
+
|
|
437
|
+
if (use_cached_or_in_memory_frame(load_trajectory, frame_idx)) return
|
|
438
|
+
|
|
439
|
+
clear_frame_load_timeout()
|
|
440
|
+
// Debouncing during playback would keep resetting the timer at fps above
|
|
441
|
+
// ~1000/FRAME_LOAD_DEBOUNCE_MS and stall uncached streamed frames entirely.
|
|
442
|
+
// untrack: this runs synchronously inside the step-change $effect, so tracking
|
|
443
|
+
// is_playing would re-run that effect on every play/pause toggle and kick off
|
|
444
|
+
// duplicate loads for a frame that's already in flight.
|
|
445
|
+
if (untrack(() => is_playing)) {
|
|
446
|
+
void load_frame_on_demand(frame_idx)
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
const request_id = frame_load_request_id
|
|
450
|
+
frame_load_timeout = setTimeout(() => {
|
|
451
|
+
frame_load_timeout = undefined
|
|
452
|
+
if (
|
|
453
|
+
request_id === frame_load_request_id &&
|
|
454
|
+
trajectory === load_trajectory &&
|
|
455
|
+
current_step_idx === frame_idx
|
|
456
|
+
) {
|
|
457
|
+
load_frame_on_demand(frame_idx)
|
|
458
|
+
}
|
|
459
|
+
}, FRAME_LOAD_DEBOUNCE_MS)
|
|
460
|
+
}
|
|
461
|
+
|
|
265
462
|
// Load frame on demand - works for both indexed files and external streaming
|
|
266
463
|
async function load_frame_on_demand(frame_idx: number) {
|
|
267
464
|
const load_trajectory = trajectory
|
|
268
465
|
const frame_loader = load_trajectory?.frame_loader
|
|
269
466
|
if (!load_trajectory || !frame_loader) return
|
|
467
|
+
ensure_frame_cache_owner()
|
|
270
468
|
|
|
271
469
|
const request_id = ++frame_load_request_id
|
|
272
470
|
const request_is_current = () =>
|
|
@@ -274,13 +472,18 @@
|
|
|
274
472
|
trajectory === load_trajectory &&
|
|
275
473
|
current_step_idx === frame_idx
|
|
276
474
|
|
|
475
|
+
if (use_cached_or_in_memory_frame(load_trajectory, frame_idx)) return
|
|
476
|
+
inflight_frames.add(frame_idx) // let concurrent prefetch skip the frame we're already loading
|
|
277
477
|
try {
|
|
278
478
|
const frame = await frame_loader.load_frame(
|
|
279
|
-
orig_data || ``, //
|
|
479
|
+
orig_data || ``, // original_data for indexed files, empty string for external streaming
|
|
280
480
|
frame_idx,
|
|
281
481
|
)
|
|
482
|
+
// cache the decoded frame even if it arrived stale (still valid data for that index)
|
|
483
|
+
if (frame && frame_cache_owner === load_trajectory) cache_put(frame_idx, frame)
|
|
282
484
|
if (!request_is_current()) return
|
|
283
485
|
current_frame = frame
|
|
486
|
+
prefetch_frames(frame_idx) // warm upcoming frames for smooth playback/scrub
|
|
284
487
|
} catch (error) {
|
|
285
488
|
if (!request_is_current()) return
|
|
286
489
|
console.error(`Failed to load frame ${frame_idx}:`, error)
|
|
@@ -292,14 +495,22 @@
|
|
|
292
495
|
step_idx: frame_idx,
|
|
293
496
|
frame_count: total_frames,
|
|
294
497
|
})
|
|
498
|
+
} finally {
|
|
499
|
+
inflight_frames.delete(frame_idx)
|
|
295
500
|
}
|
|
296
501
|
}
|
|
297
502
|
|
|
298
|
-
// Current frame structure for display
|
|
299
|
-
|
|
503
|
+
// Current frame structure for display. Holds the last resolved structure so the 3D
|
|
504
|
+
// view doesn't blank while an uncached frame loads on demand (current_frame is nulled
|
|
505
|
+
// during loads to keep the info pane from showing the previous frame's data).
|
|
506
|
+
let current_structure = $state<AnyStructure | undefined>(undefined)
|
|
507
|
+
$effect(() => {
|
|
508
|
+
if (current_frame?.structure) current_structure = current_frame.structure
|
|
509
|
+
else if (!trajectory) current_structure = undefined
|
|
510
|
+
})
|
|
300
511
|
|
|
301
512
|
// Track hidden elements (persists across frame changes)
|
|
302
|
-
let hidden_elements = $state(new
|
|
513
|
+
let hidden_elements = $state(new SvelteSet<ElementSymbol>())
|
|
303
514
|
|
|
304
515
|
let step_label_positions = $derived.by((): number[] => {
|
|
305
516
|
if (!step_labels || total_frames <= 1) return []
|
|
@@ -310,11 +521,14 @@
|
|
|
310
521
|
|
|
311
522
|
if (typeof step_labels === `number`) {
|
|
312
523
|
if (step_labels > 0) {
|
|
313
|
-
return scaleLinear()
|
|
524
|
+
return scaleLinear()
|
|
525
|
+
.domain([0, total_frames - 1])
|
|
526
|
+
.nice()
|
|
314
527
|
.ticks(Math.min(step_labels, total_frames))
|
|
315
528
|
.map((tick) => Math.round(tick))
|
|
316
|
-
.filter(
|
|
317
|
-
tick
|
|
529
|
+
.filter(
|
|
530
|
+
(tick, idx, ticks) =>
|
|
531
|
+
tick >= 0 && tick < total_frames && ticks.indexOf(tick) === idx,
|
|
318
532
|
)
|
|
319
533
|
}
|
|
320
534
|
if (step_labels < 0) {
|
|
@@ -335,11 +549,13 @@
|
|
|
335
549
|
let extended_config = $derived.by(() => {
|
|
336
550
|
if (!ELEM_PROPERTY_LABELS) return trajectory_property_config
|
|
337
551
|
|
|
338
|
-
const custom_config: Record<string,
|
|
552
|
+
const custom_config: Record<string, TrajPropertyConfig> = {}
|
|
339
553
|
for (const [key, label] of Object.entries(ELEM_PROPERTY_LABELS)) {
|
|
340
|
-
const existing =
|
|
341
|
-
trajectory_property_config[key.toLowerCase()]
|
|
342
|
-
|
|
554
|
+
const existing =
|
|
555
|
+
trajectory_property_config[key] || trajectory_property_config[key.toLowerCase()]
|
|
556
|
+
// Spread the existing config so fields like axis_group survive the
|
|
557
|
+
// label override (losing axis_group would break dedicated-axis grouping)
|
|
558
|
+
custom_config[key] = { ...existing, label, unit: existing?.unit || `` }
|
|
343
559
|
}
|
|
344
560
|
return { ...trajectory_property_config, ...custom_config }
|
|
345
561
|
})
|
|
@@ -392,7 +608,8 @@
|
|
|
392
608
|
|
|
393
609
|
// Only update if changed (use untrack to avoid circular dependency)
|
|
394
610
|
const current = untrack(() => visible_properties) || []
|
|
395
|
-
const has_changed =
|
|
611
|
+
const has_changed =
|
|
612
|
+
visible_keys.length !== current.length ||
|
|
396
613
|
!visible_keys.every((key, idx) => key === current[idx])
|
|
397
614
|
|
|
398
615
|
if (has_changed) {
|
|
@@ -407,82 +624,74 @@
|
|
|
407
624
|
plot_series = toggle_series_visibility(plot_series, series_idx)
|
|
408
625
|
}
|
|
409
626
|
|
|
627
|
+
// Streamed trajectories plot sampled per-frame metadata, so x values are frame numbers
|
|
628
|
+
let x_axis_quantity = $derived(trajectory?.plot_metadata ? `Frame` : `Step`)
|
|
410
629
|
let x_axis = $derived({
|
|
411
|
-
label:
|
|
412
|
-
|
|
630
|
+
label: x_axis_quantity,
|
|
631
|
+
// ~g (not ~s) so sub-1 values read as 0.8 rather than SI "800m"; integer steps stay clean
|
|
632
|
+
format: `~g`,
|
|
413
633
|
ticks: step_label_positions,
|
|
414
634
|
})
|
|
415
635
|
// Generate axis labels based on first visible series on each axis
|
|
416
636
|
let y_axis_labels = $derived(generate_axis_labels(plot_series))
|
|
637
|
+
// Axes whose visible series are all-positive and span many decades (e.g. SCF
|
|
638
|
+
// convergence residuals) default to log scale instead of a linear hockey stick
|
|
639
|
+
let y_axis_scale_types = $derived(generate_axis_scale_types(plot_series))
|
|
417
640
|
let y_axis = $derived({
|
|
418
641
|
label: y_axis_labels.y1,
|
|
419
|
-
format:
|
|
642
|
+
format: `~g`,
|
|
420
643
|
label_shift: { y: 10 },
|
|
644
|
+
scale_type: y_axis_scale_types.y1,
|
|
421
645
|
})
|
|
422
646
|
let y2_axis = $derived({
|
|
423
647
|
label: y_axis_labels.y2,
|
|
424
|
-
format:
|
|
648
|
+
format: `~g`,
|
|
425
649
|
label_shift: { y: 80 },
|
|
650
|
+
scale_type: y_axis_scale_types.y2,
|
|
426
651
|
})
|
|
427
|
-
|
|
428
652
|
// hide plot if all plotted values are constant (no variation)
|
|
429
653
|
let show_plot = $derived(
|
|
430
|
-
display_mode !== `structure` &&
|
|
654
|
+
display_mode !== `structure` &&
|
|
655
|
+
(plot_metadata_loading || !should_hide_plot(trajectory, plot_series)),
|
|
431
656
|
)
|
|
432
|
-
|
|
433
|
-
// Determine what to show based on display mode
|
|
434
657
|
let show_structure = $derived(![`scatter`, `histogram`].includes(display_mode))
|
|
435
|
-
let actual_show_plot = $derived(display_mode !== `structure` && show_plot)
|
|
436
658
|
|
|
437
659
|
// Check if there are any Y2 series to determine padding
|
|
438
|
-
let has_y2_series = $derived(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
//
|
|
660
|
+
let has_y2_series = $derived(plot_series.some((srs) => srs.y_axis === `y2` && srs.visible))
|
|
661
|
+
// Report the current step to on_step_change consumers. Also wired to the step
|
|
662
|
+
// slider/number input whose bind:value bypasses the navigation functions
|
|
663
|
+
// below — those handlers pass the event target's value explicitly because
|
|
664
|
+
// bind:value may not have written the binding yet when oninput fires.
|
|
665
|
+
function notify_step_change(step_idx: number = current_step_idx) {
|
|
666
|
+
if (!trajectory || !Number.isFinite(step_idx)) return
|
|
667
|
+
const last_frame = Math.max(total_frames - 1, 0)
|
|
668
|
+
const clamped_step = Math.min(Math.max(Math.round(step_idx), 0), last_frame)
|
|
669
|
+
on_step_change?.({
|
|
670
|
+
trajectory,
|
|
671
|
+
step_idx: clamped_step,
|
|
672
|
+
frame_count: total_frames,
|
|
673
|
+
frame: current_frame || undefined,
|
|
674
|
+
})
|
|
675
|
+
}
|
|
676
|
+
// Step navigation functions (streaming frame loading is handled by the reactive effect)
|
|
443
677
|
function next_step() {
|
|
444
678
|
if (current_step_idx < total_frames - 1) {
|
|
445
679
|
current_step_idx++
|
|
446
|
-
|
|
447
|
-
if (trajectory) {
|
|
448
|
-
on_step_change?.({
|
|
449
|
-
trajectory,
|
|
450
|
-
step_idx: current_step_idx,
|
|
451
|
-
frame_count: total_frames,
|
|
452
|
-
frame: current_frame || undefined,
|
|
453
|
-
})
|
|
454
|
-
}
|
|
680
|
+
notify_step_change()
|
|
455
681
|
}
|
|
456
682
|
}
|
|
457
683
|
|
|
458
684
|
function prev_step() {
|
|
459
685
|
if (current_step_idx > 0) {
|
|
460
686
|
current_step_idx--
|
|
461
|
-
|
|
462
|
-
if (trajectory) {
|
|
463
|
-
on_step_change?.({
|
|
464
|
-
trajectory,
|
|
465
|
-
step_idx: current_step_idx,
|
|
466
|
-
frame_count: total_frames,
|
|
467
|
-
frame: current_frame || undefined,
|
|
468
|
-
})
|
|
469
|
-
}
|
|
687
|
+
notify_step_change()
|
|
470
688
|
}
|
|
471
689
|
}
|
|
472
690
|
|
|
473
691
|
function go_to_step(idx: number) {
|
|
474
692
|
if (idx >= 0 && idx < total_frames) {
|
|
475
693
|
current_step_idx = idx
|
|
476
|
-
|
|
477
|
-
// Handle callbacks for both traditional and streaming modes
|
|
478
|
-
if (trajectory) {
|
|
479
|
-
on_step_change?.({
|
|
480
|
-
trajectory,
|
|
481
|
-
step_idx: current_step_idx,
|
|
482
|
-
frame_count: total_frames,
|
|
483
|
-
frame: current_frame || undefined,
|
|
484
|
-
})
|
|
485
|
-
}
|
|
694
|
+
notify_step_change()
|
|
486
695
|
}
|
|
487
696
|
}
|
|
488
697
|
|
|
@@ -501,7 +710,7 @@
|
|
|
501
710
|
reader.addEventListener(`error`, () => reject(new Error(`Failed to read file`)))
|
|
502
711
|
|
|
503
712
|
// Read as text for text-based formats, binary for others
|
|
504
|
-
if (
|
|
713
|
+
if (/\.(?:xyz|json|extxyz|lammpstrj)$/.test(file.name.toLowerCase())) {
|
|
505
714
|
reader.readAsText(file)
|
|
506
715
|
} else reader.readAsArrayBuffer(file)
|
|
507
716
|
})
|
|
@@ -528,44 +737,50 @@
|
|
|
528
737
|
})
|
|
529
738
|
}
|
|
530
739
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
on_end?.({
|
|
547
|
-
trajectory,
|
|
548
|
-
step_idx: current_step_idx,
|
|
549
|
-
frame_count: total_frames,
|
|
550
|
-
frame: current_frame || undefined,
|
|
551
|
-
})
|
|
552
|
-
}
|
|
553
|
-
go_to_step(0) // Loop back to 1st step
|
|
554
|
-
if (trajectory) {
|
|
555
|
-
on_loop?.({ trajectory, frame_count: total_frames })
|
|
556
|
-
}
|
|
557
|
-
} else next_step()
|
|
558
|
-
}, rate_ms)
|
|
559
|
-
} else if (current_interval !== undefined) {
|
|
560
|
-
// Clear interval when not playing
|
|
561
|
-
clearInterval(current_interval)
|
|
562
|
-
play_interval = undefined
|
|
563
|
-
}
|
|
564
|
-
})
|
|
740
|
+
// Advance one frame (or loop back to start), firing the matching callbacks
|
|
741
|
+
function advance_playback() {
|
|
742
|
+
if (current_step_idx >= total_frames - 1) {
|
|
743
|
+
if (trajectory) {
|
|
744
|
+
on_end?.({
|
|
745
|
+
trajectory,
|
|
746
|
+
step_idx: current_step_idx,
|
|
747
|
+
frame_count: total_frames,
|
|
748
|
+
frame: current_frame || undefined,
|
|
749
|
+
})
|
|
750
|
+
}
|
|
751
|
+
go_to_step(0) // loop back to 1st step
|
|
752
|
+
if (trajectory) on_loop?.({ trajectory, frame_count: total_frames })
|
|
753
|
+
} else next_step()
|
|
754
|
+
}
|
|
565
755
|
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
756
|
+
// rAF playback loop. Only tracks `is_playing`; `fps`/`current_step_idx`/`total_frames` are read
|
|
757
|
+
// live in the untracked tick, so changing fps mid-play retargets the cadence without restarting.
|
|
758
|
+
// Clamped delta + at most one advance per tick lets a background tab resume cleanly (no jump),
|
|
759
|
+
// but also caps playback at the ~60fps refresh rate (= default fps_range max; higher won't help).
|
|
760
|
+
$effect(() => {
|
|
761
|
+
if (!is_playing) return
|
|
762
|
+
let last = performance.now()
|
|
763
|
+
let accumulated_ms = 0
|
|
764
|
+
const tick = (now: number) => {
|
|
765
|
+
// stop if the trajectory went away or is no longer animatable (e.g. swapped mid-play)
|
|
766
|
+
if (!trajectory || total_frames <= 1) {
|
|
767
|
+
is_playing = false
|
|
768
|
+
return
|
|
769
|
+
}
|
|
770
|
+
accumulated_ms += Math.min(now - last, 250)
|
|
771
|
+
last = now
|
|
772
|
+
const step_ms = 1000 / Math.max(0.1, fps)
|
|
773
|
+
if (accumulated_ms >= step_ms) {
|
|
774
|
+
accumulated_ms = Math.min(accumulated_ms - step_ms, step_ms)
|
|
775
|
+
advance_playback()
|
|
776
|
+
}
|
|
777
|
+
play_raf = requestAnimationFrame(tick)
|
|
778
|
+
}
|
|
779
|
+
play_raf = requestAnimationFrame(tick)
|
|
780
|
+
return () => {
|
|
781
|
+
if (play_raf !== undefined) cancelAnimationFrame(play_raf)
|
|
782
|
+
play_raf = undefined
|
|
783
|
+
}
|
|
569
784
|
})
|
|
570
785
|
|
|
571
786
|
// Handle internal file format drops
|
|
@@ -582,10 +797,7 @@
|
|
|
582
797
|
const array_buffer = await response.arrayBuffer()
|
|
583
798
|
await load_trajectory_data(array_buffer, file_info.name)
|
|
584
799
|
} else {
|
|
585
|
-
console.warn(
|
|
586
|
-
`Binary file without ArrayBuffer or blob URL:`,
|
|
587
|
-
file_info.name,
|
|
588
|
-
)
|
|
800
|
+
console.warn(`Binary file without ArrayBuffer or blob URL:`, file_info.name)
|
|
589
801
|
}
|
|
590
802
|
} else {
|
|
591
803
|
await load_trajectory_data(file_info.content, file_info.name)
|
|
@@ -607,26 +819,23 @@
|
|
|
607
819
|
|
|
608
820
|
try {
|
|
609
821
|
// Check for our custom internal file format first
|
|
610
|
-
const internal_data = event.dataTransfer?.getData(
|
|
611
|
-
`application/x-matterviz-file`,
|
|
612
|
-
)
|
|
822
|
+
const internal_data = event.dataTransfer?.getData(`application/x-matterviz-file`)
|
|
613
823
|
if (internal_data) {
|
|
614
824
|
const handled = await handle_internal_file_drop(internal_data)
|
|
615
825
|
if (handled) return
|
|
616
826
|
}
|
|
617
827
|
|
|
618
828
|
// Handle URL-based files (e.g. from FilePicker)
|
|
619
|
-
const handled = await
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
829
|
+
const handled = await io
|
|
830
|
+
.handle_url_drop(event, async (content, filename) => {
|
|
831
|
+
current_filename = filename
|
|
832
|
+
file_size =
|
|
833
|
+
content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
|
|
834
|
+
await load_trajectory_data(content, filename)
|
|
835
|
+
})
|
|
836
|
+
.catch(() => false)
|
|
837
|
+
|
|
838
|
+
if (handled) return
|
|
630
839
|
|
|
631
840
|
// Handle file system drops with optimized large file support
|
|
632
841
|
const file = event.dataTransfer?.files[0]
|
|
@@ -658,33 +867,58 @@
|
|
|
658
867
|
}
|
|
659
868
|
}
|
|
660
869
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
870
|
+
// Load trajectory from URL when data_url is provided. Track the model produced by
|
|
871
|
+
// this effect so caller-owned trajectory props keep precedence while URL-owned
|
|
872
|
+
// models can reload when data_url changes.
|
|
873
|
+
$effect(() => {
|
|
874
|
+
const requested_url = data_url
|
|
875
|
+
const current_trajectory = trajectory
|
|
876
|
+
const caller_owns_trajectory = Boolean(
|
|
877
|
+
current_trajectory && current_trajectory !== url_owned_trajectory,
|
|
878
|
+
)
|
|
879
|
+
if (!requested_url || caller_owns_trajectory) {
|
|
880
|
+
loaded_data_url = undefined
|
|
881
|
+
url_owned_trajectory = undefined
|
|
882
|
+
return
|
|
883
|
+
}
|
|
884
|
+
if (loaded_data_url === requested_url) return
|
|
885
|
+
|
|
886
|
+
const load_id = ++data_url_load_id
|
|
887
|
+
const is_current = () => load_id === data_url_load_id
|
|
888
|
+
loading = true
|
|
889
|
+
error_msg = null
|
|
890
|
+
|
|
891
|
+
io.load_from_url(requested_url, async (content, filename) => {
|
|
892
|
+
if (!is_current()) return
|
|
893
|
+
current_filename = filename
|
|
894
|
+
file_size =
|
|
895
|
+
content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
|
|
896
|
+
await load_trajectory_data(content, filename, {
|
|
897
|
+
on_trajectory_loaded: (loaded_trajectory) => {
|
|
898
|
+
if (!is_current()) return
|
|
899
|
+
url_owned_trajectory = loaded_trajectory
|
|
900
|
+
loaded_data_url = requested_url
|
|
901
|
+
},
|
|
902
|
+
should_commit: is_current,
|
|
672
903
|
})
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
904
|
+
})
|
|
905
|
+
.catch((err: Error) => {
|
|
906
|
+
if (!is_current()) return
|
|
907
|
+
console.error(`Failed to load trajectory from URL:`, err)
|
|
908
|
+
error_msg = `Failed to load trajectory: ${err.message}`
|
|
909
|
+
current_filename = undefined
|
|
910
|
+
file_size = undefined
|
|
911
|
+
on_error?.({ error_msg, filename: io.basename_from_url(requested_url) })
|
|
912
|
+
})
|
|
913
|
+
.finally(() => {
|
|
914
|
+
if (is_current()) loading = false
|
|
915
|
+
})
|
|
916
|
+
|
|
917
|
+
return () => {
|
|
918
|
+
if (is_current()) {
|
|
919
|
+
data_url_load_id += 1
|
|
920
|
+
loading = false
|
|
921
|
+
}
|
|
688
922
|
}
|
|
689
923
|
})
|
|
690
924
|
|
|
@@ -693,87 +927,82 @@
|
|
|
693
927
|
on_frame_rate_change?.({ trajectory, fps })
|
|
694
928
|
})
|
|
695
929
|
|
|
696
|
-
async function load_trajectory_data(
|
|
930
|
+
async function load_trajectory_data(
|
|
931
|
+
data: string | ArrayBuffer,
|
|
932
|
+
filename: string,
|
|
933
|
+
options: {
|
|
934
|
+
on_trajectory_loaded?: (loaded_trajectory: TrajectoryType) => void
|
|
935
|
+
should_commit?: () => boolean
|
|
936
|
+
} = {},
|
|
937
|
+
) {
|
|
938
|
+
const { on_trajectory_loaded, should_commit = () => true } = options
|
|
697
939
|
loading = true
|
|
698
940
|
error_msg = null
|
|
699
941
|
parsing_progress = null
|
|
700
942
|
|
|
701
943
|
// Reset previous loading state
|
|
702
944
|
orig_data = null
|
|
945
|
+
const file_size_bytes =
|
|
946
|
+
data instanceof ArrayBuffer ? data.byteLength : new Blob([data]).size
|
|
703
947
|
|
|
704
948
|
try {
|
|
705
949
|
const data_size = data instanceof ArrayBuffer ? data.byteLength : data.length
|
|
706
950
|
|
|
707
951
|
// Determine loading strategy based on file size
|
|
708
|
-
const bin_file_threshold = loading_options.bin_file_threshold ??
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
MAX_TEXT_FILE_SIZE
|
|
712
|
-
if (
|
|
952
|
+
const bin_file_threshold = loading_options.bin_file_threshold ?? MAX_BIN_FILE_SIZE
|
|
953
|
+
const text_file_threshold = loading_options.text_file_threshold ?? MAX_TEXT_FILE_SIZE
|
|
954
|
+
const is_large_file =
|
|
713
955
|
(data instanceof ArrayBuffer && data_size > bin_file_threshold) ||
|
|
714
956
|
(typeof data === `string` && data_size > text_file_threshold)
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
parsing_progress = progress
|
|
722
|
-
},
|
|
723
|
-
|
|
957
|
+
|
|
958
|
+
// Large files get indexed loading by default (loading_options can override)
|
|
959
|
+
const parsed_trajectory = await parse_trajectory_async(
|
|
960
|
+
data,
|
|
961
|
+
filename,
|
|
962
|
+
(progress) => {
|
|
963
|
+
if (should_commit()) parsing_progress = progress
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
...(is_large_file ? { use_indexing: true } : {}),
|
|
967
|
+
...loading_options,
|
|
968
|
+
atom_type_mapping,
|
|
969
|
+
},
|
|
970
|
+
)
|
|
971
|
+
if (!should_commit()) return
|
|
972
|
+
trajectory = parsed_trajectory
|
|
973
|
+
if (trajectory) on_trajectory_loaded?.(trajectory)
|
|
974
|
+
// Keep original data only when parsing attached a frame_loader for on-demand loads.
|
|
975
|
+
// Direct-parse fallbacks load all frames upfront, so retaining a duplicate wastes memory.
|
|
976
|
+
orig_data = trajectory?.frame_loader ? data : null
|
|
724
977
|
|
|
725
978
|
current_step_idx = 0
|
|
726
979
|
current_filename = filename
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
trajectory,
|
|
733
|
-
frame_count:
|
|
734
|
-
total_atoms:
|
|
980
|
+
file_size = file_size_bytes
|
|
981
|
+
|
|
982
|
+
const loaded_trajectory = trajectory
|
|
983
|
+
on_file_load?.({
|
|
984
|
+
// emit file load event
|
|
985
|
+
trajectory: loaded_trajectory,
|
|
986
|
+
frame_count: loaded_trajectory?.frames.length ?? 0,
|
|
987
|
+
total_atoms: loaded_trajectory?.frames[0]?.structure.sites.length ?? 0,
|
|
735
988
|
filename,
|
|
736
989
|
file_size: file_size_bytes,
|
|
737
990
|
})
|
|
738
991
|
} catch (err) {
|
|
992
|
+
if (!should_commit()) return
|
|
739
993
|
const unsupported_message = get_unsupported_format_message(
|
|
740
994
|
filename,
|
|
741
995
|
typeof data === `string` ? data : ``,
|
|
742
996
|
)
|
|
743
997
|
error_msg = unsupported_message || `Failed to parse trajectory: ${err}`
|
|
998
|
+
on_error?.({ error_msg, filename, file_size: file_size_bytes })
|
|
744
999
|
current_filename = undefined
|
|
745
1000
|
file_size = undefined
|
|
746
|
-
|
|
747
|
-
on_error?.({ // emit error event
|
|
748
|
-
error_msg,
|
|
749
|
-
filename: current_filename || undefined,
|
|
750
|
-
file_size: file_size || undefined,
|
|
751
|
-
})
|
|
752
1001
|
} finally {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Load using indexed parsing for large files
|
|
759
|
-
async function load_with_indexing(data: string | ArrayBuffer, filename: string) {
|
|
760
|
-
try { // Use indexed parsing for efficient large file handling
|
|
761
|
-
const merged_options = {
|
|
762
|
-
use_indexing: true,
|
|
763
|
-
...loading_options,
|
|
764
|
-
atom_type_mapping,
|
|
1002
|
+
if (should_commit()) {
|
|
1003
|
+
parsing_progress = null
|
|
1004
|
+
loading = false
|
|
765
1005
|
}
|
|
766
|
-
trajectory = await parse_trajectory_async(data, filename, (progress) => {
|
|
767
|
-
parsing_progress = progress
|
|
768
|
-
}, merged_options)
|
|
769
|
-
|
|
770
|
-
// Keep original data for on-demand frame loads only when indexed parsing attached a
|
|
771
|
-
// frame_loader. Direct-parse fallbacks (e.g. large JSON or extensionless blob:
|
|
772
|
-
// filenames) load all frames upfront, so retaining a full duplicate payload wastes memory.
|
|
773
|
-
orig_data = trajectory?.frame_loader ? data : null
|
|
774
|
-
} catch (error) {
|
|
775
|
-
console.error(`Indexed loading failed:`, error)
|
|
776
|
-
throw error
|
|
777
1006
|
}
|
|
778
1007
|
}
|
|
779
1008
|
|
|
@@ -808,8 +1037,7 @@
|
|
|
808
1037
|
// Don't handle shortcuts if user is typing in an input field (but allow if it's our step input and not focused)
|
|
809
1038
|
const target = event.target instanceof HTMLElement ? event.target : null
|
|
810
1039
|
const is_step_input = target?.classList.contains(`step-input`) ?? false
|
|
811
|
-
const is_input_focused =
|
|
812
|
-
target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
1040
|
+
const is_input_focused = target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
813
1041
|
|
|
814
1042
|
// Skip if typing in an input that's not our step input
|
|
815
1043
|
if (is_input_focused && !is_step_input) return false
|
|
@@ -848,7 +1076,7 @@
|
|
|
848
1076
|
go_to_step(Math.max(0, current_step_idx - 25))
|
|
849
1077
|
} else if (event.key === `PageDown`) {
|
|
850
1078
|
go_to_step(Math.min(total_frames - 1, current_step_idx + 25))
|
|
851
|
-
}
|
|
1079
|
+
} // Interface shortcuts
|
|
852
1080
|
else if (event.key === `f` && fullscreen_toggle) toggle_fullscreen(wrapper)
|
|
853
1081
|
// 'i' key handled by the TrajectoryInfoPane's built-in toggle
|
|
854
1082
|
// Playback speed shortcuts (only when playing)
|
|
@@ -858,14 +1086,14 @@
|
|
|
858
1086
|
} else if (event.key === `-` && is_playing) {
|
|
859
1087
|
fps = Math.max(fps_range[0], fps - 0.2)
|
|
860
1088
|
on_frame_rate_change?.({ trajectory, fps })
|
|
861
|
-
}
|
|
1089
|
+
} // System shortcuts
|
|
862
1090
|
else if (event.key === `Escape`) {
|
|
863
1091
|
if (document.fullscreenElement) document.exitFullscreen()
|
|
864
1092
|
else if (view_mode_dropdown_open) view_mode_dropdown_open = false
|
|
865
1093
|
// Escape key for info pane handled by DraggablePane
|
|
866
|
-
}
|
|
1094
|
+
} // Number keys 0-9 - jump to percentage of trajectory
|
|
867
1095
|
else if (event.key >= `0` && event.key <= `9`) {
|
|
868
|
-
go_to_step(Math.floor((
|
|
1096
|
+
go_to_step(Math.floor((Number(event.key) / 10) * (total_frames - 1)))
|
|
869
1097
|
} else handled = false
|
|
870
1098
|
|
|
871
1099
|
return handled
|
|
@@ -889,8 +1117,12 @@
|
|
|
889
1117
|
|
|
890
1118
|
<div
|
|
891
1119
|
class:dragover
|
|
892
|
-
class:active={is_playing ||
|
|
893
|
-
|
|
1120
|
+
class:active={is_playing ||
|
|
1121
|
+
structure_info_open ||
|
|
1122
|
+
controls_open ||
|
|
1123
|
+
scatter_controls.open ||
|
|
1124
|
+
trajectory_export_open ||
|
|
1125
|
+
info_pane_open}
|
|
894
1126
|
bind:this={wrapper}
|
|
895
1127
|
bind:clientWidth={element_size.width}
|
|
896
1128
|
bind:clientHeight={element_size.height}
|
|
@@ -900,13 +1132,17 @@
|
|
|
900
1132
|
onmouseenter={() => (hovered = true)}
|
|
901
1133
|
onmouseleave={() => (hovered = false)}
|
|
902
1134
|
ondrop={handle_file_drop}
|
|
903
|
-
{...drag_over_handlers({
|
|
1135
|
+
{...io.drag_over_handlers({
|
|
1136
|
+
allow: () => allow_file_drop,
|
|
1137
|
+
set_dragover: (over) => (dragover = over),
|
|
1138
|
+
})}
|
|
904
1139
|
onclick={handle_click_outside}
|
|
905
1140
|
onkeydown={handle_and_prevent(onkeydown)}
|
|
906
1141
|
{...rest}
|
|
907
|
-
class=
|
|
1142
|
+
class={[`trajectory`, actual_layout, rest.class]}
|
|
908
1143
|
class:show-both-views={[`structure+scatter`, `structure+histogram`].includes(display_mode) &&
|
|
909
|
-
|
|
1144
|
+
show_plot &&
|
|
1145
|
+
show_structure}
|
|
910
1146
|
>
|
|
911
1147
|
{#if loading}
|
|
912
1148
|
{@const text = parsing_progress
|
|
@@ -918,11 +1154,7 @@
|
|
|
918
1154
|
{...spinner_props}
|
|
919
1155
|
/>
|
|
920
1156
|
{:else if error_msg}
|
|
921
|
-
<TrajectoryError
|
|
922
|
-
{error_msg}
|
|
923
|
-
on_dismiss={() => (error_msg = null)}
|
|
924
|
-
{error_snippet}
|
|
925
|
-
/>
|
|
1157
|
+
<TrajectoryError {error_msg} on_dismiss={() => (error_msg = null)} {error_snippet} />
|
|
926
1158
|
{:else if trajectory}
|
|
927
1159
|
{#if parse_warning_msg}
|
|
928
1160
|
<StatusMessage
|
|
@@ -934,17 +1166,14 @@
|
|
|
934
1166
|
{/if}
|
|
935
1167
|
<!-- Trajectory Controls -->
|
|
936
1168
|
{#if controls_config.mode !== `never`}
|
|
937
|
-
<div
|
|
938
|
-
class="trajectory-controls {controls_config.class}"
|
|
939
|
-
style={controls_config.style}
|
|
940
|
-
>
|
|
1169
|
+
<div class="trajectory-controls {controls_config.class}" style={controls_config.style}>
|
|
941
1170
|
{#if trajectory_controls}
|
|
942
1171
|
{@render trajectory_controls({
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1172
|
+
trajectory,
|
|
1173
|
+
current_step_idx,
|
|
1174
|
+
total_frames: total_frames,
|
|
1175
|
+
on_step_change: go_to_step,
|
|
1176
|
+
})}
|
|
948
1177
|
{:else}
|
|
949
1178
|
{#if current_filename && controls_config.visible(`filename`)}
|
|
950
1179
|
<button
|
|
@@ -955,7 +1184,7 @@
|
|
|
955
1184
|
if (current_filename) {
|
|
956
1185
|
navigator.clipboard.writeText(current_filename)
|
|
957
1186
|
filename_copied = true
|
|
958
|
-
setTimeout(() => filename_copied = false, 1000)
|
|
1187
|
+
setTimeout(() => (filename_copied = false), 1000)
|
|
959
1188
|
}
|
|
960
1189
|
}}
|
|
961
1190
|
>
|
|
@@ -963,7 +1192,7 @@
|
|
|
963
1192
|
{#if filename_copied}
|
|
964
1193
|
<Icon
|
|
965
1194
|
icon="Check"
|
|
966
|
-
style="color: var(--success-color); position: absolute; right: 3pt; top: 50%; transform: translateY(-50%);
|
|
1195
|
+
style="--icon-size: 16px; color: var(--success-color); position: absolute; right: 3pt; top: 50%; transform: translateY(-50%); animation: fade-in 0.1s; background: var(--surface-bg-hover); border-radius: 50%; padding: 2px; box-sizing: content-box"
|
|
967
1196
|
/>
|
|
968
1197
|
{/if}
|
|
969
1198
|
</button>
|
|
@@ -975,14 +1204,16 @@
|
|
|
975
1204
|
<button
|
|
976
1205
|
onclick={prev_step}
|
|
977
1206
|
disabled={current_step_idx === 0 || is_playing}
|
|
978
|
-
title="Previous step"
|
|
1207
|
+
title="Previous step (←) · Home: first · j: −10 · PageUp: −25"
|
|
979
1208
|
>
|
|
980
1209
|
⏮
|
|
981
1210
|
</button>
|
|
982
1211
|
<button
|
|
983
1212
|
onclick={toggle_play}
|
|
984
1213
|
disabled={total_frames <= 1}
|
|
985
|
-
title={
|
|
1214
|
+
title={`${
|
|
1215
|
+
is_playing ? `Pause` : `Play`
|
|
1216
|
+
} (Space) · ←/→ step · 0-9 jump % · +/- speed · f fullscreen`}
|
|
986
1217
|
class="play-button"
|
|
987
1218
|
class:playing={is_playing}
|
|
988
1219
|
>
|
|
@@ -991,7 +1222,7 @@
|
|
|
991
1222
|
<button
|
|
992
1223
|
onclick={next_step}
|
|
993
1224
|
disabled={current_step_idx === total_frames - 1 || is_playing}
|
|
994
|
-
title="Next step"
|
|
1225
|
+
title="Next step (→) · End: last · l: +10 · PageDown: +25"
|
|
995
1226
|
>
|
|
996
1227
|
⏭
|
|
997
1228
|
</button>
|
|
@@ -1006,6 +1237,7 @@
|
|
|
1006
1237
|
min="0"
|
|
1007
1238
|
max={total_frames - 1}
|
|
1008
1239
|
bind:value={current_step_idx}
|
|
1240
|
+
oninput={(event) => notify_step_change(event.currentTarget.valueAsNumber)}
|
|
1009
1241
|
class="step-input"
|
|
1010
1242
|
title="Enter step number to jump to"
|
|
1011
1243
|
aria-label="Step input"
|
|
@@ -1018,15 +1250,15 @@
|
|
|
1018
1250
|
min="0"
|
|
1019
1251
|
max={total_frames - 1}
|
|
1020
1252
|
bind:value={current_step_idx}
|
|
1253
|
+
oninput={(event) => notify_step_change(event.currentTarget.valueAsNumber)}
|
|
1021
1254
|
class="step-slider"
|
|
1022
1255
|
title="Drag to navigate steps"
|
|
1023
1256
|
/>
|
|
1024
1257
|
{#if step_label_positions.length > 0}
|
|
1025
1258
|
<div class="step-labels">
|
|
1026
1259
|
{#each step_label_positions as step_idx (step_idx)}
|
|
1027
|
-
{@const position_percent =
|
|
1028
|
-
|
|
1029
|
-
: 0}
|
|
1260
|
+
{@const position_percent =
|
|
1261
|
+
total_frames > 1 ? (step_idx / (total_frames - 1)) * 100 : 0}
|
|
1030
1262
|
{@const adjusted_position = 1.5 + (position_percent * (100 - 2)) / 100}
|
|
1031
1263
|
<div class="step-tick" style:left="{adjusted_position}%"></div>
|
|
1032
1264
|
<div class="step-label" style:left="{adjusted_position}%">
|
|
@@ -1039,8 +1271,8 @@
|
|
|
1039
1271
|
</div>
|
|
1040
1272
|
{/if}
|
|
1041
1273
|
|
|
1042
|
-
<!-- Frame rate control -
|
|
1043
|
-
{#if
|
|
1274
|
+
<!-- Frame rate control: shown for any multi-frame trajectory so speed can be set before play -->
|
|
1275
|
+
{#if total_frames > 1 && controls_config.visible(`fps`)}
|
|
1044
1276
|
<label
|
|
1045
1277
|
class="fps-section"
|
|
1046
1278
|
style="font-size: 0.9em; display: flex; align-items: center; gap: 5pt; margin-inline: 6pt"
|
|
@@ -1052,7 +1284,7 @@
|
|
|
1052
1284
|
max={fps_range[1]}
|
|
1053
1285
|
bind:value={fps}
|
|
1054
1286
|
title="Frame rate: {format_num(fps, `.2~s`)} fps"
|
|
1055
|
-
style="width: clamp(60px, 8cqw, 90px)
|
|
1287
|
+
style="width: clamp(60px, 8cqw, 90px)"
|
|
1056
1288
|
/>
|
|
1057
1289
|
<input
|
|
1058
1290
|
type="number"
|
|
@@ -1070,6 +1302,7 @@
|
|
|
1070
1302
|
{#if trajectory && controls_config.visible(`info-pane`)}
|
|
1071
1303
|
<TrajectoryInfoPane
|
|
1072
1304
|
{trajectory}
|
|
1305
|
+
{current_frame}
|
|
1073
1306
|
{current_step_idx}
|
|
1074
1307
|
{current_filename}
|
|
1075
1308
|
{current_file_path}
|
|
@@ -1091,8 +1324,7 @@
|
|
|
1091
1324
|
/>
|
|
1092
1325
|
{/if}
|
|
1093
1326
|
<!-- Display mode dropdown -->
|
|
1094
|
-
{#if plot_series.length > 0 &&
|
|
1095
|
-
controls_config.visible(`view-mode`)}
|
|
1327
|
+
{#if plot_series.length > 0 && controls_config.visible(`view-mode`)}
|
|
1096
1328
|
<div class="view-mode-dropdown-wrapper">
|
|
1097
1329
|
<button
|
|
1098
1330
|
onclick={() => (view_mode_dropdown_open = !view_mode_dropdown_open)}
|
|
@@ -1102,40 +1334,21 @@
|
|
|
1102
1334
|
style="background-color: transparent; padding: 0"
|
|
1103
1335
|
>
|
|
1104
1336
|
<Icon
|
|
1105
|
-
icon={(
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1337
|
+
icon={(
|
|
1338
|
+
{
|
|
1339
|
+
structure: `Atom`,
|
|
1340
|
+
'structure+scatter': `TwoColumns`,
|
|
1341
|
+
'structure+histogram': `TwoColumns`,
|
|
1342
|
+
scatter: `ScatterPlot`,
|
|
1343
|
+
histogram: `Histogram`,
|
|
1344
|
+
} as const
|
|
1345
|
+
)[display_mode]}
|
|
1112
1346
|
/>
|
|
1113
1347
|
<Icon icon={view_mode_dropdown_open ? `ArrowUp` : `ArrowDown`} />
|
|
1114
1348
|
</button>
|
|
1115
1349
|
{#if view_mode_dropdown_open}
|
|
1116
1350
|
<div class="view-mode-dropdown">
|
|
1117
|
-
{#each [
|
|
1118
|
-
{ mode: `structure`, icon: `Atom`, label: `Structure-only` },
|
|
1119
|
-
{
|
|
1120
|
-
mode: `structure+scatter`,
|
|
1121
|
-
icon: `TwoColumns`,
|
|
1122
|
-
label: `Structure + Scatter`,
|
|
1123
|
-
},
|
|
1124
|
-
{
|
|
1125
|
-
mode: `structure+histogram`,
|
|
1126
|
-
icon: `TwoColumns`,
|
|
1127
|
-
label: `Structure + Histogram`,
|
|
1128
|
-
},
|
|
1129
|
-
{ mode: `scatter`, icon: `ScatterPlot`, label: `Scatter-only` },
|
|
1130
|
-
{
|
|
1131
|
-
mode: `histogram`,
|
|
1132
|
-
icon: `Histogram`,
|
|
1133
|
-
label: `Histogram-only`,
|
|
1134
|
-
},
|
|
1135
|
-
] as const as
|
|
1136
|
-
option
|
|
1137
|
-
(option.mode)
|
|
1138
|
-
}
|
|
1351
|
+
{#each [{ mode: `structure`, icon: `Atom`, label: `Structure-only` }, { mode: `structure+scatter`, icon: `TwoColumns`, label: `Structure + Scatter` }, { mode: `structure+histogram`, icon: `TwoColumns`, label: `Structure + Histogram` }, { mode: `scatter`, icon: `ScatterPlot`, label: `Scatter-only` }, { mode: `histogram`, icon: `Histogram`, label: `Histogram-only` }] as const as option (option.mode)}
|
|
1139
1352
|
<button
|
|
1140
1353
|
class="view-mode-option"
|
|
1141
1354
|
class:selected={display_mode === option.mode}
|
|
@@ -1170,7 +1383,7 @@
|
|
|
1170
1383
|
|
|
1171
1384
|
<div
|
|
1172
1385
|
class="content-area"
|
|
1173
|
-
class:hide-plot={!
|
|
1386
|
+
class:hide-plot={!show_plot}
|
|
1174
1387
|
class:hide-structure={!show_structure}
|
|
1175
1388
|
class:show-both={[`structure+scatter`, `structure+histogram`].includes(display_mode)}
|
|
1176
1389
|
class:show-structure-only={display_mode === `structure`}
|
|
@@ -1191,8 +1404,12 @@
|
|
|
1191
1404
|
/>
|
|
1192
1405
|
{/if}
|
|
1193
1406
|
|
|
1194
|
-
{#if
|
|
1195
|
-
{#if
|
|
1407
|
+
{#if show_plot}
|
|
1408
|
+
{#if plot_metadata_loading}
|
|
1409
|
+
<div class="plot-metadata-loading plot">
|
|
1410
|
+
<Spinner text="Sampling trajectory plot data..." style="--spinner-size: 1.4em" />
|
|
1411
|
+
</div>
|
|
1412
|
+
{:else if display_mode === `scatter` || display_mode === `structure+scatter`}
|
|
1196
1413
|
<ScatterPlot
|
|
1197
1414
|
series={plot_series}
|
|
1198
1415
|
{x_axis}
|
|
@@ -1201,27 +1418,22 @@
|
|
|
1201
1418
|
controls={scatter_controls}
|
|
1202
1419
|
current_x_value={current_step_idx}
|
|
1203
1420
|
change={plot_skimming ? handle_plot_change : undefined}
|
|
1204
|
-
padding={{ t: 20, b: 60,
|
|
1421
|
+
padding={{ t: 20, b: 60, r: has_y2_series ? 100 : 20 }}
|
|
1205
1422
|
range_padding={0}
|
|
1206
1423
|
style="height: 100%"
|
|
1207
1424
|
{...scatter_props}
|
|
1208
1425
|
legend={{
|
|
1209
|
-
...scatter_props.legend ?? {},
|
|
1426
|
+
...(scatter_props.legend ?? {}),
|
|
1210
1427
|
on_toggle: (series_idx: number) => {
|
|
1211
1428
|
handle_legend_toggle(series_idx)
|
|
1212
1429
|
scatter_props.legend?.on_toggle?.(series_idx)
|
|
1213
1430
|
},
|
|
1214
1431
|
}}
|
|
1215
|
-
class=
|
|
1432
|
+
class={[`plot`, scatter_props.class]}
|
|
1216
1433
|
>
|
|
1217
|
-
{#snippet tooltip({
|
|
1218
|
-
x,
|
|
1219
|
-
y,
|
|
1220
|
-
metadata,
|
|
1221
|
-
label,
|
|
1222
|
-
}: ScatterHandlerProps)}
|
|
1434
|
+
{#snippet tooltip({ x, y, metadata, label }: ScatterHandlerProps)}
|
|
1223
1435
|
{@const formatted_y = typeof y === `number` ? format_num(y) : y}
|
|
1224
|
-
|
|
1436
|
+
{x_axis_quantity}: {Math.round(x)}<br />
|
|
1225
1437
|
{@html sanitize_html(metadata?.series_label || label || `Value`)}: {formatted_y}
|
|
1226
1438
|
{/snippet}
|
|
1227
1439
|
</ScatterPlot>
|
|
@@ -1242,7 +1454,7 @@
|
|
|
1242
1454
|
histogram_props.on_series_toggle?.(series_idx)
|
|
1243
1455
|
}}
|
|
1244
1456
|
style="height: 100%"
|
|
1245
|
-
class=
|
|
1457
|
+
class={[`plot`, histogram_props.class]}
|
|
1246
1458
|
--ctrl-btn-top="6ex"
|
|
1247
1459
|
>
|
|
1248
1460
|
{#snippet tooltip({
|
|
@@ -1266,8 +1478,8 @@
|
|
|
1266
1478
|
<EmptyState class="trajectory-empty-state">
|
|
1267
1479
|
<h3 id="load-trajectory">Load Trajectory</h3>
|
|
1268
1480
|
<p>
|
|
1269
|
-
Drop a trajectory file here (.xyz, .extxyz, .json, .json.gz, XDATCAR, .traj, .h5)
|
|
1270
|
-
|
|
1481
|
+
Drop a trajectory file here (.xyz, .extxyz, .json, .json.gz, XDATCAR, .traj, .h5) or
|
|
1482
|
+
provide trajectory data via props
|
|
1271
1483
|
</p>
|
|
1272
1484
|
<strong style="display: block; margin-block: 1em 1ex">Supported formats:</strong>
|
|
1273
1485
|
<ul>
|
|
@@ -1279,9 +1491,7 @@
|
|
|
1279
1491
|
<li>HDF5 trajectory files (.h5, .hdf5)</li>
|
|
1280
1492
|
<li>Compressed files (.gz)</li>
|
|
1281
1493
|
</ul>
|
|
1282
|
-
<p>
|
|
1283
|
-
💡 Force vectors will be automatically displayed when present in trajectory data
|
|
1284
|
-
</p>
|
|
1494
|
+
<p>💡 Force vectors will be automatically displayed when present in trajectory data</p>
|
|
1285
1495
|
</EmptyState>
|
|
1286
1496
|
{/if}
|
|
1287
1497
|
</div>
|
|
@@ -1299,9 +1509,6 @@
|
|
|
1299
1509
|
contain: layout;
|
|
1300
1510
|
z-index: var(--traj-z-index, 1);
|
|
1301
1511
|
container-type: size; /* enable cqh for panes if explicit height is set */
|
|
1302
|
-
:global(.plot) {
|
|
1303
|
-
background: var(--surface-bg);
|
|
1304
|
-
}
|
|
1305
1512
|
&.active {
|
|
1306
1513
|
z-index: 2; /* needed so info/control panes from an active viewer overlay those of the next (if there is one) */
|
|
1307
1514
|
.trajectory-controls {
|
|
@@ -1350,10 +1557,19 @@
|
|
|
1350
1557
|
grid-template-rows: 1fr !important;
|
|
1351
1558
|
}
|
|
1352
1559
|
}
|
|
1560
|
+
.plot-metadata-loading {
|
|
1561
|
+
display: flex;
|
|
1562
|
+
align-items: center;
|
|
1563
|
+
justify-content: center;
|
|
1564
|
+
gap: 0.6em;
|
|
1565
|
+
min-height: 0;
|
|
1566
|
+
color: var(--text-muted, currentColor);
|
|
1567
|
+
background: var(--surface-bg);
|
|
1568
|
+
}
|
|
1353
1569
|
.trajectory-controls {
|
|
1354
1570
|
display: flex;
|
|
1355
1571
|
align-items: center;
|
|
1356
|
-
gap: clamp(
|
|
1572
|
+
gap: clamp(4pt, 1.6cqw, 1.5ex);
|
|
1357
1573
|
padding: clamp(2pt, 0.5cqw, 1ex) clamp(4pt, 1cqw, 1.2ex);
|
|
1358
1574
|
background: var(--surface-bg-hover);
|
|
1359
1575
|
backdrop-filter: blur(4px);
|
|
@@ -1373,7 +1589,7 @@
|
|
|
1373
1589
|
}
|
|
1374
1590
|
button {
|
|
1375
1591
|
background: var(--btn-bg);
|
|
1376
|
-
font-size: clamp(0.
|
|
1592
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
1377
1593
|
&:hover:not(:disabled) {
|
|
1378
1594
|
background: var(--btn-bg-hover);
|
|
1379
1595
|
}
|
|
@@ -1389,7 +1605,14 @@
|
|
|
1389
1605
|
.nav-section {
|
|
1390
1606
|
display: flex;
|
|
1391
1607
|
align-items: center;
|
|
1392
|
-
gap: clamp(
|
|
1608
|
+
gap: clamp(3pt, 1cqw, 9pt);
|
|
1609
|
+
}
|
|
1610
|
+
/* nudge control-button icons slightly larger via transform */
|
|
1611
|
+
.nav-section button,
|
|
1612
|
+
.view-mode-button,
|
|
1613
|
+
.info-section
|
|
1614
|
+
:global(:is(.trajectory-info-toggle, .trajectory-export-toggle, .fullscreen-button)) {
|
|
1615
|
+
transform: scale(1.15);
|
|
1393
1616
|
}
|
|
1394
1617
|
.step-section {
|
|
1395
1618
|
display: flex;
|
|
@@ -1411,7 +1634,8 @@
|
|
|
1411
1634
|
}
|
|
1412
1635
|
.step-slider {
|
|
1413
1636
|
width: 100%;
|
|
1414
|
-
|
|
1637
|
+
position: relative;
|
|
1638
|
+
z-index: 1; /* keep the slider knob above the step labels (which follow it in the DOM) */
|
|
1415
1639
|
}
|
|
1416
1640
|
.step-labels {
|
|
1417
1641
|
position: absolute;
|
|
@@ -1469,7 +1693,7 @@
|
|
|
1469
1693
|
position: relative;
|
|
1470
1694
|
}
|
|
1471
1695
|
.info-section :global(:is(.trajectory-info-toggle, .trajectory-export-toggle)) {
|
|
1472
|
-
font-size:
|
|
1696
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
1473
1697
|
}
|
|
1474
1698
|
.play-button {
|
|
1475
1699
|
min-width: clamp(32px, 4cqw, 36px);
|
|
@@ -1479,10 +1703,7 @@
|
|
|
1479
1703
|
&.playing {
|
|
1480
1704
|
background: var(--traj-pause-btn-bg, var(--btn-bg, rgba(0, 0, 0, 0.1)));
|
|
1481
1705
|
&:hover:not(:disabled) {
|
|
1482
|
-
background: var(
|
|
1483
|
-
--traj-pause-btn-bg-hover,
|
|
1484
|
-
var(--btn-bg-hover, rgba(0, 0, 0, 0.1))
|
|
1485
|
-
);
|
|
1706
|
+
background: var(--traj-pause-btn-bg-hover, var(--btn-bg-hover, rgba(0, 0, 0, 0.1)));
|
|
1486
1707
|
}
|
|
1487
1708
|
}
|
|
1488
1709
|
}
|
|
@@ -1530,14 +1751,20 @@
|
|
|
1530
1751
|
.view-mode-dropdown-wrapper {
|
|
1531
1752
|
display: flex;
|
|
1532
1753
|
position: relative;
|
|
1754
|
+
z-index: var(--trajectory-view-mode-z-index, 20);
|
|
1533
1755
|
}
|
|
1534
1756
|
.view-mode-dropdown {
|
|
1535
1757
|
position: absolute;
|
|
1536
1758
|
top: 115%;
|
|
1537
1759
|
right: 0;
|
|
1538
|
-
|
|
1760
|
+
z-index: 1;
|
|
1761
|
+
min-width: max-content;
|
|
1762
|
+
background: var(--trajectory-view-mode-bg, light-dark(#fff, #2f3137));
|
|
1539
1763
|
border-radius: 4px;
|
|
1540
|
-
box-shadow:
|
|
1764
|
+
box-shadow:
|
|
1765
|
+
0 8px 16px -4px rgba(0, 0, 0, 0.3),
|
|
1766
|
+
0 4px 8px -2px rgba(0, 0, 0, 0.1);
|
|
1767
|
+
pointer-events: auto;
|
|
1541
1768
|
}
|
|
1542
1769
|
.view-mode-option {
|
|
1543
1770
|
display: flex;
|