matterviz 0.4.0 → 0.4.2
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 +12 -14
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/app.css +53 -0
- package/dist/brillouin/BrillouinZone.svelte +132 -219
- package/dist/brillouin/BrillouinZone.svelte.d.ts +9 -20
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +47 -96
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +15 -42
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
- package/dist/brillouin/BrillouinZoneScene.svelte +63 -230
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +3 -23
- package/dist/brillouin/ReciprocalVectors.svelte +39 -0
- package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
- package/dist/brillouin/compute.d.ts +2 -0
- package/dist/brillouin/compute.js +80 -77
- package/dist/brillouin/geometry.d.ts +8 -0
- package/dist/brillouin/geometry.js +57 -0
- package/dist/brillouin/index.d.ts +2 -0
- package/dist/brillouin/index.js +2 -0
- package/dist/brillouin/types.d.ts +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +143 -246
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +652 -976
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
- package/dist/chempot-diagram/async-compute.svelte.js +1 -1
- package/dist/chempot-diagram/color.d.ts +3 -6
- package/dist/chempot-diagram/color.js +5 -5
- package/dist/chempot-diagram/compute.d.ts +3 -3
- package/dist/chempot-diagram/compute.js +3 -1
- package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
- package/dist/chempot-diagram/controls-state.svelte.js +42 -0
- package/dist/chempot-diagram/export.d.ts +47 -0
- package/dist/chempot-diagram/export.js +133 -0
- package/dist/chempot-diagram/index.d.ts +1 -0
- package/dist/chempot-diagram/index.js +1 -0
- package/dist/chempot-diagram/pointer.d.ts +0 -10
- package/dist/chempot-diagram/pointer.js +4 -4
- package/dist/chempot-diagram/types.d.ts +3 -3
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +21 -22
- package/dist/composition/BarChart.svelte +37 -31
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +7 -11
- package/dist/composition/Formula.svelte +18 -25
- package/dist/composition/FormulaFilter.svelte +96 -98
- package/dist/composition/PieChart.svelte +60 -46
- package/dist/composition/chem-sys.js +3 -2
- package/dist/composition/format.js +4 -3
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -3
- package/dist/composition/parse.js +27 -28
- package/dist/constants.js +11 -9
- package/dist/controls.d.ts +1 -0
- package/dist/controls.js +0 -1
- package/dist/convex-hull/ConvexHull.svelte +20 -25
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
- package/dist/convex-hull/ConvexHull2D.svelte +176 -256
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +269 -804
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +236 -755
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +264 -0
- package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
- package/dist/convex-hull/ConvexHullControls.svelte +128 -49
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
- package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
- package/dist/convex-hull/ConvexHullStats.svelte +210 -351
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +16 -14
- package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +17 -21
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/barycentric-coords.d.ts +2 -4
- package/dist/convex-hull/barycentric-coords.js +6 -33
- package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
- package/dist/convex-hull/canvas-interactions.svelte.js +279 -0
- package/dist/convex-hull/helpers.d.ts +47 -17
- package/dist/convex-hull/helpers.js +186 -101
- package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
- package/dist/convex-hull/hull-state.svelte.js +124 -0
- package/dist/convex-hull/index.d.ts +9 -7
- package/dist/convex-hull/index.js +7 -2
- package/dist/convex-hull/thermodynamics.js +109 -937
- package/dist/convex-hull/types.d.ts +12 -4
- package/dist/convex-hull/types.js +12 -0
- package/dist/coordination/CoordinationBarPlot.svelte +41 -43
- 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 +6 -8
- 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 +24 -29
- package/dist/element/ElementTile.svelte.d.ts +1 -1
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +1 -2
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +1 -0
- package/dist/element/index.js +3 -0
- package/dist/feedback/DragOverlay.svelte +5 -3
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -1
- package/dist/feedback/StatusMessage.svelte +20 -7
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +78 -165
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +9 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceScene.svelte +93 -272
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +14 -13
- package/dist/fermi-surface/export.js +4 -15
- package/dist/fermi-surface/index.d.ts +0 -1
- package/dist/fermi-surface/index.js +0 -1
- package/dist/fermi-surface/parse.d.ts +1 -1
- package/dist/fermi-surface/parse.js +84 -97
- package/dist/fermi-surface/types.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +161 -185
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +6 -5
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +12 -9
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
- package/dist/heatmap-matrix/index.d.ts +8 -2
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/io/ExportPane.svelte +170 -0
- package/dist/io/ExportPane.svelte.d.ts +17 -0
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +22 -36
- package/dist/io/export.d.ts +9 -1
- package/dist/io/export.js +37 -29
- package/dist/io/fetch.d.ts +2 -4
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +13 -0
- package/dist/io/index.d.ts +1 -0
- package/dist/io/index.js +3 -0
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/types.d.ts +13 -0
- package/dist/io/url-drop.js +13 -25
- package/dist/isosurface/Isosurface.svelte +17 -23
- package/dist/isosurface/IsosurfaceControls.svelte +37 -42
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +50 -48
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +34 -0
- package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
- package/dist/layout/FullscreenToggle.svelte +12 -12
- package/dist/layout/InfoCard.svelte +7 -18
- 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 +21 -17
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +115 -0
- package/dist/layout/ViewerChrome.svelte.d.ts +18 -0
- package/dist/layout/fullscreen.d.ts +4 -0
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.d.ts +8 -0
- package/dist/layout/fullscreen.svelte.js +36 -0
- package/dist/layout/index.d.ts +4 -0
- package/dist/layout/index.js +4 -0
- package/dist/layout/json-tree/JsonNode.svelte +8 -11
- package/dist/layout/json-tree/JsonTree.svelte +53 -77
- package/dist/layout/json-tree/JsonValue.svelte +7 -15
- package/dist/layout/json-tree/utils.js +14 -23
- package/dist/math.d.ts +9 -3
- package/dist/math.js +37 -21
- package/dist/overlays/ContextMenu.svelte +4 -2
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +81 -26
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -0
- package/dist/overlays/GlassChip.svelte +30 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +11 -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 +115 -146
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +11 -63
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +50 -71
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +73 -96
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +49 -134
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +3 -3
- package/dist/phase-diagram/colors.js +1 -1
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.d.ts +2 -1
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +21 -15
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +494 -692
- package/dist/plot/bar/BarPlot.svelte.d.ts +9 -15
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +5 -7
- package/dist/plot/bar/SpacegroupBarPlot.svelte +31 -37
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +361 -439
- package/dist/plot/box/BoxPlot.svelte.d.ts +6 -3
- package/dist/plot/box/BoxPlotControls.svelte.d.ts +1 -1
- package/dist/plot/box/Violin.svelte.d.ts +2 -1
- package/dist/plot/box/box-plot.d.ts +3 -2
- package/dist/plot/box/box-plot.js +6 -3
- package/dist/plot/box/kde.d.ts +2 -1
- package/dist/plot/box/kde.js +4 -4
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +5 -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 +106 -111
- package/dist/plot/core/components/ColorBar.svelte.d.ts +5 -4
- 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 +21 -11
- package/dist/plot/core/components/Line.svelte.d.ts +5 -2
- package/dist/plot/core/components/PlotAxis.svelte +42 -18
- package/dist/plot/core/components/PlotAxis.svelte.d.ts +2 -1
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotControls.svelte.d.ts +1 -1
- 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 +56 -59
- package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +4 -4
- package/dist/plot/core/components/ReferencePlane.svelte +9 -10
- package/dist/plot/core/components/ReferencePlane.svelte.d.ts +4 -4
- 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 +29 -476
- package/dist/plot/core/data-transform.js +9 -5
- package/dist/plot/core/fill-utils.d.ts +7 -4
- package/dist/plot/core/fill-utils.js +34 -9
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.d.ts +5 -1
- package/dist/plot/core/interactions.js +14 -2
- package/dist/plot/core/layout.d.ts +3 -1
- package/dist/plot/core/layout.js +22 -13
- 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 +35 -0
- package/dist/plot/core/pan-zoom.svelte.js +221 -0
- package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -0
- package/dist/plot/core/reference-line.d.ts +10 -10
- package/dist/plot/core/reference-line.js +7 -7
- package/dist/plot/core/scales.d.ts +40 -25
- package/dist/plot/core/scales.js +49 -21
- package/dist/plot/core/svg.d.ts +2 -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 +47 -331
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +51 -0
- package/dist/plot/core/utils/hierarchy-chart.js +179 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +99 -0
- package/dist/plot/core/utils/label-placement.d.ts +1 -1
- package/dist/plot/core/utils/label-placement.js +5 -5
- package/dist/plot/core/utils.d.ts +2 -1
- package/dist/plot/histogram/Histogram.svelte +373 -631
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/HistogramControls.svelte.d.ts +1 -1
- 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 +79 -73
- package/dist/plot/sankey/Sankey.svelte.d.ts +3 -3
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +3 -1
- package/dist/plot/scatter/BinnedScatterPlot.svelte +264 -112
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +11 -4
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +623 -821
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +9 -15
- 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/binned-scatter-types.d.ts +6 -12
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/index.d.ts +1 -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 +40 -60
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +6 -14
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +17 -15
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +5 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +232 -234
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +5 -14
- package/dist/plot/scatter-3d/Surface3D.svelte +9 -17
- package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +4 -3
- package/dist/plot/sunburst/Sunburst.svelte +256 -338
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +4 -3
- 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 +14 -4
- package/dist/plot/treemap/Treemap.svelte +907 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +84 -0
- package/dist/plot/treemap/index.d.ts +3 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/treemap.d.ts +26 -0
- package/dist/plot/treemap/treemap.js +99 -0
- package/dist/rdf/RdfPlot.svelte +22 -26
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +4 -8
- package/dist/sanitize.js +14 -3
- package/dist/scene/SceneCamera.svelte +62 -0
- package/dist/scene/SceneCamera.svelte.d.ts +19 -0
- package/dist/scene/bind-renderer.svelte.d.ts +2 -0
- package/dist/scene/bind-renderer.svelte.js +22 -0
- package/dist/scene/index.d.ts +4 -0
- package/dist/scene/index.js +5 -0
- package/dist/scene/props.js +52 -0
- package/dist/scene/types.d.ts +26 -0
- package/dist/scene/types.js +1 -0
- package/dist/settings.d.ts +33 -11
- package/dist/settings.js +136 -51
- package/dist/spectral/Bands.svelte +231 -237
- package/dist/spectral/Bands.svelte.d.ts +3 -2
- package/dist/spectral/BandsAndDos.svelte +29 -29
- package/dist/spectral/BrillouinBandsDos.svelte +21 -29
- package/dist/spectral/Dos.svelte +75 -77
- package/dist/spectral/Dos.svelte.d.ts +2 -1
- package/dist/spectral/helpers.d.ts +9 -9
- package/dist/spectral/helpers.js +66 -70
- package/dist/state.svelte.d.ts +0 -7
- package/dist/state.svelte.js +5 -24
- package/dist/structure/Arrow.svelte +6 -21
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +115 -75
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +16 -3
- package/dist/structure/CellSelect.svelte +22 -29
- package/dist/structure/CellSelect.svelte.d.ts +2 -1
- 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 +725 -735
- package/dist/structure/Structure.svelte.d.ts +8 -16
- package/dist/structure/StructureCarousel.svelte +630 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +298 -325
- package/dist/structure/StructureControls.svelte.d.ts +5 -3
- package/dist/structure/StructureExportPane.svelte +62 -168
- package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
- package/dist/structure/StructureInfoPane.svelte +77 -69
- package/dist/structure/StructureInfoPane.svelte.d.ts +6 -6
- package/dist/structure/StructureScene.svelte +758 -600
- package/dist/structure/StructureScene.svelte.d.ts +24 -20
- package/dist/structure/StructureViewport.svelte +386 -0
- package/dist/structure/StructureViewport.svelte.d.ts +63 -0
- package/dist/structure/atom-label-placement.d.ts +18 -0
- package/dist/structure/atom-label-placement.js +115 -0
- package/dist/structure/atom-properties.d.ts +3 -1
- package/dist/structure/atom-properties.js +115 -54
- package/dist/structure/bond-order-perception.js +2 -4
- package/dist/structure/bonding.d.ts +12 -2
- package/dist/structure/bonding.js +172 -73
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +25 -4
- package/dist/structure/export.js +103 -153
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +22 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +18 -1
- package/dist/structure/index.js +15 -2
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +5 -3
- package/dist/structure/parse.js +551 -567
- package/dist/structure/partial-occupancy.d.ts +0 -1
- package/dist/structure/partial-occupancy.js +5 -3
- package/dist/structure/pbc.d.ts +1 -1
- package/dist/structure/pbc.js +204 -51
- package/dist/structure/polyhedra.d.ts +40 -0
- package/dist/structure/polyhedra.js +587 -0
- package/dist/structure/site.d.ts +4 -0
- package/dist/structure/site.js +1 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +10 -15
- package/dist/structure/validation.js +5 -6
- package/dist/symmetry/SymmetryElementControls.svelte +69 -0
- package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
- package/dist/symmetry/SymmetryElements.svelte +350 -0
- package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
- package/dist/symmetry/SymmetryStats.svelte +142 -49
- package/dist/symmetry/WyckoffTable.svelte +58 -16
- package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
- package/dist/symmetry/cell-transform.js +8 -14
- package/dist/symmetry/index.d.ts +15 -4
- package/dist/symmetry/index.js +308 -81
- package/dist/symmetry/spacegroups.d.ts +5 -1
- package/dist/symmetry/spacegroups.js +18 -3
- package/dist/symmetry/symmetry-elements.d.ts +33 -0
- package/dist/symmetry/symmetry-elements.js +521 -0
- package/dist/symmetry/wyckoff-db.d.ts +9 -0
- package/dist/symmetry/wyckoff-db.js +87 -0
- package/dist/table/HeatmapTable.svelte +1104 -378
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +5 -12
- 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/index.d.ts +1 -5
- package/dist/theme/index.js +4 -10
- package/dist/theme/themes.mjs +14 -7
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +2 -5
- package/dist/trajectory/Trajectory.svelte +556 -352
- package/dist/trajectory/Trajectory.svelte.d.ts +10 -22
- package/dist/trajectory/TrajectoryError.svelte +11 -8
- package/dist/trajectory/TrajectoryExportPane.svelte +28 -54
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
- package/dist/trajectory/TrajectoryInfoPane.svelte +88 -99
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
- package/dist/trajectory/constants.js +6 -2
- package/dist/trajectory/extract.js +73 -71
- package/dist/trajectory/format-detect.d.ts +0 -1
- package/dist/trajectory/format-detect.js +13 -17
- package/dist/trajectory/frame-reader.d.ts +2 -1
- package/dist/trajectory/frame-reader.js +74 -136
- package/dist/trajectory/helpers.d.ts +12 -2
- package/dist/trajectory/helpers.js +68 -37
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +3 -0
- package/dist/trajectory/parse/ase.d.ts +9 -1
- package/dist/trajectory/parse/ase.js +47 -32
- package/dist/trajectory/parse/diagnostics.d.ts +3 -0
- package/dist/trajectory/parse/diagnostics.js +14 -0
- 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 +3 -4
- package/dist/trajectory/parse/index.js +68 -119
- package/dist/trajectory/parse/lammps.d.ts +0 -2
- package/dist/trajectory/parse/lammps.js +14 -16
- package/dist/trajectory/parse/pymatgen.d.ts +2 -0
- package/dist/trajectory/parse/pymatgen.js +74 -0
- package/dist/trajectory/parse/vasp.js +13 -9
- 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 +5 -0
- package/dist/trajectory/parse/vaspout-h5.js +284 -0
- package/dist/trajectory/parse/xyz.d.ts +9 -21
- package/dist/trajectory/parse/xyz.js +38 -39
- package/dist/trajectory/plotting.d.ts +8 -10
- package/dist/trajectory/plotting.js +102 -127
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +8 -1
- package/dist/xrd/XrdPlot.svelte +56 -85
- package/dist/xrd/broadening.d.ts +2 -1
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +8 -15
- package/dist/xrd/index.d.ts +2 -2
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +56 -73
- package/package.json +77 -36
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/element/data.json +0 -11864
- package/dist/fermi-surface/marching-cubes.d.ts +0 -2
- package/dist/fermi-surface/marching-cubes.js +0 -2
- package/dist/plot/core/hover-lock.svelte.d.ts +0 -14
- package/dist/plot/core/hover-lock.svelte.js +0 -45
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/structure/label-placement.d.ts +0 -14
- package/dist/structure/label-placement.js +0 -72
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
3
|
-
import { normalize_show_controls } from '../controls'
|
|
2
|
+
import { normalize_show_controls, type ShowControlsProp } from '../controls'
|
|
4
3
|
import type { ElementSymbol } from '../element'
|
|
5
4
|
import EmptyState from '../EmptyState.svelte'
|
|
5
|
+
import { StatusMessage } from '../feedback'
|
|
6
6
|
import Spinner from '../feedback/Spinner.svelte'
|
|
7
7
|
import Icon from '../Icon.svelte'
|
|
8
|
-
import { handle_url_drop, load_from_url } from '../io'
|
|
8
|
+
import { drag_over_handlers, handle_url_drop, load_from_url } 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
|
+
import type { Vec2 } from '../math'
|
|
11
12
|
import { sanitize_html } from '../sanitize'
|
|
12
|
-
import { toggle_fullscreen } from '../layout'
|
|
13
|
+
import { FullscreenButton, type FullscreenToggleProp, toggle_fullscreen } from '../layout'
|
|
14
|
+
import { sync_fullscreen } from '../layout/fullscreen.svelte'
|
|
13
15
|
import type { ControlsConfig, DataSeries, Orientation, Point } from '../plot'
|
|
14
16
|
import type { ScatterHandlerProps } from '../plot/core/types'
|
|
15
17
|
import { Histogram, ScatterPlot } from '../plot'
|
|
@@ -18,18 +20,25 @@
|
|
|
18
20
|
import Structure from '../structure/Structure.svelte'
|
|
19
21
|
import { scaleLinear } from 'd3-scale'
|
|
20
22
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
21
|
-
import { untrack } from 'svelte'
|
|
23
|
+
import { onMount, untrack } from 'svelte'
|
|
22
24
|
import { tooltip } from 'svelte-multiselect/attachments'
|
|
23
25
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
26
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
24
27
|
import { full_data_extractor } from './extract'
|
|
25
28
|
import type {
|
|
26
29
|
ParseProgress,
|
|
27
30
|
TrajectoryDataExtractor,
|
|
28
31
|
TrajectoryFrame,
|
|
32
|
+
TrajectoryMetadata,
|
|
29
33
|
TrajectoryType,
|
|
30
34
|
TrajHandlerData,
|
|
31
35
|
} from './index'
|
|
32
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
FRAME_LOAD_DEBOUNCE_MS,
|
|
38
|
+
TrajectoryError,
|
|
39
|
+
TrajectoryExportPane,
|
|
40
|
+
TrajectoryInfoPane,
|
|
41
|
+
} from './index'
|
|
33
42
|
import type { AtomTypeMapping, LoadingOptions } from './parse'
|
|
34
43
|
import {
|
|
35
44
|
get_unsupported_format_message,
|
|
@@ -39,6 +48,7 @@
|
|
|
39
48
|
} from './parse'
|
|
40
49
|
import {
|
|
41
50
|
generate_axis_labels,
|
|
51
|
+
generate_axis_scale_types,
|
|
42
52
|
generate_plot_series,
|
|
43
53
|
generate_streaming_plot_series,
|
|
44
54
|
should_hide_plot,
|
|
@@ -62,6 +72,12 @@
|
|
|
62
72
|
total_frames: number
|
|
63
73
|
on_step_change: (idx: number) => void
|
|
64
74
|
}
|
|
75
|
+
type PlotMetadataStreamMessage = {
|
|
76
|
+
command?: string
|
|
77
|
+
file_path?: string
|
|
78
|
+
plot_metadata?: TrajectoryMetadata[]
|
|
79
|
+
is_complete?: boolean
|
|
80
|
+
}
|
|
65
81
|
|
|
66
82
|
let {
|
|
67
83
|
trajectory = $bindable(),
|
|
@@ -99,116 +115,120 @@
|
|
|
99
115
|
atom_type_mapping,
|
|
100
116
|
plot_skimming = true,
|
|
101
117
|
hovered = $bindable(false),
|
|
118
|
+
controls_open = $bindable(false),
|
|
119
|
+
info_pane_open = $bindable(false),
|
|
120
|
+
wrapper = $bindable(),
|
|
121
|
+
fullscreen = $bindable(false),
|
|
102
122
|
...rest
|
|
103
|
-
}: EventHandlers &
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
loading_options?: LoadingOptions
|
|
184
|
-
// Map LAMMPS atom types to element symbols (e.g. {1: 'Na', 2: 'Cl'})
|
|
185
|
-
atom_type_mapping?: AtomTypeMapping
|
|
186
|
-
// Disable plot skimming (mouse over plot doesn't update structure/step slider)
|
|
187
|
-
plot_skimming?: boolean
|
|
188
|
-
// bindable: true while the pointer is over the viewer (drives hover-scoped shortcuts)
|
|
189
|
-
hovered?: boolean
|
|
190
|
-
} = $props()
|
|
123
|
+
}: EventHandlers &
|
|
124
|
+
HTMLAttributes<HTMLDivElement> & {
|
|
125
|
+
// trajectory data - can be provided directly or loaded from file
|
|
126
|
+
trajectory?: TrajectoryType
|
|
127
|
+
// URL to load trajectory from (alternative to providing trajectory directly)
|
|
128
|
+
data_url?: string
|
|
129
|
+
// current step index being displayed
|
|
130
|
+
current_step_idx?: number
|
|
131
|
+
// custom function to extract plot data from trajectory frames
|
|
132
|
+
data_extractor?: TrajectoryDataExtractor
|
|
133
|
+
|
|
134
|
+
// file drop handlers
|
|
135
|
+
allow_file_drop?: boolean
|
|
136
|
+
// layout configuration - 'auto' (default) adapts to element size, 'horizontal'/'vertical' forces layout
|
|
137
|
+
layout?: `auto` | Orientation
|
|
138
|
+
// structure viewer props (passed to Structure component)
|
|
139
|
+
structure_props?: ComponentProps<typeof Structure>
|
|
140
|
+
// plot props (passed to ScatterPlot component)
|
|
141
|
+
scatter_props?: ComponentProps<typeof ScatterPlot>
|
|
142
|
+
// histogram props (passed to Histogram component, excluding series which is handled separately)
|
|
143
|
+
histogram_props?: Omit<ComponentProps<typeof Histogram>, `series`>
|
|
144
|
+
// spinner props (passed to Spinner component)
|
|
145
|
+
spinner_props?: ComponentProps<typeof Spinner>
|
|
146
|
+
// custom snippets for additional UI elements
|
|
147
|
+
trajectory_controls?: Snippet<[ControlsProps]>
|
|
148
|
+
// Custom error snippet for advanced error handling
|
|
149
|
+
error_snippet?: Snippet<[{ error_msg: string; on_dismiss: () => void }]>
|
|
150
|
+
// Controls visibility configuration.
|
|
151
|
+
// - 'always': controls always visible
|
|
152
|
+
// - 'hover': controls visible on component hover (default)
|
|
153
|
+
// - 'never': controls never visible
|
|
154
|
+
// - object: { mode, hidden, style } for fine-grained control
|
|
155
|
+
// Control names: 'filename', 'nav', 'step', 'fps', 'info-pane', 'export-pane', 'view-mode', 'fullscreen'
|
|
156
|
+
show_controls?: ShowControlsProp
|
|
157
|
+
// show/hide the fullscreen button
|
|
158
|
+
fullscreen_toggle?: FullscreenToggleProp
|
|
159
|
+
// automatically start playing when trajectory data is loaded
|
|
160
|
+
auto_play?: boolean
|
|
161
|
+
// display mode: 'structure+scatter' (default), 'structure' (only structure), 'scatter' (only scatter), 'histogram' (only histogram), 'structure+histogram' (structure with histogram)
|
|
162
|
+
display_mode?:
|
|
163
|
+
| `structure+scatter`
|
|
164
|
+
| `structure`
|
|
165
|
+
| `scatter`
|
|
166
|
+
| `histogram`
|
|
167
|
+
| `structure+histogram`
|
|
168
|
+
// step labels configuration for slider
|
|
169
|
+
// - positive number: number of evenly spaced ticks
|
|
170
|
+
// - negative number: spacing between ticks (e.g. -10 = every 10th step)
|
|
171
|
+
// - array: exact step indices to label
|
|
172
|
+
// - undefined: no labels
|
|
173
|
+
step_labels?: number | number[]
|
|
174
|
+
// visible properties - bindable array of property keys currently shown in the plot
|
|
175
|
+
// - controls which trajectory properties are plotted (e.g. ['energy', 'volume', 'force_max'])
|
|
176
|
+
// - bindable: reflects current visibility state and can be used for external control
|
|
177
|
+
// - if not provided, uses default visible properties (energy, force_max, stress_frobenius)
|
|
178
|
+
// - if specified properties don't exist in data, falls back to automatic selection
|
|
179
|
+
visible_properties?: string[]
|
|
180
|
+
// custom labels for trajectory properties - maps property keys to display labels
|
|
181
|
+
// - e.g. {energy: 'Total Energy', volume: 'Cell Volume', force_max: 'Max Force'}
|
|
182
|
+
// - merged with built-in trajectory_property_config
|
|
183
|
+
ELEM_PROPERTY_LABELS?: Record<string, string>
|
|
184
|
+
fps_range?: Vec2 // allowed FPS range [min_fps, max_fps]
|
|
185
|
+
fps?: number // frame rate for playback
|
|
186
|
+
// Loading options for large files
|
|
187
|
+
loading_options?: LoadingOptions
|
|
188
|
+
// Map LAMMPS atom types to element symbols (e.g. {1: 'Na', 2: 'Cl'})
|
|
189
|
+
atom_type_mapping?: AtomTypeMapping
|
|
190
|
+
// Disable plot skimming (mouse over plot doesn't update structure/step slider)
|
|
191
|
+
plot_skimming?: boolean
|
|
192
|
+
// bindable: true while the pointer is over the viewer (drives hover-scoped shortcuts)
|
|
193
|
+
hovered?: boolean
|
|
194
|
+
// bindable: whether the (structure) controls pane is currently open
|
|
195
|
+
controls_open?: boolean
|
|
196
|
+
// bindable: whether the trajectory info pane is currently open
|
|
197
|
+
info_pane_open?: boolean
|
|
198
|
+
// bindable: top-level wrapper element
|
|
199
|
+
wrapper?: HTMLDivElement
|
|
200
|
+
// bindable: fullscreen state
|
|
201
|
+
fullscreen?: boolean
|
|
202
|
+
} = $props()
|
|
191
203
|
|
|
192
204
|
let dragover = $state(false)
|
|
193
205
|
let loading = $state(false)
|
|
194
206
|
let error_msg = $state<string | null>(null)
|
|
207
|
+
// Non-fatal parse warnings from trajectory metadata (set by parser via attach_parse_warnings); dismissible
|
|
208
|
+
let parse_warning_msg = $state<string | undefined>(undefined)
|
|
209
|
+
$effect(() => {
|
|
210
|
+
const warnings = trajectory?.metadata?.parse_warnings
|
|
211
|
+
parse_warning_msg =
|
|
212
|
+
Array.isArray(warnings) && warnings.length > 0
|
|
213
|
+
? `${warnings.length} parse warning${warnings.length > 1 ? `s` : ``}: ${warnings.join(
|
|
214
|
+
`; `,
|
|
215
|
+
)}`
|
|
216
|
+
: undefined
|
|
217
|
+
})
|
|
195
218
|
let is_playing = $state(false)
|
|
196
|
-
|
|
219
|
+
// requestAnimationFrame handle for the playback loop (rAF auto-pauses in background tabs and
|
|
220
|
+
// self-throttles, unlike setInterval which drifts and queues work when a frame render overruns)
|
|
221
|
+
let play_raf: number | undefined
|
|
197
222
|
|
|
198
223
|
// Ensure fps is within the allowed range
|
|
199
224
|
$effect(() => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
} else if (fps > fps_range[1]) {
|
|
203
|
-
fps = fps_range[1]
|
|
204
|
-
}
|
|
225
|
+
const clamped = Math.max(fps_range[0], Math.min(fps_range[1], fps))
|
|
226
|
+
if (clamped !== fps) fps = clamped
|
|
205
227
|
})
|
|
206
228
|
let current_filename = $state<string | undefined>(undefined)
|
|
207
229
|
let current_file_path = $state<string | null>(null)
|
|
208
230
|
let file_size = $state<number | undefined>(undefined)
|
|
209
231
|
let file_object = $state<File | null>(null)
|
|
210
|
-
let wrapper = $state<HTMLDivElement | undefined>(undefined)
|
|
211
|
-
let info_pane_open = $state(false)
|
|
212
232
|
let parsing_progress = $state<ParseProgress | null>(null)
|
|
213
233
|
let element_size = $state({ width: 0, height: 0 })
|
|
214
234
|
let filename_copied = $state(false)
|
|
@@ -227,13 +247,26 @@
|
|
|
227
247
|
})
|
|
228
248
|
|
|
229
249
|
// Get total frame count (supports both regular and indexed trajectories)
|
|
230
|
-
let total_frames = $derived(
|
|
231
|
-
|
|
232
|
-
|
|
250
|
+
let total_frames = $derived(trajectory?.total_frames || trajectory?.frames.length || 0)
|
|
251
|
+
|
|
252
|
+
// Clamp out-of-range step indices (e.g. an initial current_step_idx of
|
|
253
|
+
// Number.MAX_SAFE_INTEGER means "jump to the last frame", used by hosts
|
|
254
|
+
// restoring viewer position across trajectory reloads). Hosts are notified
|
|
255
|
+
// of the correction so their recorded position matches the shown frame.
|
|
256
|
+
$effect(() => {
|
|
257
|
+
if (total_frames > 0 && current_step_idx > total_frames - 1) {
|
|
258
|
+
current_step_idx = total_frames - 1
|
|
259
|
+
notify_step_change()
|
|
260
|
+
} else if (current_step_idx < 0) {
|
|
261
|
+
current_step_idx = 0
|
|
262
|
+
notify_step_change()
|
|
263
|
+
}
|
|
264
|
+
})
|
|
233
265
|
|
|
234
266
|
// Current frame - load on demand for indexed trajectories
|
|
235
267
|
let current_frame = $state<TrajectoryFrame | null>(null)
|
|
236
268
|
let frame_load_request_id = 0
|
|
269
|
+
let frame_load_timeout: ReturnType<typeof setTimeout> | undefined
|
|
237
270
|
|
|
238
271
|
// Auto-play when trajectory changes (handles both props and file loading)
|
|
239
272
|
$effect(() => {
|
|
@@ -247,8 +280,9 @@
|
|
|
247
280
|
if (trajectory && current_step_idx >= 0 && current_step_idx < total_frames) {
|
|
248
281
|
if (trajectory.frame_loader) {
|
|
249
282
|
// Load frame on demand (works for both indexed files and external streaming)
|
|
250
|
-
|
|
283
|
+
schedule_frame_load_on_demand(current_step_idx)
|
|
251
284
|
} else {
|
|
285
|
+
clear_frame_load_timeout()
|
|
252
286
|
// Use in-memory frame for regular trajectories
|
|
253
287
|
current_frame = trajectory.frames[current_step_idx] || null
|
|
254
288
|
}
|
|
@@ -257,11 +291,169 @@
|
|
|
257
291
|
}
|
|
258
292
|
})
|
|
259
293
|
|
|
294
|
+
// LRU cache of decoded frames (keyed to the current trajectory) so scrub/playback over
|
|
295
|
+
// indexed/streaming trajectories avoids re-reading frames and prefetch can warm upcoming ones.
|
|
296
|
+
// Capped by frame count AND a total-atom budget (cache many tiny frames or few huge ones).
|
|
297
|
+
const FRAME_CACHE_MAX = 64
|
|
298
|
+
const FRAME_CACHE_MAX_ATOMS = 200_000
|
|
299
|
+
let frame_cache = new Map<number, TrajectoryFrame>()
|
|
300
|
+
let frame_cache_owner: TrajectoryType | undefined
|
|
301
|
+
// Frames currently being read (direct or prefetch) so we don't kick off duplicate loads
|
|
302
|
+
const inflight_frames = new Set<number>()
|
|
303
|
+
let streaming_file_path = $derived(
|
|
304
|
+
trajectory?.metadata?.streaming_file_path as string | undefined,
|
|
305
|
+
)
|
|
306
|
+
let plot_metadata_loading = $derived(trajectory?.metadata?.plot_metadata_loading === true)
|
|
307
|
+
|
|
308
|
+
const clear_frame_load_timeout = () => {
|
|
309
|
+
if (frame_load_timeout) clearTimeout(frame_load_timeout)
|
|
310
|
+
frame_load_timeout = undefined
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const merge_plot_metadata = (batch: TrajectoryMetadata[]) => {
|
|
314
|
+
if (!trajectory || batch.length === 0) return
|
|
315
|
+
trajectory = {
|
|
316
|
+
...trajectory,
|
|
317
|
+
plot_metadata: [...(trajectory.plot_metadata ?? []), ...batch],
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const finish_plot_metadata_loading = () => {
|
|
322
|
+
if (!trajectory) return
|
|
323
|
+
trajectory = {
|
|
324
|
+
...trajectory,
|
|
325
|
+
metadata: { ...trajectory.metadata, plot_metadata_loading: false },
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
onMount(() => {
|
|
330
|
+
const handle_plot_metadata_stream = (event: MessageEvent<PlotMetadataStreamMessage>) => {
|
|
331
|
+
// Global listener: other code posts arbitrary messages (including null data)
|
|
332
|
+
if (typeof event.data !== `object` || event.data === null) return
|
|
333
|
+
const { command, file_path, is_complete, plot_metadata } = event.data
|
|
334
|
+
if (command !== `plot_metadata_stream` || file_path !== streaming_file_path) return
|
|
335
|
+
if (Array.isArray(plot_metadata)) merge_plot_metadata(plot_metadata)
|
|
336
|
+
if (is_complete) finish_plot_metadata_loading()
|
|
337
|
+
}
|
|
338
|
+
globalThis.addEventListener(`message`, handle_plot_metadata_stream)
|
|
339
|
+
return () => {
|
|
340
|
+
globalThis.removeEventListener(`message`, handle_plot_metadata_stream)
|
|
341
|
+
clear_frame_load_timeout()
|
|
342
|
+
}
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
// Reset per-trajectory caches when the trajectory changes (frames belong to the old one)
|
|
346
|
+
function ensure_frame_cache_owner() {
|
|
347
|
+
if (frame_cache_owner !== trajectory) {
|
|
348
|
+
frame_cache = new Map()
|
|
349
|
+
inflight_frames.clear()
|
|
350
|
+
frame_cache_owner = trajectory
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Sync LRU read (delete + re-insert refreshes recency); undefined on miss
|
|
354
|
+
function cache_get(frame_idx: number): TrajectoryFrame | undefined {
|
|
355
|
+
const hit = frame_cache.get(frame_idx)
|
|
356
|
+
if (!hit) return undefined
|
|
357
|
+
frame_cache.delete(frame_idx)
|
|
358
|
+
frame_cache.set(frame_idx, hit)
|
|
359
|
+
return hit
|
|
360
|
+
}
|
|
361
|
+
// Sync LRU write, evicting oldest entries until under both the frame and atom budgets
|
|
362
|
+
function cache_put(frame_idx: number, frame: TrajectoryFrame) {
|
|
363
|
+
frame_cache.set(frame_idx, frame)
|
|
364
|
+
let total_atoms = 0
|
|
365
|
+
for (const cached of frame_cache.values())
|
|
366
|
+
total_atoms += cached.structure?.sites?.length ?? 0
|
|
367
|
+
while (
|
|
368
|
+
frame_cache.size > 1 &&
|
|
369
|
+
(frame_cache.size > FRAME_CACHE_MAX || total_atoms > FRAME_CACHE_MAX_ATOMS)
|
|
370
|
+
) {
|
|
371
|
+
const oldest = frame_cache.keys().next().value
|
|
372
|
+
if (oldest === undefined) break
|
|
373
|
+
total_atoms -= frame_cache.get(oldest)?.structure?.sites?.length ?? 0
|
|
374
|
+
frame_cache.delete(oldest)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Warm the next couple of frames in the background (fire-and-forget) so playback/forward-scrub
|
|
379
|
+
// doesn't stall on a serial per-frame read; skips frames already cached or in flight
|
|
380
|
+
function prefetch_frames(from_idx: number) {
|
|
381
|
+
const frame_loader = trajectory?.frame_loader
|
|
382
|
+
if (!frame_loader) return
|
|
383
|
+
if (trajectory?.indexed_frames && trajectory.indexed_frames.length < total_frames) return
|
|
384
|
+
const owner = trajectory
|
|
385
|
+
for (const ahead of [1, 2]) {
|
|
386
|
+
const idx = from_idx + ahead
|
|
387
|
+
if (idx >= total_frames || frame_cache.has(idx) || inflight_frames.has(idx)) continue
|
|
388
|
+
inflight_frames.add(idx)
|
|
389
|
+
frame_loader
|
|
390
|
+
.load_frame(orig_data || ``, idx)
|
|
391
|
+
.then((frame) => {
|
|
392
|
+
if (frame && frame_cache_owner === owner) cache_put(idx, frame)
|
|
393
|
+
})
|
|
394
|
+
.catch(() => {})
|
|
395
|
+
.finally(() => inflight_frames.delete(idx))
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function use_cached_or_in_memory_frame(
|
|
400
|
+
load_trajectory: TrajectoryType,
|
|
401
|
+
frame_idx: number,
|
|
402
|
+
): boolean {
|
|
403
|
+
const cached = cache_get(frame_idx)
|
|
404
|
+
if (cached) {
|
|
405
|
+
current_frame = cached
|
|
406
|
+
prefetch_frames(frame_idx)
|
|
407
|
+
return true
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const in_memory_frame = load_trajectory.frames[frame_idx]
|
|
411
|
+
if (in_memory_frame) {
|
|
412
|
+
cache_put(frame_idx, in_memory_frame)
|
|
413
|
+
current_frame = in_memory_frame
|
|
414
|
+
prefetch_frames(frame_idx)
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
return false
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function schedule_frame_load_on_demand(frame_idx: number) {
|
|
421
|
+
const load_trajectory = trajectory
|
|
422
|
+
if (!load_trajectory?.frame_loader) return
|
|
423
|
+
ensure_frame_cache_owner()
|
|
424
|
+
frame_load_request_id++
|
|
425
|
+
|
|
426
|
+
if (use_cached_or_in_memory_frame(load_trajectory, frame_idx)) return
|
|
427
|
+
|
|
428
|
+
clear_frame_load_timeout()
|
|
429
|
+
// Debouncing during playback would keep resetting the timer at fps above
|
|
430
|
+
// ~1000/FRAME_LOAD_DEBOUNCE_MS and stall uncached streamed frames entirely.
|
|
431
|
+
// untrack: this runs synchronously inside the step-change $effect, so tracking
|
|
432
|
+
// is_playing would re-run that effect on every play/pause toggle and kick off
|
|
433
|
+
// duplicate loads for a frame that's already in flight.
|
|
434
|
+
if (untrack(() => is_playing)) {
|
|
435
|
+
void load_frame_on_demand(frame_idx)
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
const request_id = frame_load_request_id
|
|
439
|
+
frame_load_timeout = setTimeout(() => {
|
|
440
|
+
frame_load_timeout = undefined
|
|
441
|
+
if (
|
|
442
|
+
request_id === frame_load_request_id &&
|
|
443
|
+
trajectory === load_trajectory &&
|
|
444
|
+
current_step_idx === frame_idx
|
|
445
|
+
) {
|
|
446
|
+
load_frame_on_demand(frame_idx)
|
|
447
|
+
}
|
|
448
|
+
}, FRAME_LOAD_DEBOUNCE_MS)
|
|
449
|
+
}
|
|
450
|
+
|
|
260
451
|
// Load frame on demand - works for both indexed files and external streaming
|
|
261
452
|
async function load_frame_on_demand(frame_idx: number) {
|
|
262
453
|
const load_trajectory = trajectory
|
|
263
454
|
const frame_loader = load_trajectory?.frame_loader
|
|
264
455
|
if (!load_trajectory || !frame_loader) return
|
|
456
|
+
ensure_frame_cache_owner()
|
|
265
457
|
|
|
266
458
|
const request_id = ++frame_load_request_id
|
|
267
459
|
const request_is_current = () =>
|
|
@@ -269,13 +461,18 @@
|
|
|
269
461
|
trajectory === load_trajectory &&
|
|
270
462
|
current_step_idx === frame_idx
|
|
271
463
|
|
|
464
|
+
if (use_cached_or_in_memory_frame(load_trajectory, frame_idx)) return
|
|
465
|
+
inflight_frames.add(frame_idx) // let concurrent prefetch skip the frame we're already loading
|
|
272
466
|
try {
|
|
273
467
|
const frame = await frame_loader.load_frame(
|
|
274
|
-
orig_data || ``, //
|
|
468
|
+
orig_data || ``, // original_data for indexed files, empty string for external streaming
|
|
275
469
|
frame_idx,
|
|
276
470
|
)
|
|
471
|
+
// cache the decoded frame even if it arrived stale (still valid data for that index)
|
|
472
|
+
if (frame && frame_cache_owner === load_trajectory) cache_put(frame_idx, frame)
|
|
277
473
|
if (!request_is_current()) return
|
|
278
474
|
current_frame = frame
|
|
475
|
+
prefetch_frames(frame_idx) // warm upcoming frames for smooth playback/scrub
|
|
279
476
|
} catch (error) {
|
|
280
477
|
if (!request_is_current()) return
|
|
281
478
|
console.error(`Failed to load frame ${frame_idx}:`, error)
|
|
@@ -287,6 +484,8 @@
|
|
|
287
484
|
step_idx: frame_idx,
|
|
288
485
|
frame_count: total_frames,
|
|
289
486
|
})
|
|
487
|
+
} finally {
|
|
488
|
+
inflight_frames.delete(frame_idx)
|
|
290
489
|
}
|
|
291
490
|
}
|
|
292
491
|
|
|
@@ -294,7 +493,7 @@
|
|
|
294
493
|
let current_structure = $derived(current_frame?.structure)
|
|
295
494
|
|
|
296
495
|
// Track hidden elements (persists across frame changes)
|
|
297
|
-
let hidden_elements = $state(new
|
|
496
|
+
let hidden_elements = $state(new SvelteSet<ElementSymbol>())
|
|
298
497
|
|
|
299
498
|
let step_label_positions = $derived.by((): number[] => {
|
|
300
499
|
if (!step_labels || total_frames <= 1) return []
|
|
@@ -305,11 +504,14 @@
|
|
|
305
504
|
|
|
306
505
|
if (typeof step_labels === `number`) {
|
|
307
506
|
if (step_labels > 0) {
|
|
308
|
-
return scaleLinear()
|
|
507
|
+
return scaleLinear()
|
|
508
|
+
.domain([0, total_frames - 1])
|
|
509
|
+
.nice()
|
|
309
510
|
.ticks(Math.min(step_labels, total_frames))
|
|
310
511
|
.map((tick) => Math.round(tick))
|
|
311
|
-
.filter(
|
|
312
|
-
tick
|
|
512
|
+
.filter(
|
|
513
|
+
(tick, idx, ticks) =>
|
|
514
|
+
tick >= 0 && tick < total_frames && ticks.indexOf(tick) === idx,
|
|
313
515
|
)
|
|
314
516
|
}
|
|
315
517
|
if (step_labels < 0) {
|
|
@@ -330,11 +532,13 @@
|
|
|
330
532
|
let extended_config = $derived.by(() => {
|
|
331
533
|
if (!ELEM_PROPERTY_LABELS) return trajectory_property_config
|
|
332
534
|
|
|
333
|
-
const custom_config: Record<string,
|
|
535
|
+
const custom_config: Record<string, TrajPropertyConfig> = {}
|
|
334
536
|
for (const [key, label] of Object.entries(ELEM_PROPERTY_LABELS)) {
|
|
335
|
-
const existing =
|
|
336
|
-
trajectory_property_config[key.toLowerCase()]
|
|
337
|
-
|
|
537
|
+
const existing =
|
|
538
|
+
trajectory_property_config[key] || trajectory_property_config[key.toLowerCase()]
|
|
539
|
+
// Spread the existing config so fields like axis_group survive the
|
|
540
|
+
// label override (losing axis_group would break dedicated-axis grouping)
|
|
541
|
+
custom_config[key] = { ...existing, label, unit: existing?.unit || `` }
|
|
338
542
|
}
|
|
339
543
|
return { ...trajectory_property_config, ...custom_config }
|
|
340
544
|
})
|
|
@@ -387,7 +591,8 @@
|
|
|
387
591
|
|
|
388
592
|
// Only update if changed (use untrack to avoid circular dependency)
|
|
389
593
|
const current = untrack(() => visible_properties) || []
|
|
390
|
-
const has_changed =
|
|
594
|
+
const has_changed =
|
|
595
|
+
visible_keys.length !== current.length ||
|
|
391
596
|
!visible_keys.every((key, idx) => key === current[idx])
|
|
392
597
|
|
|
393
598
|
if (has_changed) {
|
|
@@ -402,27 +607,35 @@
|
|
|
402
607
|
plot_series = toggle_series_visibility(plot_series, series_idx)
|
|
403
608
|
}
|
|
404
609
|
|
|
610
|
+
// Streamed trajectories plot sampled per-frame metadata, so x values are frame numbers
|
|
611
|
+
let x_axis_quantity = $derived(trajectory?.plot_metadata ? `Frame` : `Step`)
|
|
405
612
|
let x_axis = $derived({
|
|
406
|
-
label:
|
|
407
|
-
|
|
613
|
+
label: x_axis_quantity,
|
|
614
|
+
// ~g (not ~s) so sub-1 values read as 0.8 rather than SI "800m"; integer steps stay clean
|
|
615
|
+
format: `~g`,
|
|
408
616
|
ticks: step_label_positions,
|
|
409
617
|
})
|
|
410
618
|
// Generate axis labels based on first visible series on each axis
|
|
411
619
|
let y_axis_labels = $derived(generate_axis_labels(plot_series))
|
|
620
|
+
// Axes whose visible series are all-positive and span many decades (e.g. SCF
|
|
621
|
+
// convergence residuals) default to log scale instead of a linear hockey stick
|
|
622
|
+
let y_axis_scale_types = $derived(generate_axis_scale_types(plot_series))
|
|
412
623
|
let y_axis = $derived({
|
|
413
624
|
label: y_axis_labels.y1,
|
|
414
|
-
format:
|
|
625
|
+
format: `~g`,
|
|
415
626
|
label_shift: { y: 10 },
|
|
627
|
+
scale_type: y_axis_scale_types.y1,
|
|
416
628
|
})
|
|
417
629
|
let y2_axis = $derived({
|
|
418
630
|
label: y_axis_labels.y2,
|
|
419
|
-
format:
|
|
631
|
+
format: `~g`,
|
|
420
632
|
label_shift: { y: 80 },
|
|
633
|
+
scale_type: y_axis_scale_types.y2,
|
|
421
634
|
})
|
|
422
|
-
|
|
423
635
|
// hide plot if all plotted values are constant (no variation)
|
|
424
636
|
let show_plot = $derived(
|
|
425
|
-
display_mode !== `structure` &&
|
|
637
|
+
display_mode !== `structure` &&
|
|
638
|
+
(plot_metadata_loading || !should_hide_plot(trajectory, plot_series)),
|
|
426
639
|
)
|
|
427
640
|
|
|
428
641
|
// Determine what to show based on display mode
|
|
@@ -430,23 +643,28 @@
|
|
|
430
643
|
let actual_show_plot = $derived(display_mode !== `structure` && show_plot)
|
|
431
644
|
|
|
432
645
|
// Check if there are any Y2 series to determine padding
|
|
433
|
-
let has_y2_series = $derived(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
646
|
+
let has_y2_series = $derived(plot_series.some((srs) => srs.y_axis === `y2` && srs.visible))
|
|
647
|
+
// Report the current step to on_step_change consumers. Also wired to the step
|
|
648
|
+
// slider/number input whose bind:value bypasses the navigation functions
|
|
649
|
+
// below — those handlers pass the event target's value explicitly because
|
|
650
|
+
// bind:value may not have written the binding yet when oninput fires.
|
|
651
|
+
function notify_step_change(step_idx: number = current_step_idx) {
|
|
652
|
+
if (!trajectory || !Number.isFinite(step_idx)) return
|
|
653
|
+
const last_frame = Math.max(total_frames - 1, 0)
|
|
654
|
+
const clamped_step = Math.min(Math.max(Math.round(step_idx), 0), last_frame)
|
|
655
|
+
on_step_change?.({
|
|
656
|
+
trajectory,
|
|
657
|
+
step_idx: clamped_step,
|
|
658
|
+
frame_count: total_frames,
|
|
659
|
+
frame: current_frame || undefined,
|
|
660
|
+
})
|
|
661
|
+
}
|
|
437
662
|
// Step navigation functions
|
|
438
663
|
function next_step() {
|
|
439
664
|
if (current_step_idx < total_frames - 1) {
|
|
440
665
|
current_step_idx++
|
|
441
666
|
// Streaming frame loading handled by reactive effect
|
|
442
|
-
|
|
443
|
-
on_step_change?.({
|
|
444
|
-
trajectory,
|
|
445
|
-
step_idx: current_step_idx,
|
|
446
|
-
frame_count: total_frames,
|
|
447
|
-
frame: current_frame || undefined,
|
|
448
|
-
})
|
|
449
|
-
}
|
|
667
|
+
notify_step_change()
|
|
450
668
|
}
|
|
451
669
|
}
|
|
452
670
|
|
|
@@ -454,14 +672,7 @@
|
|
|
454
672
|
if (current_step_idx > 0) {
|
|
455
673
|
current_step_idx--
|
|
456
674
|
// Streaming frame loading handled by reactive effect
|
|
457
|
-
|
|
458
|
-
on_step_change?.({
|
|
459
|
-
trajectory,
|
|
460
|
-
step_idx: current_step_idx,
|
|
461
|
-
frame_count: total_frames,
|
|
462
|
-
frame: current_frame || undefined,
|
|
463
|
-
})
|
|
464
|
-
}
|
|
675
|
+
notify_step_change()
|
|
465
676
|
}
|
|
466
677
|
}
|
|
467
678
|
|
|
@@ -470,14 +681,7 @@
|
|
|
470
681
|
current_step_idx = idx
|
|
471
682
|
// Note: streaming frame loading is handled by reactive effect
|
|
472
683
|
// Handle callbacks for both traditional and streaming modes
|
|
473
|
-
|
|
474
|
-
on_step_change?.({
|
|
475
|
-
trajectory,
|
|
476
|
-
step_idx: current_step_idx,
|
|
477
|
-
frame_count: total_frames,
|
|
478
|
-
frame: current_frame || undefined,
|
|
479
|
-
})
|
|
480
|
-
}
|
|
684
|
+
notify_step_change()
|
|
481
685
|
}
|
|
482
686
|
}
|
|
483
687
|
|
|
@@ -496,7 +700,7 @@
|
|
|
496
700
|
reader.addEventListener(`error`, () => reject(new Error(`Failed to read file`)))
|
|
497
701
|
|
|
498
702
|
// Read as text for text-based formats, binary for others
|
|
499
|
-
if (
|
|
703
|
+
if (/\.(?:xyz|json|extxyz|lammpstrj)$/.test(file.name.toLowerCase())) {
|
|
500
704
|
reader.readAsText(file)
|
|
501
705
|
} else reader.readAsArrayBuffer(file)
|
|
502
706
|
})
|
|
@@ -523,44 +727,50 @@
|
|
|
523
727
|
})
|
|
524
728
|
}
|
|
525
729
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
//
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
on_end?.({
|
|
542
|
-
trajectory,
|
|
543
|
-
step_idx: current_step_idx,
|
|
544
|
-
frame_count: total_frames,
|
|
545
|
-
frame: current_frame || undefined,
|
|
546
|
-
})
|
|
547
|
-
}
|
|
548
|
-
go_to_step(0) // Loop back to 1st step
|
|
549
|
-
if (trajectory) {
|
|
550
|
-
on_loop?.({ trajectory, frame_count: total_frames })
|
|
551
|
-
}
|
|
552
|
-
} else next_step()
|
|
553
|
-
}, rate_ms)
|
|
554
|
-
} else if (current_interval !== undefined) {
|
|
555
|
-
// Clear interval when not playing
|
|
556
|
-
clearInterval(current_interval)
|
|
557
|
-
play_interval = undefined
|
|
558
|
-
}
|
|
559
|
-
})
|
|
730
|
+
// Advance one frame (or loop back to start), firing the matching callbacks
|
|
731
|
+
function advance_playback() {
|
|
732
|
+
if (current_step_idx >= total_frames - 1) {
|
|
733
|
+
if (trajectory) {
|
|
734
|
+
on_end?.({
|
|
735
|
+
trajectory,
|
|
736
|
+
step_idx: current_step_idx,
|
|
737
|
+
frame_count: total_frames,
|
|
738
|
+
frame: current_frame || undefined,
|
|
739
|
+
})
|
|
740
|
+
}
|
|
741
|
+
go_to_step(0) // loop back to 1st step
|
|
742
|
+
if (trajectory) on_loop?.({ trajectory, frame_count: total_frames })
|
|
743
|
+
} else next_step()
|
|
744
|
+
}
|
|
560
745
|
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
746
|
+
// rAF playback loop. Only tracks `is_playing`; `fps`/`current_step_idx`/`total_frames` are read
|
|
747
|
+
// live in the untracked tick, so changing fps mid-play retargets the cadence without restarting.
|
|
748
|
+
// Clamped delta + at most one advance per tick lets a background tab resume cleanly (no jump),
|
|
749
|
+
// but also caps playback at the ~60fps refresh rate (= default fps_range max; higher won't help).
|
|
750
|
+
$effect(() => {
|
|
751
|
+
if (!is_playing) return
|
|
752
|
+
let last = performance.now()
|
|
753
|
+
let accumulated_ms = 0
|
|
754
|
+
const tick = (now: number) => {
|
|
755
|
+
// stop if the trajectory went away or is no longer animatable (e.g. swapped mid-play)
|
|
756
|
+
if (!trajectory || total_frames <= 1) {
|
|
757
|
+
is_playing = false
|
|
758
|
+
return
|
|
759
|
+
}
|
|
760
|
+
accumulated_ms += Math.min(now - last, 250)
|
|
761
|
+
last = now
|
|
762
|
+
const step_ms = 1000 / Math.max(0.1, fps)
|
|
763
|
+
if (accumulated_ms >= step_ms) {
|
|
764
|
+
accumulated_ms = Math.min(accumulated_ms - step_ms, step_ms)
|
|
765
|
+
advance_playback()
|
|
766
|
+
}
|
|
767
|
+
play_raf = requestAnimationFrame(tick)
|
|
768
|
+
}
|
|
769
|
+
play_raf = requestAnimationFrame(tick)
|
|
770
|
+
return () => {
|
|
771
|
+
if (play_raf !== undefined) cancelAnimationFrame(play_raf)
|
|
772
|
+
play_raf = undefined
|
|
773
|
+
}
|
|
564
774
|
})
|
|
565
775
|
|
|
566
776
|
// Handle internal file format drops
|
|
@@ -577,10 +787,7 @@
|
|
|
577
787
|
const array_buffer = await response.arrayBuffer()
|
|
578
788
|
await load_trajectory_data(array_buffer, file_info.name)
|
|
579
789
|
} else {
|
|
580
|
-
console.warn(
|
|
581
|
-
`Binary file without ArrayBuffer or blob URL:`,
|
|
582
|
-
file_info.name,
|
|
583
|
-
)
|
|
790
|
+
console.warn(`Binary file without ArrayBuffer or blob URL:`, file_info.name)
|
|
584
791
|
}
|
|
585
792
|
} else {
|
|
586
793
|
await load_trajectory_data(file_info.content, file_info.name)
|
|
@@ -602,9 +809,7 @@
|
|
|
602
809
|
|
|
603
810
|
try {
|
|
604
811
|
// Check for our custom internal file format first
|
|
605
|
-
const internal_data = event.dataTransfer?.getData(
|
|
606
|
-
`application/x-matterviz-file`,
|
|
607
|
-
)
|
|
812
|
+
const internal_data = event.dataTransfer?.getData(`application/x-matterviz-file`)
|
|
608
813
|
if (internal_data) {
|
|
609
814
|
const handled = await handle_internal_file_drop(internal_data)
|
|
610
815
|
if (handled) return
|
|
@@ -613,9 +818,8 @@
|
|
|
613
818
|
// Handle URL-based files (e.g. from FilePicker)
|
|
614
819
|
const handled = await handle_url_drop(event, async (content, filename) => {
|
|
615
820
|
current_filename = filename
|
|
616
|
-
file_size =
|
|
617
|
-
? content.byteLength
|
|
618
|
-
: new Blob([content]).size
|
|
821
|
+
file_size =
|
|
822
|
+
content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
|
|
619
823
|
await load_trajectory_data(content, filename)
|
|
620
824
|
}).catch(() => false)
|
|
621
825
|
|
|
@@ -653,16 +857,16 @@
|
|
|
653
857
|
}
|
|
654
858
|
}
|
|
655
859
|
|
|
656
|
-
$effect(() => {
|
|
860
|
+
$effect(() => {
|
|
861
|
+
// Load trajectory from URL when data_url is provided
|
|
657
862
|
if (data_url && !trajectory) {
|
|
658
863
|
loading = true
|
|
659
864
|
error_msg = null
|
|
660
865
|
|
|
661
866
|
load_from_url(data_url, async (content, filename) => {
|
|
662
867
|
current_filename = filename
|
|
663
|
-
file_size =
|
|
664
|
-
? content.byteLength
|
|
665
|
-
: new Blob([content]).size
|
|
868
|
+
file_size =
|
|
869
|
+
content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
|
|
666
870
|
await load_trajectory_data(content, filename)
|
|
667
871
|
})
|
|
668
872
|
.then(() => {
|
|
@@ -700,30 +904,34 @@
|
|
|
700
904
|
const data_size = data instanceof ArrayBuffer ? data.byteLength : data.length
|
|
701
905
|
|
|
702
906
|
// Determine loading strategy based on file size
|
|
703
|
-
const bin_file_threshold = loading_options.bin_file_threshold ??
|
|
704
|
-
|
|
705
|
-
const text_file_threshold = loading_options.text_file_threshold ??
|
|
706
|
-
MAX_TEXT_FILE_SIZE
|
|
907
|
+
const bin_file_threshold = loading_options.bin_file_threshold ?? MAX_BIN_FILE_SIZE
|
|
908
|
+
const text_file_threshold = loading_options.text_file_threshold ?? MAX_TEXT_FILE_SIZE
|
|
707
909
|
if (
|
|
708
910
|
(data instanceof ArrayBuffer && data_size > bin_file_threshold) ||
|
|
709
911
|
(typeof data === `string` && data_size > text_file_threshold)
|
|
710
|
-
) {
|
|
912
|
+
) {
|
|
913
|
+
// Large files: Use indexed loading
|
|
711
914
|
await load_with_indexing(data, filename)
|
|
712
915
|
} else {
|
|
713
916
|
// Small files: Use regular loading
|
|
714
917
|
const merged_options = { ...loading_options, atom_type_mapping }
|
|
715
|
-
trajectory = await parse_trajectory_async(
|
|
716
|
-
|
|
717
|
-
|
|
918
|
+
trajectory = await parse_trajectory_async(
|
|
919
|
+
data,
|
|
920
|
+
filename,
|
|
921
|
+
(progress) => {
|
|
922
|
+
parsing_progress = progress
|
|
923
|
+
},
|
|
924
|
+
merged_options,
|
|
925
|
+
)
|
|
718
926
|
}
|
|
719
927
|
|
|
720
928
|
current_step_idx = 0
|
|
721
929
|
current_filename = filename
|
|
722
930
|
|
|
723
|
-
const file_size_bytes =
|
|
724
|
-
? data.byteLength
|
|
725
|
-
|
|
726
|
-
|
|
931
|
+
const file_size_bytes =
|
|
932
|
+
data instanceof ArrayBuffer ? data.byteLength : new Blob([data]).size
|
|
933
|
+
on_file_load?.({
|
|
934
|
+
// emit file load event
|
|
727
935
|
trajectory,
|
|
728
936
|
frame_count: trajectory?.frames.length ?? 0,
|
|
729
937
|
total_atoms: trajectory?.frames[0]?.structure.sites.length ?? 0,
|
|
@@ -739,7 +947,8 @@
|
|
|
739
947
|
current_filename = undefined
|
|
740
948
|
file_size = undefined
|
|
741
949
|
|
|
742
|
-
on_error?.({
|
|
950
|
+
on_error?.({
|
|
951
|
+
// emit error event
|
|
743
952
|
error_msg,
|
|
744
953
|
filename: current_filename || undefined,
|
|
745
954
|
file_size: file_size || undefined,
|
|
@@ -752,15 +961,21 @@
|
|
|
752
961
|
|
|
753
962
|
// Load using indexed parsing for large files
|
|
754
963
|
async function load_with_indexing(data: string | ArrayBuffer, filename: string) {
|
|
755
|
-
try {
|
|
964
|
+
try {
|
|
965
|
+
// Use indexed parsing for efficient large file handling
|
|
756
966
|
const merged_options = {
|
|
757
967
|
use_indexing: true,
|
|
758
968
|
...loading_options,
|
|
759
969
|
atom_type_mapping,
|
|
760
970
|
}
|
|
761
|
-
trajectory = await parse_trajectory_async(
|
|
762
|
-
|
|
763
|
-
|
|
971
|
+
trajectory = await parse_trajectory_async(
|
|
972
|
+
data,
|
|
973
|
+
filename,
|
|
974
|
+
(progress) => {
|
|
975
|
+
parsing_progress = progress
|
|
976
|
+
},
|
|
977
|
+
merged_options,
|
|
978
|
+
)
|
|
764
979
|
|
|
765
980
|
// Keep original data for on-demand frame loads only when indexed parsing attached a
|
|
766
981
|
// frame_loader. Direct-parse fallbacks (e.g. large JSON or extensionless blob:
|
|
@@ -803,8 +1018,7 @@
|
|
|
803
1018
|
// Don't handle shortcuts if user is typing in an input field (but allow if it's our step input and not focused)
|
|
804
1019
|
const target = event.target instanceof HTMLElement ? event.target : null
|
|
805
1020
|
const is_step_input = target?.classList.contains(`step-input`) ?? false
|
|
806
|
-
const is_input_focused =
|
|
807
|
-
target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
1021
|
+
const is_input_focused = target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
808
1022
|
|
|
809
1023
|
// Skip if typing in an input that's not our step input
|
|
810
1024
|
if (is_input_focused && !is_step_input) return false
|
|
@@ -843,7 +1057,7 @@
|
|
|
843
1057
|
go_to_step(Math.max(0, current_step_idx - 25))
|
|
844
1058
|
} else if (event.key === `PageDown`) {
|
|
845
1059
|
go_to_step(Math.min(total_frames - 1, current_step_idx + 25))
|
|
846
|
-
}
|
|
1060
|
+
} // Interface shortcuts
|
|
847
1061
|
else if (event.key === `f` && fullscreen_toggle) toggle_fullscreen(wrapper)
|
|
848
1062
|
// 'i' key handled by the TrajectoryInfoPane's built-in toggle
|
|
849
1063
|
// Playback speed shortcuts (only when playing)
|
|
@@ -853,14 +1067,14 @@
|
|
|
853
1067
|
} else if (event.key === `-` && is_playing) {
|
|
854
1068
|
fps = Math.max(fps_range[0], fps - 0.2)
|
|
855
1069
|
on_frame_rate_change?.({ trajectory, fps })
|
|
856
|
-
}
|
|
1070
|
+
} // System shortcuts
|
|
857
1071
|
else if (event.key === `Escape`) {
|
|
858
1072
|
if (document.fullscreenElement) document.exitFullscreen()
|
|
859
1073
|
else if (view_mode_dropdown_open) view_mode_dropdown_open = false
|
|
860
1074
|
// Escape key for info pane handled by DraggablePane
|
|
861
|
-
}
|
|
1075
|
+
} // Number keys 0-9 - jump to percentage of trajectory
|
|
862
1076
|
else if (event.key >= `0` && event.key <= `9`) {
|
|
863
|
-
go_to_step(Math.floor((
|
|
1077
|
+
go_to_step(Math.floor((Number(event.key) / 10) * (total_frames - 1)))
|
|
864
1078
|
} else handled = false
|
|
865
1079
|
|
|
866
1080
|
return handled
|
|
@@ -868,25 +1082,28 @@
|
|
|
868
1082
|
|
|
869
1083
|
// Separate state variables for each pane to match component prop types
|
|
870
1084
|
let structure_info_open = $state(false)
|
|
871
|
-
let structure_controls_open = $state(false)
|
|
872
1085
|
let scatter_controls = $state<ControlsConfig>({ open: false })
|
|
873
1086
|
let trajectory_export_open = $state(false)
|
|
874
|
-
let fullscreen = $state(false)
|
|
875
|
-
</script>
|
|
876
1087
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1088
|
+
sync_fullscreen({
|
|
1089
|
+
get_wrapper: () => wrapper,
|
|
1090
|
+
get_fullscreen: () => fullscreen,
|
|
1091
|
+
set_fullscreen: (val) => (fullscreen = val),
|
|
1092
|
+
bg_css_var: `--traj-bg-fullscreen`,
|
|
1093
|
+
on_change: (val) => on_fullscreen_change?.({ trajectory, fullscreen: val }),
|
|
1094
|
+
})
|
|
1095
|
+
</script>
|
|
883
1096
|
|
|
884
1097
|
<svelte:window onkeydown={forward_window_keydown(() => hovered, onkeydown)} />
|
|
885
1098
|
|
|
886
1099
|
<div
|
|
887
1100
|
class:dragover
|
|
888
|
-
class:active={is_playing ||
|
|
889
|
-
|
|
1101
|
+
class:active={is_playing ||
|
|
1102
|
+
structure_info_open ||
|
|
1103
|
+
controls_open ||
|
|
1104
|
+
scatter_controls.open ||
|
|
1105
|
+
trajectory_export_open ||
|
|
1106
|
+
info_pane_open}
|
|
890
1107
|
bind:this={wrapper}
|
|
891
1108
|
bind:clientWidth={element_size.width}
|
|
892
1109
|
bind:clientHeight={element_size.height}
|
|
@@ -896,21 +1113,17 @@
|
|
|
896
1113
|
onmouseenter={() => (hovered = true)}
|
|
897
1114
|
onmouseleave={() => (hovered = false)}
|
|
898
1115
|
ondrop={handle_file_drop}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
}}
|
|
904
|
-
ondragleave={(event) => {
|
|
905
|
-
event.preventDefault()
|
|
906
|
-
dragover = false
|
|
907
|
-
}}
|
|
1116
|
+
{...drag_over_handlers({
|
|
1117
|
+
allow: () => allow_file_drop,
|
|
1118
|
+
set_dragover: (over) => (dragover = over),
|
|
1119
|
+
})}
|
|
908
1120
|
onclick={handle_click_outside}
|
|
909
1121
|
onkeydown={handle_and_prevent(onkeydown)}
|
|
910
1122
|
{...rest}
|
|
911
|
-
class=
|
|
1123
|
+
class={[`trajectory`, actual_layout, rest.class]}
|
|
912
1124
|
class:show-both-views={[`structure+scatter`, `structure+histogram`].includes(display_mode) &&
|
|
913
|
-
|
|
1125
|
+
actual_show_plot &&
|
|
1126
|
+
show_structure}
|
|
914
1127
|
>
|
|
915
1128
|
{#if loading}
|
|
916
1129
|
{@const text = parsing_progress
|
|
@@ -922,25 +1135,26 @@
|
|
|
922
1135
|
{...spinner_props}
|
|
923
1136
|
/>
|
|
924
1137
|
{:else if error_msg}
|
|
925
|
-
<TrajectoryError
|
|
926
|
-
{error_msg}
|
|
927
|
-
on_dismiss={() => (error_msg = null)}
|
|
928
|
-
{error_snippet}
|
|
929
|
-
/>
|
|
1138
|
+
<TrajectoryError {error_msg} on_dismiss={() => (error_msg = null)} {error_snippet} />
|
|
930
1139
|
{:else if trajectory}
|
|
1140
|
+
{#if parse_warning_msg}
|
|
1141
|
+
<StatusMessage
|
|
1142
|
+
bind:message={parse_warning_msg}
|
|
1143
|
+
type="warning"
|
|
1144
|
+
dismissible
|
|
1145
|
+
style="position: absolute; bottom: 4pt; left: 4pt; right: 4pt; z-index: 2; font-size: 0.85em"
|
|
1146
|
+
/>
|
|
1147
|
+
{/if}
|
|
931
1148
|
<!-- Trajectory Controls -->
|
|
932
1149
|
{#if controls_config.mode !== `never`}
|
|
933
|
-
<div
|
|
934
|
-
class="trajectory-controls {controls_config.class}"
|
|
935
|
-
style={controls_config.style}
|
|
936
|
-
>
|
|
1150
|
+
<div class="trajectory-controls {controls_config.class}" style={controls_config.style}>
|
|
937
1151
|
{#if trajectory_controls}
|
|
938
1152
|
{@render trajectory_controls({
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1153
|
+
trajectory,
|
|
1154
|
+
current_step_idx,
|
|
1155
|
+
total_frames: total_frames,
|
|
1156
|
+
on_step_change: go_to_step,
|
|
1157
|
+
})}
|
|
944
1158
|
{:else}
|
|
945
1159
|
{#if current_filename && controls_config.visible(`filename`)}
|
|
946
1160
|
<button
|
|
@@ -951,7 +1165,7 @@
|
|
|
951
1165
|
if (current_filename) {
|
|
952
1166
|
navigator.clipboard.writeText(current_filename)
|
|
953
1167
|
filename_copied = true
|
|
954
|
-
setTimeout(() => filename_copied = false, 1000)
|
|
1168
|
+
setTimeout(() => (filename_copied = false), 1000)
|
|
955
1169
|
}
|
|
956
1170
|
}}
|
|
957
1171
|
>
|
|
@@ -959,7 +1173,7 @@
|
|
|
959
1173
|
{#if filename_copied}
|
|
960
1174
|
<Icon
|
|
961
1175
|
icon="Check"
|
|
962
|
-
style="color: var(--success-color); position: absolute; right: 3pt; top: 50%; transform: translateY(-50%);
|
|
1176
|
+
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"
|
|
963
1177
|
/>
|
|
964
1178
|
{/if}
|
|
965
1179
|
</button>
|
|
@@ -971,14 +1185,16 @@
|
|
|
971
1185
|
<button
|
|
972
1186
|
onclick={prev_step}
|
|
973
1187
|
disabled={current_step_idx === 0 || is_playing}
|
|
974
|
-
title="Previous step"
|
|
1188
|
+
title="Previous step (←) · Home: first · j: −10 · PageUp: −25"
|
|
975
1189
|
>
|
|
976
1190
|
⏮
|
|
977
1191
|
</button>
|
|
978
1192
|
<button
|
|
979
1193
|
onclick={toggle_play}
|
|
980
1194
|
disabled={total_frames <= 1}
|
|
981
|
-
title={
|
|
1195
|
+
title={`${
|
|
1196
|
+
is_playing ? `Pause` : `Play`
|
|
1197
|
+
} (Space) · ←/→ step · 0-9 jump % · +/- speed · f fullscreen`}
|
|
982
1198
|
class="play-button"
|
|
983
1199
|
class:playing={is_playing}
|
|
984
1200
|
>
|
|
@@ -987,7 +1203,7 @@
|
|
|
987
1203
|
<button
|
|
988
1204
|
onclick={next_step}
|
|
989
1205
|
disabled={current_step_idx === total_frames - 1 || is_playing}
|
|
990
|
-
title="Next step"
|
|
1206
|
+
title="Next step (→) · End: last · l: +10 · PageDown: +25"
|
|
991
1207
|
>
|
|
992
1208
|
⏭
|
|
993
1209
|
</button>
|
|
@@ -1002,6 +1218,7 @@
|
|
|
1002
1218
|
min="0"
|
|
1003
1219
|
max={total_frames - 1}
|
|
1004
1220
|
bind:value={current_step_idx}
|
|
1221
|
+
oninput={(event) => notify_step_change(event.currentTarget.valueAsNumber)}
|
|
1005
1222
|
class="step-input"
|
|
1006
1223
|
title="Enter step number to jump to"
|
|
1007
1224
|
aria-label="Step input"
|
|
@@ -1014,15 +1231,15 @@
|
|
|
1014
1231
|
min="0"
|
|
1015
1232
|
max={total_frames - 1}
|
|
1016
1233
|
bind:value={current_step_idx}
|
|
1234
|
+
oninput={(event) => notify_step_change(event.currentTarget.valueAsNumber)}
|
|
1017
1235
|
class="step-slider"
|
|
1018
1236
|
title="Drag to navigate steps"
|
|
1019
1237
|
/>
|
|
1020
1238
|
{#if step_label_positions.length > 0}
|
|
1021
1239
|
<div class="step-labels">
|
|
1022
1240
|
{#each step_label_positions as step_idx (step_idx)}
|
|
1023
|
-
{@const position_percent =
|
|
1024
|
-
|
|
1025
|
-
: 0}
|
|
1241
|
+
{@const position_percent =
|
|
1242
|
+
total_frames > 1 ? (step_idx / (total_frames - 1)) * 100 : 0}
|
|
1026
1243
|
{@const adjusted_position = 1.5 + (position_percent * (100 - 2)) / 100}
|
|
1027
1244
|
<div class="step-tick" style:left="{adjusted_position}%"></div>
|
|
1028
1245
|
<div class="step-label" style:left="{adjusted_position}%">
|
|
@@ -1035,8 +1252,8 @@
|
|
|
1035
1252
|
</div>
|
|
1036
1253
|
{/if}
|
|
1037
1254
|
|
|
1038
|
-
<!-- Frame rate control -
|
|
1039
|
-
{#if
|
|
1255
|
+
<!-- Frame rate control: shown for any multi-frame trajectory so speed can be set before play -->
|
|
1256
|
+
{#if total_frames > 1 && controls_config.visible(`fps`)}
|
|
1040
1257
|
<label
|
|
1041
1258
|
class="fps-section"
|
|
1042
1259
|
style="font-size: 0.9em; display: flex; align-items: center; gap: 5pt; margin-inline: 6pt"
|
|
@@ -1048,7 +1265,7 @@
|
|
|
1048
1265
|
max={fps_range[1]}
|
|
1049
1266
|
bind:value={fps}
|
|
1050
1267
|
title="Frame rate: {format_num(fps, `.2~s`)} fps"
|
|
1051
|
-
style="width: clamp(60px, 8cqw, 90px)
|
|
1268
|
+
style="width: clamp(60px, 8cqw, 90px)"
|
|
1052
1269
|
/>
|
|
1053
1270
|
<input
|
|
1054
1271
|
type="number"
|
|
@@ -1087,8 +1304,7 @@
|
|
|
1087
1304
|
/>
|
|
1088
1305
|
{/if}
|
|
1089
1306
|
<!-- Display mode dropdown -->
|
|
1090
|
-
{#if plot_series.length > 0 &&
|
|
1091
|
-
controls_config.visible(`view-mode`)}
|
|
1307
|
+
{#if plot_series.length > 0 && controls_config.visible(`view-mode`)}
|
|
1092
1308
|
<div class="view-mode-dropdown-wrapper">
|
|
1093
1309
|
<button
|
|
1094
1310
|
onclick={() => (view_mode_dropdown_open = !view_mode_dropdown_open)}
|
|
@@ -1098,40 +1314,21 @@
|
|
|
1098
1314
|
style="background-color: transparent; padding: 0"
|
|
1099
1315
|
>
|
|
1100
1316
|
<Icon
|
|
1101
|
-
icon={(
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1317
|
+
icon={(
|
|
1318
|
+
{
|
|
1319
|
+
structure: `Atom`,
|
|
1320
|
+
'structure+scatter': `TwoColumns`,
|
|
1321
|
+
'structure+histogram': `TwoColumns`,
|
|
1322
|
+
scatter: `ScatterPlot`,
|
|
1323
|
+
histogram: `Histogram`,
|
|
1324
|
+
} as const
|
|
1325
|
+
)[display_mode]}
|
|
1108
1326
|
/>
|
|
1109
1327
|
<Icon icon={view_mode_dropdown_open ? `ArrowUp` : `ArrowDown`} />
|
|
1110
1328
|
</button>
|
|
1111
1329
|
{#if view_mode_dropdown_open}
|
|
1112
1330
|
<div class="view-mode-dropdown">
|
|
1113
|
-
{#each [
|
|
1114
|
-
{ mode: `structure`, icon: `Atom`, label: `Structure-only` },
|
|
1115
|
-
{
|
|
1116
|
-
mode: `structure+scatter`,
|
|
1117
|
-
icon: `TwoColumns`,
|
|
1118
|
-
label: `Structure + Scatter`,
|
|
1119
|
-
},
|
|
1120
|
-
{
|
|
1121
|
-
mode: `structure+histogram`,
|
|
1122
|
-
icon: `TwoColumns`,
|
|
1123
|
-
label: `Structure + Histogram`,
|
|
1124
|
-
},
|
|
1125
|
-
{ mode: `scatter`, icon: `ScatterPlot`, label: `Scatter-only` },
|
|
1126
|
-
{
|
|
1127
|
-
mode: `histogram`,
|
|
1128
|
-
icon: `Histogram`,
|
|
1129
|
-
label: `Histogram-only`,
|
|
1130
|
-
},
|
|
1131
|
-
] as const as
|
|
1132
|
-
option
|
|
1133
|
-
(option.mode)
|
|
1134
|
-
}
|
|
1331
|
+
{#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)}
|
|
1135
1332
|
<button
|
|
1136
1333
|
class="view-mode-option"
|
|
1137
1334
|
class:selected={display_mode === option.mode}
|
|
@@ -1150,22 +1347,14 @@
|
|
|
1150
1347
|
</div>
|
|
1151
1348
|
{/if}
|
|
1152
1349
|
<!-- Fullscreen button - rightmost position -->
|
|
1153
|
-
{#if fullscreen_toggle &&
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
title="{fullscreen ? `Exit` : `Enter`} fullscreen"
|
|
1350
|
+
{#if fullscreen_toggle && controls_config.visible(`fullscreen`)}
|
|
1351
|
+
<FullscreenButton
|
|
1352
|
+
{fullscreen}
|
|
1353
|
+
toggle={fullscreen_toggle}
|
|
1354
|
+
{wrapper}
|
|
1159
1355
|
aria-label="{fullscreen ? `Exit` : `Enter`} fullscreen"
|
|
1160
|
-
aria-pressed={fullscreen}
|
|
1161
1356
|
class="fullscreen-button"
|
|
1162
|
-
|
|
1163
|
-
{#if typeof fullscreen_toggle === `function`}
|
|
1164
|
-
{@render fullscreen_toggle({ fullscreen })}
|
|
1165
|
-
{:else}
|
|
1166
|
-
<Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
|
|
1167
|
-
{/if}
|
|
1168
|
-
</button>
|
|
1357
|
+
/>
|
|
1169
1358
|
{/if}
|
|
1170
1359
|
</div>
|
|
1171
1360
|
{/if}
|
|
@@ -1189,14 +1378,18 @@
|
|
|
1189
1378
|
show_image_atoms: false, // Default to false to avoid atoms popping in/out at cell edges
|
|
1190
1379
|
...structure_props,
|
|
1191
1380
|
}}
|
|
1192
|
-
bind:controls_open
|
|
1381
|
+
bind:controls_open
|
|
1193
1382
|
bind:info_pane_open={structure_info_open}
|
|
1194
1383
|
bind:hidden_elements
|
|
1195
1384
|
/>
|
|
1196
1385
|
{/if}
|
|
1197
1386
|
|
|
1198
1387
|
{#if actual_show_plot}
|
|
1199
|
-
{#if
|
|
1388
|
+
{#if plot_metadata_loading}
|
|
1389
|
+
<div class="plot-metadata-loading plot">
|
|
1390
|
+
<Spinner text="Sampling trajectory plot data..." style="--spinner-size: 1.4em" />
|
|
1391
|
+
</div>
|
|
1392
|
+
{:else if display_mode === `scatter` || display_mode === `structure+scatter`}
|
|
1200
1393
|
<ScatterPlot
|
|
1201
1394
|
series={plot_series}
|
|
1202
1395
|
{x_axis}
|
|
@@ -1205,27 +1398,22 @@
|
|
|
1205
1398
|
controls={scatter_controls}
|
|
1206
1399
|
current_x_value={current_step_idx}
|
|
1207
1400
|
change={plot_skimming ? handle_plot_change : undefined}
|
|
1208
|
-
padding={{ t: 20, b: 60,
|
|
1401
|
+
padding={{ t: 20, b: 60, r: has_y2_series ? 100 : 20 }}
|
|
1209
1402
|
range_padding={0}
|
|
1210
1403
|
style="height: 100%"
|
|
1211
1404
|
{...scatter_props}
|
|
1212
1405
|
legend={{
|
|
1213
|
-
...scatter_props.legend ?? {},
|
|
1406
|
+
...(scatter_props.legend ?? {}),
|
|
1214
1407
|
on_toggle: (series_idx: number) => {
|
|
1215
1408
|
handle_legend_toggle(series_idx)
|
|
1216
1409
|
scatter_props.legend?.on_toggle?.(series_idx)
|
|
1217
1410
|
},
|
|
1218
1411
|
}}
|
|
1219
|
-
class=
|
|
1412
|
+
class={[`plot`, scatter_props.class]}
|
|
1220
1413
|
>
|
|
1221
|
-
{#snippet tooltip({
|
|
1222
|
-
x,
|
|
1223
|
-
y,
|
|
1224
|
-
metadata,
|
|
1225
|
-
label,
|
|
1226
|
-
}: ScatterHandlerProps)}
|
|
1414
|
+
{#snippet tooltip({ x, y, metadata, label }: ScatterHandlerProps)}
|
|
1227
1415
|
{@const formatted_y = typeof y === `number` ? format_num(y) : y}
|
|
1228
|
-
|
|
1416
|
+
{x_axis_quantity}: {Math.round(x)}<br />
|
|
1229
1417
|
{@html sanitize_html(metadata?.series_label || label || `Value`)}: {formatted_y}
|
|
1230
1418
|
{/snippet}
|
|
1231
1419
|
</ScatterPlot>
|
|
@@ -1246,7 +1434,7 @@
|
|
|
1246
1434
|
histogram_props.on_series_toggle?.(series_idx)
|
|
1247
1435
|
}}
|
|
1248
1436
|
style="height: 100%"
|
|
1249
|
-
class=
|
|
1437
|
+
class={[`plot`, histogram_props.class]}
|
|
1250
1438
|
--ctrl-btn-top="6ex"
|
|
1251
1439
|
>
|
|
1252
1440
|
{#snippet tooltip({
|
|
@@ -1270,8 +1458,8 @@
|
|
|
1270
1458
|
<EmptyState class="trajectory-empty-state">
|
|
1271
1459
|
<h3 id="load-trajectory">Load Trajectory</h3>
|
|
1272
1460
|
<p>
|
|
1273
|
-
Drop a trajectory file here (.xyz, .extxyz, .json, .json.gz, XDATCAR, .traj, .h5)
|
|
1274
|
-
|
|
1461
|
+
Drop a trajectory file here (.xyz, .extxyz, .json, .json.gz, XDATCAR, .traj, .h5) or
|
|
1462
|
+
provide trajectory data via props
|
|
1275
1463
|
</p>
|
|
1276
1464
|
<strong style="display: block; margin-block: 1em 1ex">Supported formats:</strong>
|
|
1277
1465
|
<ul>
|
|
@@ -1283,9 +1471,7 @@
|
|
|
1283
1471
|
<li>HDF5 trajectory files (.h5, .hdf5)</li>
|
|
1284
1472
|
<li>Compressed files (.gz)</li>
|
|
1285
1473
|
</ul>
|
|
1286
|
-
<p>
|
|
1287
|
-
💡 Force vectors will be automatically displayed when present in trajectory data
|
|
1288
|
-
</p>
|
|
1474
|
+
<p>💡 Force vectors will be automatically displayed when present in trajectory data</p>
|
|
1289
1475
|
</EmptyState>
|
|
1290
1476
|
{/if}
|
|
1291
1477
|
</div>
|
|
@@ -1303,9 +1489,6 @@
|
|
|
1303
1489
|
contain: layout;
|
|
1304
1490
|
z-index: var(--traj-z-index, 1);
|
|
1305
1491
|
container-type: size; /* enable cqh for panes if explicit height is set */
|
|
1306
|
-
:global(.plot) {
|
|
1307
|
-
background: var(--surface-bg);
|
|
1308
|
-
}
|
|
1309
1492
|
&.active {
|
|
1310
1493
|
z-index: 2; /* needed so info/control panes from an active viewer overlay those of the next (if there is one) */
|
|
1311
1494
|
.trajectory-controls {
|
|
@@ -1316,7 +1499,7 @@
|
|
|
1316
1499
|
height: 100vh !important;
|
|
1317
1500
|
width: 100vw !important;
|
|
1318
1501
|
border-radius: 0 !important;
|
|
1319
|
-
background: var(--surface-bg);
|
|
1502
|
+
background: var(--traj-bg-fullscreen, var(--surface-bg));
|
|
1320
1503
|
overflow: hidden;
|
|
1321
1504
|
}
|
|
1322
1505
|
&.horizontal .content-area {
|
|
@@ -1354,10 +1537,19 @@
|
|
|
1354
1537
|
grid-template-rows: 1fr !important;
|
|
1355
1538
|
}
|
|
1356
1539
|
}
|
|
1540
|
+
.plot-metadata-loading {
|
|
1541
|
+
display: flex;
|
|
1542
|
+
align-items: center;
|
|
1543
|
+
justify-content: center;
|
|
1544
|
+
gap: 0.6em;
|
|
1545
|
+
min-height: 0;
|
|
1546
|
+
color: var(--text-muted, currentColor);
|
|
1547
|
+
background: var(--surface-bg);
|
|
1548
|
+
}
|
|
1357
1549
|
.trajectory-controls {
|
|
1358
1550
|
display: flex;
|
|
1359
1551
|
align-items: center;
|
|
1360
|
-
gap: clamp(
|
|
1552
|
+
gap: clamp(4pt, 1.6cqw, 1.5ex);
|
|
1361
1553
|
padding: clamp(2pt, 0.5cqw, 1ex) clamp(4pt, 1cqw, 1.2ex);
|
|
1362
1554
|
background: var(--surface-bg-hover);
|
|
1363
1555
|
backdrop-filter: blur(4px);
|
|
@@ -1377,7 +1569,7 @@
|
|
|
1377
1569
|
}
|
|
1378
1570
|
button {
|
|
1379
1571
|
background: var(--btn-bg);
|
|
1380
|
-
font-size: clamp(0.
|
|
1572
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
1381
1573
|
&:hover:not(:disabled) {
|
|
1382
1574
|
background: var(--btn-bg-hover);
|
|
1383
1575
|
}
|
|
@@ -1393,7 +1585,14 @@
|
|
|
1393
1585
|
.nav-section {
|
|
1394
1586
|
display: flex;
|
|
1395
1587
|
align-items: center;
|
|
1396
|
-
gap: clamp(
|
|
1588
|
+
gap: clamp(3pt, 1cqw, 9pt);
|
|
1589
|
+
}
|
|
1590
|
+
/* nudge control-button icons slightly larger via transform */
|
|
1591
|
+
.nav-section button,
|
|
1592
|
+
.view-mode-button,
|
|
1593
|
+
.info-section
|
|
1594
|
+
:global(:is(.trajectory-info-toggle, .trajectory-export-toggle, .fullscreen-button)) {
|
|
1595
|
+
transform: scale(1.15);
|
|
1397
1596
|
}
|
|
1398
1597
|
.step-section {
|
|
1399
1598
|
display: flex;
|
|
@@ -1415,7 +1614,8 @@
|
|
|
1415
1614
|
}
|
|
1416
1615
|
.step-slider {
|
|
1417
1616
|
width: 100%;
|
|
1418
|
-
|
|
1617
|
+
position: relative;
|
|
1618
|
+
z-index: 1; /* keep the slider knob above the step labels (which follow it in the DOM) */
|
|
1419
1619
|
}
|
|
1420
1620
|
.step-labels {
|
|
1421
1621
|
position: absolute;
|
|
@@ -1458,7 +1658,8 @@
|
|
|
1458
1658
|
opacity: 0;
|
|
1459
1659
|
}
|
|
1460
1660
|
}
|
|
1461
|
-
.
|
|
1661
|
+
/* :global pierces into FullscreenButton's markup (anchored on local .info-section) */
|
|
1662
|
+
.info-section :global(.fullscreen-button) {
|
|
1462
1663
|
background: transparent !important;
|
|
1463
1664
|
padding: 0;
|
|
1464
1665
|
&:hover:not(:disabled) {
|
|
@@ -1472,7 +1673,7 @@
|
|
|
1472
1673
|
position: relative;
|
|
1473
1674
|
}
|
|
1474
1675
|
.info-section :global(:is(.trajectory-info-toggle, .trajectory-export-toggle)) {
|
|
1475
|
-
font-size:
|
|
1676
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
1476
1677
|
}
|
|
1477
1678
|
.play-button {
|
|
1478
1679
|
min-width: clamp(32px, 4cqw, 36px);
|
|
@@ -1482,10 +1683,7 @@
|
|
|
1482
1683
|
&.playing {
|
|
1483
1684
|
background: var(--traj-pause-btn-bg, var(--btn-bg, rgba(0, 0, 0, 0.1)));
|
|
1484
1685
|
&:hover:not(:disabled) {
|
|
1485
|
-
background: var(
|
|
1486
|
-
--traj-pause-btn-bg-hover,
|
|
1487
|
-
var(--btn-bg-hover, rgba(0, 0, 0, 0.1))
|
|
1488
|
-
);
|
|
1686
|
+
background: var(--traj-pause-btn-bg-hover, var(--btn-bg-hover, rgba(0, 0, 0, 0.1)));
|
|
1489
1687
|
}
|
|
1490
1688
|
}
|
|
1491
1689
|
}
|
|
@@ -1533,14 +1731,20 @@
|
|
|
1533
1731
|
.view-mode-dropdown-wrapper {
|
|
1534
1732
|
display: flex;
|
|
1535
1733
|
position: relative;
|
|
1734
|
+
z-index: var(--trajectory-view-mode-z-index, 20);
|
|
1536
1735
|
}
|
|
1537
1736
|
.view-mode-dropdown {
|
|
1538
1737
|
position: absolute;
|
|
1539
1738
|
top: 115%;
|
|
1540
1739
|
right: 0;
|
|
1541
|
-
|
|
1740
|
+
z-index: 1;
|
|
1741
|
+
min-width: max-content;
|
|
1742
|
+
background: var(--trajectory-view-mode-bg, light-dark(#fff, #2f3137));
|
|
1542
1743
|
border-radius: 4px;
|
|
1543
|
-
box-shadow:
|
|
1744
|
+
box-shadow:
|
|
1745
|
+
0 8px 16px -4px rgba(0, 0, 0, 0.3),
|
|
1746
|
+
0 4px 8px -2px rgba(0, 0, 0, 0.1);
|
|
1747
|
+
pointer-events: auto;
|
|
1544
1748
|
}
|
|
1545
1749
|
.view-mode-option {
|
|
1546
1750
|
display: flex;
|