matterviz 0.4.1 → 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 +75 -85
- 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 +26 -39
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +62 -74
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +511 -547
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +1 -1
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- 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 +92 -95
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +1 -1
- 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 +11 -10
- package/dist/constants.js +11 -9
- 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 -92
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +109 -140
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +75 -94
- 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 +17 -21
- 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/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +36 -36
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +18 -17
- package/dist/coordination/CoordinationBarPlot.svelte +38 -33
- 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 -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/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 -4
- package/dist/element/index.js +3 -18
- 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 +14 -22
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -7
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +37 -64
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +3 -3
- package/dist/fermi-surface/parse.js +22 -24
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +109 -148
- 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/io/ExportPane.svelte +10 -6
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +22 -35
- package/dist/io/export.d.ts +4 -0
- package/dist/io/export.js +5 -1
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- 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.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 +6 -6
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +43 -13
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- 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 +9 -10
- 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 +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 +2 -0
- package/dist/math.js +19 -0
- 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 +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 +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 +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 +3 -3
- 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 +21 -15
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +430 -391
- 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 +29 -36
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +301 -209
- 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.js +1 -1
- 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.js +9 -5
- 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 +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/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/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 +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.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +304 -325
- 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/scatter/BinnedScatterPlot.svelte +263 -109
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +477 -385
- 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/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 +251 -329
- 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 +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 +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +77 -50
- package/dist/spectral/Bands.svelte +223 -230
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -27
- package/dist/spectral/Dos.svelte +70 -73
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +25 -35
- 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 +115 -75
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +11 -26
- 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 +473 -419
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +630 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +157 -399
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +72 -66
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +467 -476
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +386 -0
- package/dist/structure/StructureViewport.svelte.d.ts +63 -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 +104 -38
- package/dist/structure/bonding.d.ts +10 -3
- package/dist/structure/bonding.js +84 -28
- 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 +39 -31
- 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 +16 -1
- package/dist/structure/index.js +13 -2
- 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 +243 -222
- package/dist/structure/partial-occupancy.js +4 -2
- package/dist/structure/pbc.js +78 -98
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +3 -18
- 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 +56 -68
- package/dist/symmetry/WyckoffTable.svelte +5 -24
- package/dist/symmetry/cell-transform.js +1 -0
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +8 -2
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1102 -365
- 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 +515 -308
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +11 -8
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -31
- package/dist/trajectory/TrajectoryInfoPane.svelte +85 -97
- package/dist/trajectory/extract.js +56 -34
- package/dist/trajectory/format-detect.js +11 -9
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +33 -29
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +18 -7
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +3 -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 +2 -3
- package/dist/trajectory/parse/index.js +17 -20
- 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 +5 -0
- package/dist/trajectory/parse/vaspout-h5.js +284 -0
- package/dist/trajectory/parse/xyz.js +11 -7
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +99 -27
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +47 -61
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +2 -4
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +56 -73
- package/package.json +61 -33
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import Icon from '../Icon.svelte'
|
|
8
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
11
|
import type { Vec2 } from '../math'
|
|
12
12
|
import { sanitize_html } from '../sanitize'
|
|
13
13
|
import { FullscreenButton, type FullscreenToggleProp, toggle_fullscreen } from '../layout'
|
|
@@ -20,18 +20,25 @@
|
|
|
20
20
|
import Structure from '../structure/Structure.svelte'
|
|
21
21
|
import { scaleLinear } from 'd3-scale'
|
|
22
22
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
23
|
-
import { untrack } from 'svelte'
|
|
23
|
+
import { onMount, untrack } from 'svelte'
|
|
24
24
|
import { tooltip } from 'svelte-multiselect/attachments'
|
|
25
25
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
26
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
26
27
|
import { full_data_extractor } from './extract'
|
|
27
28
|
import type {
|
|
28
29
|
ParseProgress,
|
|
29
30
|
TrajectoryDataExtractor,
|
|
30
31
|
TrajectoryFrame,
|
|
32
|
+
TrajectoryMetadata,
|
|
31
33
|
TrajectoryType,
|
|
32
34
|
TrajHandlerData,
|
|
33
35
|
} from './index'
|
|
34
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
FRAME_LOAD_DEBOUNCE_MS,
|
|
38
|
+
TrajectoryError,
|
|
39
|
+
TrajectoryExportPane,
|
|
40
|
+
TrajectoryInfoPane,
|
|
41
|
+
} from './index'
|
|
35
42
|
import type { AtomTypeMapping, LoadingOptions } from './parse'
|
|
36
43
|
import {
|
|
37
44
|
get_unsupported_format_message,
|
|
@@ -41,6 +48,7 @@
|
|
|
41
48
|
} from './parse'
|
|
42
49
|
import {
|
|
43
50
|
generate_axis_labels,
|
|
51
|
+
generate_axis_scale_types,
|
|
44
52
|
generate_plot_series,
|
|
45
53
|
generate_streaming_plot_series,
|
|
46
54
|
should_hide_plot,
|
|
@@ -64,6 +72,12 @@
|
|
|
64
72
|
total_frames: number
|
|
65
73
|
on_step_change: (idx: number) => void
|
|
66
74
|
}
|
|
75
|
+
type PlotMetadataStreamMessage = {
|
|
76
|
+
command?: string
|
|
77
|
+
file_path?: string
|
|
78
|
+
plot_metadata?: TrajectoryMetadata[]
|
|
79
|
+
is_complete?: boolean
|
|
80
|
+
}
|
|
67
81
|
|
|
68
82
|
let {
|
|
69
83
|
trajectory = $bindable(),
|
|
@@ -106,85 +120,86 @@
|
|
|
106
120
|
wrapper = $bindable(),
|
|
107
121
|
fullscreen = $bindable(false),
|
|
108
122
|
...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
|
-
|
|
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()
|
|
188
203
|
|
|
189
204
|
let dragover = $state(false)
|
|
190
205
|
let loading = $state(false)
|
|
@@ -193,22 +208,22 @@
|
|
|
193
208
|
let parse_warning_msg = $state<string | undefined>(undefined)
|
|
194
209
|
$effect(() => {
|
|
195
210
|
const warnings = trajectory?.metadata?.parse_warnings
|
|
196
|
-
parse_warning_msg =
|
|
197
|
-
|
|
198
|
-
warnings.join(
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
201
217
|
})
|
|
202
218
|
let is_playing = $state(false)
|
|
203
|
-
|
|
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
|
|
204
222
|
|
|
205
223
|
// Ensure fps is within the allowed range
|
|
206
224
|
$effect(() => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} else if (fps > fps_range[1]) {
|
|
210
|
-
fps = fps_range[1]
|
|
211
|
-
}
|
|
225
|
+
const clamped = Math.max(fps_range[0], Math.min(fps_range[1], fps))
|
|
226
|
+
if (clamped !== fps) fps = clamped
|
|
212
227
|
})
|
|
213
228
|
let current_filename = $state<string | undefined>(undefined)
|
|
214
229
|
let current_file_path = $state<string | null>(null)
|
|
@@ -232,13 +247,26 @@
|
|
|
232
247
|
})
|
|
233
248
|
|
|
234
249
|
// Get total frame count (supports both regular and indexed trajectories)
|
|
235
|
-
let total_frames = $derived(
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
})
|
|
238
265
|
|
|
239
266
|
// Current frame - load on demand for indexed trajectories
|
|
240
267
|
let current_frame = $state<TrajectoryFrame | null>(null)
|
|
241
268
|
let frame_load_request_id = 0
|
|
269
|
+
let frame_load_timeout: ReturnType<typeof setTimeout> | undefined
|
|
242
270
|
|
|
243
271
|
// Auto-play when trajectory changes (handles both props and file loading)
|
|
244
272
|
$effect(() => {
|
|
@@ -252,8 +280,9 @@
|
|
|
252
280
|
if (trajectory && current_step_idx >= 0 && current_step_idx < total_frames) {
|
|
253
281
|
if (trajectory.frame_loader) {
|
|
254
282
|
// Load frame on demand (works for both indexed files and external streaming)
|
|
255
|
-
|
|
283
|
+
schedule_frame_load_on_demand(current_step_idx)
|
|
256
284
|
} else {
|
|
285
|
+
clear_frame_load_timeout()
|
|
257
286
|
// Use in-memory frame for regular trajectories
|
|
258
287
|
current_frame = trajectory.frames[current_step_idx] || null
|
|
259
288
|
}
|
|
@@ -262,11 +291,169 @@
|
|
|
262
291
|
}
|
|
263
292
|
})
|
|
264
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
|
+
|
|
265
451
|
// Load frame on demand - works for both indexed files and external streaming
|
|
266
452
|
async function load_frame_on_demand(frame_idx: number) {
|
|
267
453
|
const load_trajectory = trajectory
|
|
268
454
|
const frame_loader = load_trajectory?.frame_loader
|
|
269
455
|
if (!load_trajectory || !frame_loader) return
|
|
456
|
+
ensure_frame_cache_owner()
|
|
270
457
|
|
|
271
458
|
const request_id = ++frame_load_request_id
|
|
272
459
|
const request_is_current = () =>
|
|
@@ -274,13 +461,18 @@
|
|
|
274
461
|
trajectory === load_trajectory &&
|
|
275
462
|
current_step_idx === frame_idx
|
|
276
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
|
|
277
466
|
try {
|
|
278
467
|
const frame = await frame_loader.load_frame(
|
|
279
|
-
orig_data || ``, //
|
|
468
|
+
orig_data || ``, // original_data for indexed files, empty string for external streaming
|
|
280
469
|
frame_idx,
|
|
281
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)
|
|
282
473
|
if (!request_is_current()) return
|
|
283
474
|
current_frame = frame
|
|
475
|
+
prefetch_frames(frame_idx) // warm upcoming frames for smooth playback/scrub
|
|
284
476
|
} catch (error) {
|
|
285
477
|
if (!request_is_current()) return
|
|
286
478
|
console.error(`Failed to load frame ${frame_idx}:`, error)
|
|
@@ -292,6 +484,8 @@
|
|
|
292
484
|
step_idx: frame_idx,
|
|
293
485
|
frame_count: total_frames,
|
|
294
486
|
})
|
|
487
|
+
} finally {
|
|
488
|
+
inflight_frames.delete(frame_idx)
|
|
295
489
|
}
|
|
296
490
|
}
|
|
297
491
|
|
|
@@ -299,7 +493,7 @@
|
|
|
299
493
|
let current_structure = $derived(current_frame?.structure)
|
|
300
494
|
|
|
301
495
|
// Track hidden elements (persists across frame changes)
|
|
302
|
-
let hidden_elements = $state(new
|
|
496
|
+
let hidden_elements = $state(new SvelteSet<ElementSymbol>())
|
|
303
497
|
|
|
304
498
|
let step_label_positions = $derived.by((): number[] => {
|
|
305
499
|
if (!step_labels || total_frames <= 1) return []
|
|
@@ -310,11 +504,14 @@
|
|
|
310
504
|
|
|
311
505
|
if (typeof step_labels === `number`) {
|
|
312
506
|
if (step_labels > 0) {
|
|
313
|
-
return scaleLinear()
|
|
507
|
+
return scaleLinear()
|
|
508
|
+
.domain([0, total_frames - 1])
|
|
509
|
+
.nice()
|
|
314
510
|
.ticks(Math.min(step_labels, total_frames))
|
|
315
511
|
.map((tick) => Math.round(tick))
|
|
316
|
-
.filter(
|
|
317
|
-
tick
|
|
512
|
+
.filter(
|
|
513
|
+
(tick, idx, ticks) =>
|
|
514
|
+
tick >= 0 && tick < total_frames && ticks.indexOf(tick) === idx,
|
|
318
515
|
)
|
|
319
516
|
}
|
|
320
517
|
if (step_labels < 0) {
|
|
@@ -335,11 +532,13 @@
|
|
|
335
532
|
let extended_config = $derived.by(() => {
|
|
336
533
|
if (!ELEM_PROPERTY_LABELS) return trajectory_property_config
|
|
337
534
|
|
|
338
|
-
const custom_config: Record<string,
|
|
535
|
+
const custom_config: Record<string, TrajPropertyConfig> = {}
|
|
339
536
|
for (const [key, label] of Object.entries(ELEM_PROPERTY_LABELS)) {
|
|
340
|
-
const existing =
|
|
341
|
-
trajectory_property_config[key.toLowerCase()]
|
|
342
|
-
|
|
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 || `` }
|
|
343
542
|
}
|
|
344
543
|
return { ...trajectory_property_config, ...custom_config }
|
|
345
544
|
})
|
|
@@ -392,7 +591,8 @@
|
|
|
392
591
|
|
|
393
592
|
// Only update if changed (use untrack to avoid circular dependency)
|
|
394
593
|
const current = untrack(() => visible_properties) || []
|
|
395
|
-
const has_changed =
|
|
594
|
+
const has_changed =
|
|
595
|
+
visible_keys.length !== current.length ||
|
|
396
596
|
!visible_keys.every((key, idx) => key === current[idx])
|
|
397
597
|
|
|
398
598
|
if (has_changed) {
|
|
@@ -407,27 +607,35 @@
|
|
|
407
607
|
plot_series = toggle_series_visibility(plot_series, series_idx)
|
|
408
608
|
}
|
|
409
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`)
|
|
410
612
|
let x_axis = $derived({
|
|
411
|
-
label:
|
|
412
|
-
|
|
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`,
|
|
413
616
|
ticks: step_label_positions,
|
|
414
617
|
})
|
|
415
618
|
// Generate axis labels based on first visible series on each axis
|
|
416
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))
|
|
417
623
|
let y_axis = $derived({
|
|
418
624
|
label: y_axis_labels.y1,
|
|
419
|
-
format:
|
|
625
|
+
format: `~g`,
|
|
420
626
|
label_shift: { y: 10 },
|
|
627
|
+
scale_type: y_axis_scale_types.y1,
|
|
421
628
|
})
|
|
422
629
|
let y2_axis = $derived({
|
|
423
630
|
label: y_axis_labels.y2,
|
|
424
|
-
format:
|
|
631
|
+
format: `~g`,
|
|
425
632
|
label_shift: { y: 80 },
|
|
633
|
+
scale_type: y_axis_scale_types.y2,
|
|
426
634
|
})
|
|
427
|
-
|
|
428
635
|
// hide plot if all plotted values are constant (no variation)
|
|
429
636
|
let show_plot = $derived(
|
|
430
|
-
display_mode !== `structure` &&
|
|
637
|
+
display_mode !== `structure` &&
|
|
638
|
+
(plot_metadata_loading || !should_hide_plot(trajectory, plot_series)),
|
|
431
639
|
)
|
|
432
640
|
|
|
433
641
|
// Determine what to show based on display mode
|
|
@@ -435,23 +643,28 @@
|
|
|
435
643
|
let actual_show_plot = $derived(display_mode !== `structure` && show_plot)
|
|
436
644
|
|
|
437
645
|
// Check if there are any Y2 series to determine padding
|
|
438
|
-
let has_y2_series = $derived(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
+
}
|
|
442
662
|
// Step navigation functions
|
|
443
663
|
function next_step() {
|
|
444
664
|
if (current_step_idx < total_frames - 1) {
|
|
445
665
|
current_step_idx++
|
|
446
666
|
// Streaming frame loading handled by reactive effect
|
|
447
|
-
|
|
448
|
-
on_step_change?.({
|
|
449
|
-
trajectory,
|
|
450
|
-
step_idx: current_step_idx,
|
|
451
|
-
frame_count: total_frames,
|
|
452
|
-
frame: current_frame || undefined,
|
|
453
|
-
})
|
|
454
|
-
}
|
|
667
|
+
notify_step_change()
|
|
455
668
|
}
|
|
456
669
|
}
|
|
457
670
|
|
|
@@ -459,14 +672,7 @@
|
|
|
459
672
|
if (current_step_idx > 0) {
|
|
460
673
|
current_step_idx--
|
|
461
674
|
// Streaming frame loading handled by reactive effect
|
|
462
|
-
|
|
463
|
-
on_step_change?.({
|
|
464
|
-
trajectory,
|
|
465
|
-
step_idx: current_step_idx,
|
|
466
|
-
frame_count: total_frames,
|
|
467
|
-
frame: current_frame || undefined,
|
|
468
|
-
})
|
|
469
|
-
}
|
|
675
|
+
notify_step_change()
|
|
470
676
|
}
|
|
471
677
|
}
|
|
472
678
|
|
|
@@ -475,14 +681,7 @@
|
|
|
475
681
|
current_step_idx = idx
|
|
476
682
|
// Note: streaming frame loading is handled by reactive effect
|
|
477
683
|
// Handle callbacks for both traditional and streaming modes
|
|
478
|
-
|
|
479
|
-
on_step_change?.({
|
|
480
|
-
trajectory,
|
|
481
|
-
step_idx: current_step_idx,
|
|
482
|
-
frame_count: total_frames,
|
|
483
|
-
frame: current_frame || undefined,
|
|
484
|
-
})
|
|
485
|
-
}
|
|
684
|
+
notify_step_change()
|
|
486
685
|
}
|
|
487
686
|
}
|
|
488
687
|
|
|
@@ -501,7 +700,7 @@
|
|
|
501
700
|
reader.addEventListener(`error`, () => reject(new Error(`Failed to read file`)))
|
|
502
701
|
|
|
503
702
|
// Read as text for text-based formats, binary for others
|
|
504
|
-
if (
|
|
703
|
+
if (/\.(?:xyz|json|extxyz|lammpstrj)$/.test(file.name.toLowerCase())) {
|
|
505
704
|
reader.readAsText(file)
|
|
506
705
|
} else reader.readAsArrayBuffer(file)
|
|
507
706
|
})
|
|
@@ -528,44 +727,50 @@
|
|
|
528
727
|
})
|
|
529
728
|
}
|
|
530
729
|
}
|
|
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
|
-
})
|
|
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
|
+
}
|
|
565
745
|
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
}
|
|
569
774
|
})
|
|
570
775
|
|
|
571
776
|
// Handle internal file format drops
|
|
@@ -582,10 +787,7 @@
|
|
|
582
787
|
const array_buffer = await response.arrayBuffer()
|
|
583
788
|
await load_trajectory_data(array_buffer, file_info.name)
|
|
584
789
|
} else {
|
|
585
|
-
console.warn(
|
|
586
|
-
`Binary file without ArrayBuffer or blob URL:`,
|
|
587
|
-
file_info.name,
|
|
588
|
-
)
|
|
790
|
+
console.warn(`Binary file without ArrayBuffer or blob URL:`, file_info.name)
|
|
589
791
|
}
|
|
590
792
|
} else {
|
|
591
793
|
await load_trajectory_data(file_info.content, file_info.name)
|
|
@@ -607,9 +809,7 @@
|
|
|
607
809
|
|
|
608
810
|
try {
|
|
609
811
|
// Check for our custom internal file format first
|
|
610
|
-
const internal_data = event.dataTransfer?.getData(
|
|
611
|
-
`application/x-matterviz-file`,
|
|
612
|
-
)
|
|
812
|
+
const internal_data = event.dataTransfer?.getData(`application/x-matterviz-file`)
|
|
613
813
|
if (internal_data) {
|
|
614
814
|
const handled = await handle_internal_file_drop(internal_data)
|
|
615
815
|
if (handled) return
|
|
@@ -618,9 +818,8 @@
|
|
|
618
818
|
// Handle URL-based files (e.g. from FilePicker)
|
|
619
819
|
const handled = await handle_url_drop(event, async (content, filename) => {
|
|
620
820
|
current_filename = filename
|
|
621
|
-
file_size =
|
|
622
|
-
? content.byteLength
|
|
623
|
-
: new Blob([content]).size
|
|
821
|
+
file_size =
|
|
822
|
+
content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
|
|
624
823
|
await load_trajectory_data(content, filename)
|
|
625
824
|
}).catch(() => false)
|
|
626
825
|
|
|
@@ -658,16 +857,16 @@
|
|
|
658
857
|
}
|
|
659
858
|
}
|
|
660
859
|
|
|
661
|
-
$effect(() => {
|
|
860
|
+
$effect(() => {
|
|
861
|
+
// Load trajectory from URL when data_url is provided
|
|
662
862
|
if (data_url && !trajectory) {
|
|
663
863
|
loading = true
|
|
664
864
|
error_msg = null
|
|
665
865
|
|
|
666
866
|
load_from_url(data_url, async (content, filename) => {
|
|
667
867
|
current_filename = filename
|
|
668
|
-
file_size =
|
|
669
|
-
? content.byteLength
|
|
670
|
-
: new Blob([content]).size
|
|
868
|
+
file_size =
|
|
869
|
+
content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
|
|
671
870
|
await load_trajectory_data(content, filename)
|
|
672
871
|
})
|
|
673
872
|
.then(() => {
|
|
@@ -705,30 +904,34 @@
|
|
|
705
904
|
const data_size = data instanceof ArrayBuffer ? data.byteLength : data.length
|
|
706
905
|
|
|
707
906
|
// Determine loading strategy based on file size
|
|
708
|
-
const bin_file_threshold = loading_options.bin_file_threshold ??
|
|
709
|
-
|
|
710
|
-
const text_file_threshold = loading_options.text_file_threshold ??
|
|
711
|
-
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
|
|
712
909
|
if (
|
|
713
910
|
(data instanceof ArrayBuffer && data_size > bin_file_threshold) ||
|
|
714
911
|
(typeof data === `string` && data_size > text_file_threshold)
|
|
715
|
-
) {
|
|
912
|
+
) {
|
|
913
|
+
// Large files: Use indexed loading
|
|
716
914
|
await load_with_indexing(data, filename)
|
|
717
915
|
} else {
|
|
718
916
|
// Small files: Use regular loading
|
|
719
917
|
const merged_options = { ...loading_options, atom_type_mapping }
|
|
720
|
-
trajectory = await parse_trajectory_async(
|
|
721
|
-
|
|
722
|
-
|
|
918
|
+
trajectory = await parse_trajectory_async(
|
|
919
|
+
data,
|
|
920
|
+
filename,
|
|
921
|
+
(progress) => {
|
|
922
|
+
parsing_progress = progress
|
|
923
|
+
},
|
|
924
|
+
merged_options,
|
|
925
|
+
)
|
|
723
926
|
}
|
|
724
927
|
|
|
725
928
|
current_step_idx = 0
|
|
726
929
|
current_filename = filename
|
|
727
930
|
|
|
728
|
-
const file_size_bytes =
|
|
729
|
-
? data.byteLength
|
|
730
|
-
|
|
731
|
-
|
|
931
|
+
const file_size_bytes =
|
|
932
|
+
data instanceof ArrayBuffer ? data.byteLength : new Blob([data]).size
|
|
933
|
+
on_file_load?.({
|
|
934
|
+
// emit file load event
|
|
732
935
|
trajectory,
|
|
733
936
|
frame_count: trajectory?.frames.length ?? 0,
|
|
734
937
|
total_atoms: trajectory?.frames[0]?.structure.sites.length ?? 0,
|
|
@@ -744,7 +947,8 @@
|
|
|
744
947
|
current_filename = undefined
|
|
745
948
|
file_size = undefined
|
|
746
949
|
|
|
747
|
-
on_error?.({
|
|
950
|
+
on_error?.({
|
|
951
|
+
// emit error event
|
|
748
952
|
error_msg,
|
|
749
953
|
filename: current_filename || undefined,
|
|
750
954
|
file_size: file_size || undefined,
|
|
@@ -757,15 +961,21 @@
|
|
|
757
961
|
|
|
758
962
|
// Load using indexed parsing for large files
|
|
759
963
|
async function load_with_indexing(data: string | ArrayBuffer, filename: string) {
|
|
760
|
-
try {
|
|
964
|
+
try {
|
|
965
|
+
// Use indexed parsing for efficient large file handling
|
|
761
966
|
const merged_options = {
|
|
762
967
|
use_indexing: true,
|
|
763
968
|
...loading_options,
|
|
764
969
|
atom_type_mapping,
|
|
765
970
|
}
|
|
766
|
-
trajectory = await parse_trajectory_async(
|
|
767
|
-
|
|
768
|
-
|
|
971
|
+
trajectory = await parse_trajectory_async(
|
|
972
|
+
data,
|
|
973
|
+
filename,
|
|
974
|
+
(progress) => {
|
|
975
|
+
parsing_progress = progress
|
|
976
|
+
},
|
|
977
|
+
merged_options,
|
|
978
|
+
)
|
|
769
979
|
|
|
770
980
|
// Keep original data for on-demand frame loads only when indexed parsing attached a
|
|
771
981
|
// frame_loader. Direct-parse fallbacks (e.g. large JSON or extensionless blob:
|
|
@@ -808,8 +1018,7 @@
|
|
|
808
1018
|
// Don't handle shortcuts if user is typing in an input field (but allow if it's our step input and not focused)
|
|
809
1019
|
const target = event.target instanceof HTMLElement ? event.target : null
|
|
810
1020
|
const is_step_input = target?.classList.contains(`step-input`) ?? false
|
|
811
|
-
const is_input_focused =
|
|
812
|
-
target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
1021
|
+
const is_input_focused = target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
813
1022
|
|
|
814
1023
|
// Skip if typing in an input that's not our step input
|
|
815
1024
|
if (is_input_focused && !is_step_input) return false
|
|
@@ -848,7 +1057,7 @@
|
|
|
848
1057
|
go_to_step(Math.max(0, current_step_idx - 25))
|
|
849
1058
|
} else if (event.key === `PageDown`) {
|
|
850
1059
|
go_to_step(Math.min(total_frames - 1, current_step_idx + 25))
|
|
851
|
-
}
|
|
1060
|
+
} // Interface shortcuts
|
|
852
1061
|
else if (event.key === `f` && fullscreen_toggle) toggle_fullscreen(wrapper)
|
|
853
1062
|
// 'i' key handled by the TrajectoryInfoPane's built-in toggle
|
|
854
1063
|
// Playback speed shortcuts (only when playing)
|
|
@@ -858,14 +1067,14 @@
|
|
|
858
1067
|
} else if (event.key === `-` && is_playing) {
|
|
859
1068
|
fps = Math.max(fps_range[0], fps - 0.2)
|
|
860
1069
|
on_frame_rate_change?.({ trajectory, fps })
|
|
861
|
-
}
|
|
1070
|
+
} // System shortcuts
|
|
862
1071
|
else if (event.key === `Escape`) {
|
|
863
1072
|
if (document.fullscreenElement) document.exitFullscreen()
|
|
864
1073
|
else if (view_mode_dropdown_open) view_mode_dropdown_open = false
|
|
865
1074
|
// Escape key for info pane handled by DraggablePane
|
|
866
|
-
}
|
|
1075
|
+
} // Number keys 0-9 - jump to percentage of trajectory
|
|
867
1076
|
else if (event.key >= `0` && event.key <= `9`) {
|
|
868
|
-
go_to_step(Math.floor((
|
|
1077
|
+
go_to_step(Math.floor((Number(event.key) / 10) * (total_frames - 1)))
|
|
869
1078
|
} else handled = false
|
|
870
1079
|
|
|
871
1080
|
return handled
|
|
@@ -889,8 +1098,12 @@
|
|
|
889
1098
|
|
|
890
1099
|
<div
|
|
891
1100
|
class:dragover
|
|
892
|
-
class:active={is_playing ||
|
|
893
|
-
|
|
1101
|
+
class:active={is_playing ||
|
|
1102
|
+
structure_info_open ||
|
|
1103
|
+
controls_open ||
|
|
1104
|
+
scatter_controls.open ||
|
|
1105
|
+
trajectory_export_open ||
|
|
1106
|
+
info_pane_open}
|
|
894
1107
|
bind:this={wrapper}
|
|
895
1108
|
bind:clientWidth={element_size.width}
|
|
896
1109
|
bind:clientHeight={element_size.height}
|
|
@@ -900,13 +1113,17 @@
|
|
|
900
1113
|
onmouseenter={() => (hovered = true)}
|
|
901
1114
|
onmouseleave={() => (hovered = false)}
|
|
902
1115
|
ondrop={handle_file_drop}
|
|
903
|
-
{...drag_over_handlers({
|
|
1116
|
+
{...drag_over_handlers({
|
|
1117
|
+
allow: () => allow_file_drop,
|
|
1118
|
+
set_dragover: (over) => (dragover = over),
|
|
1119
|
+
})}
|
|
904
1120
|
onclick={handle_click_outside}
|
|
905
1121
|
onkeydown={handle_and_prevent(onkeydown)}
|
|
906
1122
|
{...rest}
|
|
907
|
-
class=
|
|
1123
|
+
class={[`trajectory`, actual_layout, rest.class]}
|
|
908
1124
|
class:show-both-views={[`structure+scatter`, `structure+histogram`].includes(display_mode) &&
|
|
909
|
-
|
|
1125
|
+
actual_show_plot &&
|
|
1126
|
+
show_structure}
|
|
910
1127
|
>
|
|
911
1128
|
{#if loading}
|
|
912
1129
|
{@const text = parsing_progress
|
|
@@ -918,11 +1135,7 @@
|
|
|
918
1135
|
{...spinner_props}
|
|
919
1136
|
/>
|
|
920
1137
|
{:else if error_msg}
|
|
921
|
-
<TrajectoryError
|
|
922
|
-
{error_msg}
|
|
923
|
-
on_dismiss={() => (error_msg = null)}
|
|
924
|
-
{error_snippet}
|
|
925
|
-
/>
|
|
1138
|
+
<TrajectoryError {error_msg} on_dismiss={() => (error_msg = null)} {error_snippet} />
|
|
926
1139
|
{:else if trajectory}
|
|
927
1140
|
{#if parse_warning_msg}
|
|
928
1141
|
<StatusMessage
|
|
@@ -934,17 +1147,14 @@
|
|
|
934
1147
|
{/if}
|
|
935
1148
|
<!-- Trajectory Controls -->
|
|
936
1149
|
{#if controls_config.mode !== `never`}
|
|
937
|
-
<div
|
|
938
|
-
class="trajectory-controls {controls_config.class}"
|
|
939
|
-
style={controls_config.style}
|
|
940
|
-
>
|
|
1150
|
+
<div class="trajectory-controls {controls_config.class}" style={controls_config.style}>
|
|
941
1151
|
{#if trajectory_controls}
|
|
942
1152
|
{@render trajectory_controls({
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1153
|
+
trajectory,
|
|
1154
|
+
current_step_idx,
|
|
1155
|
+
total_frames: total_frames,
|
|
1156
|
+
on_step_change: go_to_step,
|
|
1157
|
+
})}
|
|
948
1158
|
{:else}
|
|
949
1159
|
{#if current_filename && controls_config.visible(`filename`)}
|
|
950
1160
|
<button
|
|
@@ -955,7 +1165,7 @@
|
|
|
955
1165
|
if (current_filename) {
|
|
956
1166
|
navigator.clipboard.writeText(current_filename)
|
|
957
1167
|
filename_copied = true
|
|
958
|
-
setTimeout(() => filename_copied = false, 1000)
|
|
1168
|
+
setTimeout(() => (filename_copied = false), 1000)
|
|
959
1169
|
}
|
|
960
1170
|
}}
|
|
961
1171
|
>
|
|
@@ -963,7 +1173,7 @@
|
|
|
963
1173
|
{#if filename_copied}
|
|
964
1174
|
<Icon
|
|
965
1175
|
icon="Check"
|
|
966
|
-
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"
|
|
967
1177
|
/>
|
|
968
1178
|
{/if}
|
|
969
1179
|
</button>
|
|
@@ -975,14 +1185,16 @@
|
|
|
975
1185
|
<button
|
|
976
1186
|
onclick={prev_step}
|
|
977
1187
|
disabled={current_step_idx === 0 || is_playing}
|
|
978
|
-
title="Previous step"
|
|
1188
|
+
title="Previous step (←) · Home: first · j: −10 · PageUp: −25"
|
|
979
1189
|
>
|
|
980
1190
|
⏮
|
|
981
1191
|
</button>
|
|
982
1192
|
<button
|
|
983
1193
|
onclick={toggle_play}
|
|
984
1194
|
disabled={total_frames <= 1}
|
|
985
|
-
title={
|
|
1195
|
+
title={`${
|
|
1196
|
+
is_playing ? `Pause` : `Play`
|
|
1197
|
+
} (Space) · ←/→ step · 0-9 jump % · +/- speed · f fullscreen`}
|
|
986
1198
|
class="play-button"
|
|
987
1199
|
class:playing={is_playing}
|
|
988
1200
|
>
|
|
@@ -991,7 +1203,7 @@
|
|
|
991
1203
|
<button
|
|
992
1204
|
onclick={next_step}
|
|
993
1205
|
disabled={current_step_idx === total_frames - 1 || is_playing}
|
|
994
|
-
title="Next step"
|
|
1206
|
+
title="Next step (→) · End: last · l: +10 · PageDown: +25"
|
|
995
1207
|
>
|
|
996
1208
|
⏭
|
|
997
1209
|
</button>
|
|
@@ -1006,6 +1218,7 @@
|
|
|
1006
1218
|
min="0"
|
|
1007
1219
|
max={total_frames - 1}
|
|
1008
1220
|
bind:value={current_step_idx}
|
|
1221
|
+
oninput={(event) => notify_step_change(event.currentTarget.valueAsNumber)}
|
|
1009
1222
|
class="step-input"
|
|
1010
1223
|
title="Enter step number to jump to"
|
|
1011
1224
|
aria-label="Step input"
|
|
@@ -1018,15 +1231,15 @@
|
|
|
1018
1231
|
min="0"
|
|
1019
1232
|
max={total_frames - 1}
|
|
1020
1233
|
bind:value={current_step_idx}
|
|
1234
|
+
oninput={(event) => notify_step_change(event.currentTarget.valueAsNumber)}
|
|
1021
1235
|
class="step-slider"
|
|
1022
1236
|
title="Drag to navigate steps"
|
|
1023
1237
|
/>
|
|
1024
1238
|
{#if step_label_positions.length > 0}
|
|
1025
1239
|
<div class="step-labels">
|
|
1026
1240
|
{#each step_label_positions as step_idx (step_idx)}
|
|
1027
|
-
{@const position_percent =
|
|
1028
|
-
|
|
1029
|
-
: 0}
|
|
1241
|
+
{@const position_percent =
|
|
1242
|
+
total_frames > 1 ? (step_idx / (total_frames - 1)) * 100 : 0}
|
|
1030
1243
|
{@const adjusted_position = 1.5 + (position_percent * (100 - 2)) / 100}
|
|
1031
1244
|
<div class="step-tick" style:left="{adjusted_position}%"></div>
|
|
1032
1245
|
<div class="step-label" style:left="{adjusted_position}%">
|
|
@@ -1039,8 +1252,8 @@
|
|
|
1039
1252
|
</div>
|
|
1040
1253
|
{/if}
|
|
1041
1254
|
|
|
1042
|
-
<!-- Frame rate control -
|
|
1043
|
-
{#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`)}
|
|
1044
1257
|
<label
|
|
1045
1258
|
class="fps-section"
|
|
1046
1259
|
style="font-size: 0.9em; display: flex; align-items: center; gap: 5pt; margin-inline: 6pt"
|
|
@@ -1052,7 +1265,7 @@
|
|
|
1052
1265
|
max={fps_range[1]}
|
|
1053
1266
|
bind:value={fps}
|
|
1054
1267
|
title="Frame rate: {format_num(fps, `.2~s`)} fps"
|
|
1055
|
-
style="width: clamp(60px, 8cqw, 90px)
|
|
1268
|
+
style="width: clamp(60px, 8cqw, 90px)"
|
|
1056
1269
|
/>
|
|
1057
1270
|
<input
|
|
1058
1271
|
type="number"
|
|
@@ -1091,8 +1304,7 @@
|
|
|
1091
1304
|
/>
|
|
1092
1305
|
{/if}
|
|
1093
1306
|
<!-- Display mode dropdown -->
|
|
1094
|
-
{#if plot_series.length > 0 &&
|
|
1095
|
-
controls_config.visible(`view-mode`)}
|
|
1307
|
+
{#if plot_series.length > 0 && controls_config.visible(`view-mode`)}
|
|
1096
1308
|
<div class="view-mode-dropdown-wrapper">
|
|
1097
1309
|
<button
|
|
1098
1310
|
onclick={() => (view_mode_dropdown_open = !view_mode_dropdown_open)}
|
|
@@ -1102,40 +1314,21 @@
|
|
|
1102
1314
|
style="background-color: transparent; padding: 0"
|
|
1103
1315
|
>
|
|
1104
1316
|
<Icon
|
|
1105
|
-
icon={(
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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]}
|
|
1112
1326
|
/>
|
|
1113
1327
|
<Icon icon={view_mode_dropdown_open ? `ArrowUp` : `ArrowDown`} />
|
|
1114
1328
|
</button>
|
|
1115
1329
|
{#if view_mode_dropdown_open}
|
|
1116
1330
|
<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
|
-
}
|
|
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)}
|
|
1139
1332
|
<button
|
|
1140
1333
|
class="view-mode-option"
|
|
1141
1334
|
class:selected={display_mode === option.mode}
|
|
@@ -1192,7 +1385,11 @@
|
|
|
1192
1385
|
{/if}
|
|
1193
1386
|
|
|
1194
1387
|
{#if actual_show_plot}
|
|
1195
|
-
{#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`}
|
|
1196
1393
|
<ScatterPlot
|
|
1197
1394
|
series={plot_series}
|
|
1198
1395
|
{x_axis}
|
|
@@ -1201,27 +1398,22 @@
|
|
|
1201
1398
|
controls={scatter_controls}
|
|
1202
1399
|
current_x_value={current_step_idx}
|
|
1203
1400
|
change={plot_skimming ? handle_plot_change : undefined}
|
|
1204
|
-
padding={{ t: 20, b: 60,
|
|
1401
|
+
padding={{ t: 20, b: 60, r: has_y2_series ? 100 : 20 }}
|
|
1205
1402
|
range_padding={0}
|
|
1206
1403
|
style="height: 100%"
|
|
1207
1404
|
{...scatter_props}
|
|
1208
1405
|
legend={{
|
|
1209
|
-
...scatter_props.legend ?? {},
|
|
1406
|
+
...(scatter_props.legend ?? {}),
|
|
1210
1407
|
on_toggle: (series_idx: number) => {
|
|
1211
1408
|
handle_legend_toggle(series_idx)
|
|
1212
1409
|
scatter_props.legend?.on_toggle?.(series_idx)
|
|
1213
1410
|
},
|
|
1214
1411
|
}}
|
|
1215
|
-
class=
|
|
1412
|
+
class={[`plot`, scatter_props.class]}
|
|
1216
1413
|
>
|
|
1217
|
-
{#snippet tooltip({
|
|
1218
|
-
x,
|
|
1219
|
-
y,
|
|
1220
|
-
metadata,
|
|
1221
|
-
label,
|
|
1222
|
-
}: ScatterHandlerProps)}
|
|
1414
|
+
{#snippet tooltip({ x, y, metadata, label }: ScatterHandlerProps)}
|
|
1223
1415
|
{@const formatted_y = typeof y === `number` ? format_num(y) : y}
|
|
1224
|
-
|
|
1416
|
+
{x_axis_quantity}: {Math.round(x)}<br />
|
|
1225
1417
|
{@html sanitize_html(metadata?.series_label || label || `Value`)}: {formatted_y}
|
|
1226
1418
|
{/snippet}
|
|
1227
1419
|
</ScatterPlot>
|
|
@@ -1242,7 +1434,7 @@
|
|
|
1242
1434
|
histogram_props.on_series_toggle?.(series_idx)
|
|
1243
1435
|
}}
|
|
1244
1436
|
style="height: 100%"
|
|
1245
|
-
class=
|
|
1437
|
+
class={[`plot`, histogram_props.class]}
|
|
1246
1438
|
--ctrl-btn-top="6ex"
|
|
1247
1439
|
>
|
|
1248
1440
|
{#snippet tooltip({
|
|
@@ -1266,8 +1458,8 @@
|
|
|
1266
1458
|
<EmptyState class="trajectory-empty-state">
|
|
1267
1459
|
<h3 id="load-trajectory">Load Trajectory</h3>
|
|
1268
1460
|
<p>
|
|
1269
|
-
Drop a trajectory file here (.xyz, .extxyz, .json, .json.gz, XDATCAR, .traj, .h5)
|
|
1270
|
-
|
|
1461
|
+
Drop a trajectory file here (.xyz, .extxyz, .json, .json.gz, XDATCAR, .traj, .h5) or
|
|
1462
|
+
provide trajectory data via props
|
|
1271
1463
|
</p>
|
|
1272
1464
|
<strong style="display: block; margin-block: 1em 1ex">Supported formats:</strong>
|
|
1273
1465
|
<ul>
|
|
@@ -1279,9 +1471,7 @@
|
|
|
1279
1471
|
<li>HDF5 trajectory files (.h5, .hdf5)</li>
|
|
1280
1472
|
<li>Compressed files (.gz)</li>
|
|
1281
1473
|
</ul>
|
|
1282
|
-
<p>
|
|
1283
|
-
💡 Force vectors will be automatically displayed when present in trajectory data
|
|
1284
|
-
</p>
|
|
1474
|
+
<p>💡 Force vectors will be automatically displayed when present in trajectory data</p>
|
|
1285
1475
|
</EmptyState>
|
|
1286
1476
|
{/if}
|
|
1287
1477
|
</div>
|
|
@@ -1299,9 +1489,6 @@
|
|
|
1299
1489
|
contain: layout;
|
|
1300
1490
|
z-index: var(--traj-z-index, 1);
|
|
1301
1491
|
container-type: size; /* enable cqh for panes if explicit height is set */
|
|
1302
|
-
:global(.plot) {
|
|
1303
|
-
background: var(--surface-bg);
|
|
1304
|
-
}
|
|
1305
1492
|
&.active {
|
|
1306
1493
|
z-index: 2; /* needed so info/control panes from an active viewer overlay those of the next (if there is one) */
|
|
1307
1494
|
.trajectory-controls {
|
|
@@ -1350,10 +1537,19 @@
|
|
|
1350
1537
|
grid-template-rows: 1fr !important;
|
|
1351
1538
|
}
|
|
1352
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
|
+
}
|
|
1353
1549
|
.trajectory-controls {
|
|
1354
1550
|
display: flex;
|
|
1355
1551
|
align-items: center;
|
|
1356
|
-
gap: clamp(
|
|
1552
|
+
gap: clamp(4pt, 1.6cqw, 1.5ex);
|
|
1357
1553
|
padding: clamp(2pt, 0.5cqw, 1ex) clamp(4pt, 1cqw, 1.2ex);
|
|
1358
1554
|
background: var(--surface-bg-hover);
|
|
1359
1555
|
backdrop-filter: blur(4px);
|
|
@@ -1373,7 +1569,7 @@
|
|
|
1373
1569
|
}
|
|
1374
1570
|
button {
|
|
1375
1571
|
background: var(--btn-bg);
|
|
1376
|
-
font-size: clamp(0.
|
|
1572
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
1377
1573
|
&:hover:not(:disabled) {
|
|
1378
1574
|
background: var(--btn-bg-hover);
|
|
1379
1575
|
}
|
|
@@ -1389,7 +1585,14 @@
|
|
|
1389
1585
|
.nav-section {
|
|
1390
1586
|
display: flex;
|
|
1391
1587
|
align-items: center;
|
|
1392
|
-
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);
|
|
1393
1596
|
}
|
|
1394
1597
|
.step-section {
|
|
1395
1598
|
display: flex;
|
|
@@ -1411,7 +1614,8 @@
|
|
|
1411
1614
|
}
|
|
1412
1615
|
.step-slider {
|
|
1413
1616
|
width: 100%;
|
|
1414
|
-
|
|
1617
|
+
position: relative;
|
|
1618
|
+
z-index: 1; /* keep the slider knob above the step labels (which follow it in the DOM) */
|
|
1415
1619
|
}
|
|
1416
1620
|
.step-labels {
|
|
1417
1621
|
position: absolute;
|
|
@@ -1469,7 +1673,7 @@
|
|
|
1469
1673
|
position: relative;
|
|
1470
1674
|
}
|
|
1471
1675
|
.info-section :global(:is(.trajectory-info-toggle, .trajectory-export-toggle)) {
|
|
1472
|
-
font-size:
|
|
1676
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
1473
1677
|
}
|
|
1474
1678
|
.play-button {
|
|
1475
1679
|
min-width: clamp(32px, 4cqw, 36px);
|
|
@@ -1479,10 +1683,7 @@
|
|
|
1479
1683
|
&.playing {
|
|
1480
1684
|
background: var(--traj-pause-btn-bg, var(--btn-bg, rgba(0, 0, 0, 0.1)));
|
|
1481
1685
|
&:hover:not(:disabled) {
|
|
1482
|
-
background: var(
|
|
1483
|
-
--traj-pause-btn-bg-hover,
|
|
1484
|
-
var(--btn-bg-hover, rgba(0, 0, 0, 0.1))
|
|
1485
|
-
);
|
|
1686
|
+
background: var(--traj-pause-btn-bg-hover, var(--btn-bg-hover, rgba(0, 0, 0, 0.1)));
|
|
1486
1687
|
}
|
|
1487
1688
|
}
|
|
1488
1689
|
}
|
|
@@ -1530,14 +1731,20 @@
|
|
|
1530
1731
|
.view-mode-dropdown-wrapper {
|
|
1531
1732
|
display: flex;
|
|
1532
1733
|
position: relative;
|
|
1734
|
+
z-index: var(--trajectory-view-mode-z-index, 20);
|
|
1533
1735
|
}
|
|
1534
1736
|
.view-mode-dropdown {
|
|
1535
1737
|
position: absolute;
|
|
1536
1738
|
top: 115%;
|
|
1537
1739
|
right: 0;
|
|
1538
|
-
|
|
1740
|
+
z-index: 1;
|
|
1741
|
+
min-width: max-content;
|
|
1742
|
+
background: var(--trajectory-view-mode-bg, light-dark(#fff, #2f3137));
|
|
1539
1743
|
border-radius: 4px;
|
|
1540
|
-
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;
|
|
1541
1748
|
}
|
|
1542
1749
|
.view-mode-option {
|
|
1543
1750
|
display: flex;
|