matterviz 0.3.0 → 0.3.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/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
1
|
+
<script lang="ts">import { get_alphabetical_formula, get_electro_neg_formula, get_reduced_formula, } from '../composition';
|
|
2
|
+
import Icon from '../Icon.svelte';
|
|
2
3
|
import { format_num } from '../labels';
|
|
3
4
|
import Histogram from '../plot/Histogram.svelte';
|
|
4
|
-
import
|
|
5
|
-
|
|
5
|
+
import HeatmapTable from '../table/HeatmapTable.svelte';
|
|
6
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
7
|
+
import { get_arity, is_on_hull } from './types';
|
|
8
|
+
let { phase_stats, stable_entries, unstable_entries, layout = `toggle`, on_entry_click, highlighted_entry_id, min_n_elements = $bindable(1), entry_href, ...rest } = $props();
|
|
6
9
|
let copied_items = new SvelteSet();
|
|
10
|
+
let view_mode = $state(`stats`);
|
|
11
|
+
// Formula filter: when set, table shows only entries with this reduced formula
|
|
12
|
+
let formula_filter = $state(``);
|
|
13
|
+
let show_export_dropdown = $state(false);
|
|
7
14
|
async function copy_to_clipboard(label, value, key) {
|
|
8
15
|
try {
|
|
9
16
|
await navigator.clipboard.writeText(`${label}: ${value}`);
|
|
@@ -14,119 +21,365 @@ async function copy_to_clipboard(label, value, key) {
|
|
|
14
21
|
console.error(`Failed to copy to clipboard:`, error);
|
|
15
22
|
}
|
|
16
23
|
}
|
|
24
|
+
function handle_copy_keydown(event, label, value, key) {
|
|
25
|
+
if (event.key !== `Enter` && event.key !== ` `)
|
|
26
|
+
return;
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
copy_to_clipboard(label, value, key);
|
|
29
|
+
}
|
|
30
|
+
// Shared concatenation of stable + unstable for histograms
|
|
31
|
+
let all_entries = $derived([...stable_entries, ...unstable_entries]);
|
|
32
|
+
// Static arity labels for phase breakdown display
|
|
33
|
+
const arity_types = [
|
|
34
|
+
[`Unary`, `unary`, 1],
|
|
35
|
+
[`Binary`, `binary`, 2],
|
|
36
|
+
[`Ternary`, `ternary`, 3],
|
|
37
|
+
[`Quaternary`, `quaternary`, 4],
|
|
38
|
+
[`Quinary+`, `quinary_plus`, 5],
|
|
39
|
+
];
|
|
40
|
+
const histogram_props = {
|
|
41
|
+
bins: 50,
|
|
42
|
+
y_axis: { label: ``, ticks: 3 },
|
|
43
|
+
show_legend: false,
|
|
44
|
+
show_controls: false,
|
|
45
|
+
padding: { t: 5, b: 22, l: 35, r: 5 },
|
|
46
|
+
style: `height: 100px; --histogram-min-height: 100px`,
|
|
47
|
+
};
|
|
17
48
|
// Prepare histogram data for formation energies and hull distances
|
|
18
|
-
let e_form_data = $derived
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const distances = all_entries
|
|
33
|
-
.map((entry) => entry.e_above_hull)
|
|
34
|
-
.filter((val) => val !== undefined && isFinite(val));
|
|
35
|
-
return [{
|
|
36
|
-
x: [],
|
|
37
|
-
y: distances,
|
|
38
|
-
label: `E above hull`,
|
|
39
|
-
line_style: { stroke: `coral` },
|
|
40
|
-
}];
|
|
41
|
-
});
|
|
49
|
+
let e_form_data = $derived([{
|
|
50
|
+
x: [],
|
|
51
|
+
y: all_entries
|
|
52
|
+
.map((entry) => entry.e_form_per_atom ?? entry.energy_per_atom)
|
|
53
|
+
.filter((val) => val !== undefined && isFinite(val)),
|
|
54
|
+
label: `Formation Energy`,
|
|
55
|
+
}]);
|
|
56
|
+
let hull_distance_data = $derived([{
|
|
57
|
+
x: [],
|
|
58
|
+
y: all_entries
|
|
59
|
+
.map((entry) => entry.e_above_hull)
|
|
60
|
+
.filter((val) => val !== undefined && isFinite(val)),
|
|
61
|
+
label: `E above hull`,
|
|
62
|
+
}]);
|
|
42
63
|
let pane_data = $derived.by(() => {
|
|
43
64
|
if (!phase_stats)
|
|
44
65
|
return [];
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const num_elements = phase_stats.chemical_system.split(`-`).length;
|
|
48
|
-
const max_arity = Math.max(num_elements, phase_stats.quaternary > 0
|
|
49
|
-
? 4
|
|
50
|
-
: phase_stats.ternary > 0
|
|
51
|
-
? 3
|
|
52
|
-
: phase_stats.binary > 0
|
|
53
|
-
? 2
|
|
54
|
-
: 1);
|
|
55
|
-
const phase_items = [
|
|
66
|
+
const pct = (count) => phase_stats.total > 0 ? format_num(count / phase_stats.total, `.1~%`) : `0%`;
|
|
67
|
+
return [
|
|
56
68
|
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
title: ``,
|
|
70
|
+
items: [
|
|
71
|
+
{
|
|
72
|
+
label: `Total entries in ${phase_stats.chemical_system}`,
|
|
73
|
+
value: format_num(phase_stats.total),
|
|
74
|
+
key: `total-entries`,
|
|
75
|
+
},
|
|
76
|
+
// Only show phase types that exist or are within the max_arity
|
|
77
|
+
// used when computing stats (respects zeroed-out counts)
|
|
78
|
+
...arity_types
|
|
79
|
+
.filter(([, field, arity]) => phase_stats[field] > 0 || phase_stats.max_arity >= arity)
|
|
80
|
+
.map(([display, field]) => ({
|
|
81
|
+
label: `${display} phases`,
|
|
82
|
+
value: `${format_num(phase_stats[field])} (${pct(phase_stats[field])})`,
|
|
83
|
+
key: `${field}-phases`,
|
|
84
|
+
})),
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: `Stability`,
|
|
89
|
+
items: [
|
|
90
|
+
{
|
|
91
|
+
label: `Stable phases`,
|
|
92
|
+
value: `${format_num(phase_stats.stable)} (${pct(phase_stats.stable)})`,
|
|
93
|
+
key: `stable-phases`,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: `Unstable phases`,
|
|
97
|
+
value: `${format_num(phase_stats.unstable)} (${pct(phase_stats.unstable)})`,
|
|
98
|
+
key: `unstable-phases`,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
title: `E<sub>form</sub> distribution`,
|
|
104
|
+
items: [{
|
|
105
|
+
label: `Min / avg / max (eV/atom)`,
|
|
106
|
+
value: [
|
|
107
|
+
phase_stats.energy_range.min,
|
|
108
|
+
phase_stats.energy_range.avg,
|
|
109
|
+
phase_stats.energy_range.max,
|
|
110
|
+
]
|
|
111
|
+
.map((val) => format_num(val, `.3f`)).join(` / `),
|
|
112
|
+
key: `formation-energy`,
|
|
113
|
+
}],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
title: `E<sub>above hull</sub> distribution`,
|
|
117
|
+
items: [{
|
|
118
|
+
label: `Max / avg (eV/atom)`,
|
|
119
|
+
value: [phase_stats.hull_distance.max, phase_stats.hull_distance.avg]
|
|
120
|
+
.map((val) => format_num(val, `.3f`)).join(` / `),
|
|
121
|
+
key: `hull-distance`,
|
|
122
|
+
}],
|
|
60
123
|
},
|
|
61
124
|
];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
});
|
|
126
|
+
// Subsystem coverage: count entries per element pair for the stats pane
|
|
127
|
+
let subsystem_coverage = $derived.by(() => {
|
|
128
|
+
if (!phase_stats)
|
|
129
|
+
return null;
|
|
130
|
+
const elements = phase_stats.chemical_system.split(`-`);
|
|
131
|
+
if (elements.length < 3 || elements.length > 10)
|
|
132
|
+
return null;
|
|
133
|
+
// Count entries containing each pair
|
|
134
|
+
const pair_counts = new SvelteMap();
|
|
135
|
+
for (const entry of all_entries) {
|
|
136
|
+
const active = Object.keys(entry.composition)
|
|
137
|
+
.filter((el) => (entry.composition[el] ?? 0) > 0);
|
|
138
|
+
// Count all pairs present in this entry
|
|
139
|
+
for (let idx_a = 0; idx_a < active.length; idx_a++) {
|
|
140
|
+
for (let idx_b = idx_a + 1; idx_b < active.length; idx_b++) {
|
|
141
|
+
const key = [active[idx_a], active[idx_b]].sort().join(`-`);
|
|
142
|
+
pair_counts.set(key, (pair_counts.get(key) ?? 0) + 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Build pairs list sorted by element order in chemical_system
|
|
147
|
+
return elements.flatMap((el_a, idx_a) => elements.slice(idx_a + 1).map((el_b) => {
|
|
148
|
+
const key = [el_a, el_b].sort().join(`-`);
|
|
149
|
+
return { pair: key, count: pair_counts.get(key) ?? 0 };
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
152
|
+
let subsystem_coverage_summary = $derived(subsystem_coverage?.map(({ pair, count }) => `${pair}: ${count}`).join(` | `) ??
|
|
153
|
+
null);
|
|
154
|
+
// Table view: visible entries filtered by min element count and formula
|
|
155
|
+
let visible_entries = $derived(all_entries.filter((entry) => {
|
|
156
|
+
if (!entry.visible)
|
|
157
|
+
return false;
|
|
158
|
+
if (min_n_elements > 1 && get_arity(entry) < min_n_elements)
|
|
159
|
+
return false;
|
|
160
|
+
if (active_formula_filter &&
|
|
161
|
+
composition_key(entry.composition) !== active_formula_filter)
|
|
162
|
+
return false;
|
|
163
|
+
return true;
|
|
164
|
+
}));
|
|
165
|
+
let has_raw = $derived(visible_entries.some((entry) => entry.energy_per_atom !== undefined));
|
|
166
|
+
let has_ids = $derived(visible_entries.some((entry) => entry.entry_id));
|
|
167
|
+
let max_n_el = $derived(all_entries.reduce((max, entry) => Math.max(max, get_arity(entry)), 1));
|
|
168
|
+
// Sortable HTML cell with a hidden data-sort-value for HeatmapTable sorting
|
|
169
|
+
const sort_span = (sort_val, display, attrs = ``) => `<span data-sort-value="${sort_val}"${attrs ? ` ${attrs}` : ``}>${display}</span>`;
|
|
170
|
+
// Escape HTML special chars to prevent XSS when rendering user-supplied strings via {@html}
|
|
171
|
+
const escape_html = (str) => str
|
|
172
|
+
.replace(/&/g, `&`)
|
|
173
|
+
.replace(/</g, `<`)
|
|
174
|
+
.replace(/>/g, `>`)
|
|
175
|
+
.replace(/"/g, `"`)
|
|
176
|
+
.replace(/'/g, `'`);
|
|
177
|
+
const unescape_html = (str, max_rounds = 5) => {
|
|
178
|
+
let decoded = str;
|
|
179
|
+
for (let round_idx = 0; round_idx < max_rounds; round_idx++) {
|
|
180
|
+
const next_decoded = decoded
|
|
181
|
+
.replace(/&/g, `&`)
|
|
182
|
+
.replace(/</g, `<`)
|
|
183
|
+
.replace(/>/g, `>`)
|
|
184
|
+
.replace(/"/g, `"`)
|
|
185
|
+
.replace(/'/g, `'`);
|
|
186
|
+
if (next_decoded === decoded)
|
|
187
|
+
break;
|
|
188
|
+
decoded = next_decoded;
|
|
189
|
+
}
|
|
190
|
+
return decoded;
|
|
191
|
+
};
|
|
192
|
+
// Convert legacy/html formula strings like Fe<sub>2</sub>O<sub>3</sub> back to plain
|
|
193
|
+
// stoichiometric input before parsing/reordering.
|
|
194
|
+
const normalize_formula_markup = (formula) => unescape_html(formula)
|
|
195
|
+
.replaceAll(/<sub>\s*([^<]+?)\s*<\/sub>/gi, `$1`)
|
|
196
|
+
.replaceAll(/<[^>]+>/g, ``)
|
|
197
|
+
.replaceAll(/\s+/g, ``);
|
|
198
|
+
const sanitize_href = (href) => {
|
|
199
|
+
const trimmed_href = href?.trim();
|
|
200
|
+
if (!trimmed_href)
|
|
201
|
+
return null;
|
|
202
|
+
const lower_href = trimmed_href.toLowerCase();
|
|
203
|
+
const blocked_schemes = [`javascript:`, `data:`, `vbscript:`];
|
|
204
|
+
if (blocked_schemes.some((scheme) => lower_href.startsWith(scheme)))
|
|
205
|
+
return null;
|
|
206
|
+
return trimmed_href;
|
|
207
|
+
};
|
|
208
|
+
// Serialize reduced composition to a stable string key for polymorph counting
|
|
209
|
+
const composition_key = (comp) => get_alphabetical_formula(get_reduced_formula(comp), true, ``);
|
|
210
|
+
// Count polymorphs per reduced formula across all entries
|
|
211
|
+
let polymorph_counts = $derived.by(() => {
|
|
212
|
+
const counts = new SvelteMap();
|
|
213
|
+
for (const entry of all_entries) {
|
|
214
|
+
const key = composition_key(entry.composition);
|
|
215
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
216
|
+
}
|
|
217
|
+
return counts;
|
|
218
|
+
});
|
|
219
|
+
let poly_formulas = $derived([...polymorph_counts.entries()]
|
|
220
|
+
.filter(([, count]) => count > 1)
|
|
221
|
+
.sort(([, count_a], [, count_b]) => count_b - count_a));
|
|
222
|
+
let has_polymorphs = $derived(poly_formulas.length > 0);
|
|
223
|
+
let active_formula_filter = $derived.by(() => {
|
|
224
|
+
if (!formula_filter || !has_polymorphs)
|
|
225
|
+
return ``;
|
|
226
|
+
return poly_formulas.some(([formula]) => formula === formula_filter)
|
|
227
|
+
? formula_filter
|
|
228
|
+
: ``;
|
|
229
|
+
});
|
|
230
|
+
$effect(() => {
|
|
231
|
+
if (formula_filter && formula_filter !== active_formula_filter) {
|
|
232
|
+
formula_filter = ``;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// Build table rows and a WeakMap from row→entry for the click handler
|
|
236
|
+
let { table_data, entry_by_row } = $derived.by(() => {
|
|
237
|
+
const map = new WeakMap();
|
|
238
|
+
const rows = visible_entries.map((entry, idx) => {
|
|
239
|
+
const n_atoms = Object.values(entry.composition).reduce((sum, count) => sum + count, 0);
|
|
240
|
+
const on_hull = is_on_hull(entry);
|
|
241
|
+
const formula_source = entry.reduced_formula ?? entry.name ??
|
|
242
|
+
get_alphabetical_formula(entry.composition, true, ``);
|
|
243
|
+
const normalized_formula = normalize_formula_markup(formula_source);
|
|
244
|
+
const formatted_formula = get_electro_neg_formula(normalized_formula);
|
|
245
|
+
const formula_html = formatted_formula || escape_html(normalized_formula);
|
|
246
|
+
// Match by entry_id or common data fields (mat_id, structure_id)
|
|
247
|
+
// since entry_id may be wrapped in HTML (e.g. <a> tags)
|
|
248
|
+
const entry_data = entry.data;
|
|
249
|
+
const is_highlighted = !!(highlighted_entry_id && (entry.entry_id === highlighted_entry_id ||
|
|
250
|
+
entry_data?.mat_id === highlighted_entry_id ||
|
|
251
|
+
entry_data?.structure_id === highlighted_entry_id));
|
|
252
|
+
const row = {
|
|
253
|
+
'#': sort_span(idx + 1, `${idx + 1}`),
|
|
254
|
+
Formula: on_hull ? `<strong>${formula_html}</strong>` : formula_html,
|
|
255
|
+
'E<sub>hull</sub>': entry.e_above_hull ?? null,
|
|
256
|
+
'E<sub>form</sub>': entry.e_form_per_atom ?? entry.energy_per_atom ?? null,
|
|
257
|
+
};
|
|
258
|
+
if (has_raw)
|
|
259
|
+
row[`E<sub>raw</sub>`] = entry.energy_per_atom;
|
|
260
|
+
if (has_ids) {
|
|
261
|
+
const safe_href = sanitize_href(entry_href?.(entry));
|
|
262
|
+
const safe_id = entry.entry_id ? escape_html(entry.entry_id) : undefined;
|
|
263
|
+
row.ID = safe_href && safe_id
|
|
264
|
+
? `<a href="${escape_html(safe_href)}" target="_blank" rel="noopener">${safe_id}</a>`
|
|
265
|
+
: safe_id;
|
|
266
|
+
}
|
|
267
|
+
if (has_polymorphs) {
|
|
268
|
+
const comp_key = composition_key(entry.composition);
|
|
269
|
+
const poly_count = polymorph_counts.get(comp_key) ?? 1;
|
|
270
|
+
row.Poly = poly_count;
|
|
271
|
+
}
|
|
272
|
+
row[`N<sub>el</sub>`] = get_arity(entry);
|
|
273
|
+
row[`N<sub>at</sub>`] = n_atoms;
|
|
274
|
+
// Highlight row for current material
|
|
275
|
+
if (is_highlighted) {
|
|
276
|
+
row.style =
|
|
277
|
+
`background: color-mix(in srgb, var(--hull-stable-color, #22c55e) 15%, transparent)`;
|
|
278
|
+
}
|
|
279
|
+
map.set(row, entry);
|
|
280
|
+
return row;
|
|
123
281
|
});
|
|
124
|
-
return
|
|
282
|
+
return { table_data: rows, entry_by_row: map };
|
|
125
283
|
});
|
|
284
|
+
function handle_row_click(_event, row) {
|
|
285
|
+
const entry = entry_by_row.get(row);
|
|
286
|
+
if (entry)
|
|
287
|
+
on_entry_click?.(entry);
|
|
288
|
+
}
|
|
289
|
+
let table_columns = $derived([
|
|
290
|
+
{ label: `#`, color_scale: null, description: `Row number` },
|
|
291
|
+
{ label: `Formula`, color_scale: null },
|
|
292
|
+
{
|
|
293
|
+
label: `E<sub>hull</sub>`,
|
|
294
|
+
better: `lower`,
|
|
295
|
+
color_scale: `interpolateRdYlGn`,
|
|
296
|
+
format: `.4f`,
|
|
297
|
+
description: `Energy above convex hull (eV/atom)`,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
label: `E<sub>form</sub>`,
|
|
301
|
+
better: `lower`,
|
|
302
|
+
color_scale: `interpolateBlues`,
|
|
303
|
+
format: `.4f`,
|
|
304
|
+
description: `Formation energy (eV/atom)`,
|
|
305
|
+
},
|
|
306
|
+
...(has_raw
|
|
307
|
+
? [{
|
|
308
|
+
label: `E<sub>raw</sub>`,
|
|
309
|
+
color_scale: `interpolateCool`,
|
|
310
|
+
format: `.4f`,
|
|
311
|
+
description: `Raw energy per atom (eV/atom)`,
|
|
312
|
+
}]
|
|
313
|
+
: []),
|
|
314
|
+
...(has_ids
|
|
315
|
+
? [{ label: `ID`, color_scale: null, description: `Entry identifier` }]
|
|
316
|
+
: []),
|
|
317
|
+
...(has_polymorphs
|
|
318
|
+
? [{
|
|
319
|
+
label: `Poly`,
|
|
320
|
+
color_scale: null,
|
|
321
|
+
description: `Number of polymorphs (same reduced formula)`,
|
|
322
|
+
}]
|
|
323
|
+
: []),
|
|
324
|
+
{
|
|
325
|
+
label: `N<sub>el</sub>`,
|
|
326
|
+
color_scale: null,
|
|
327
|
+
description: `Number of elements`,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
label: `N<sub>at</sub>`,
|
|
331
|
+
color_scale: null,
|
|
332
|
+
format: `d`,
|
|
333
|
+
description: `Number of atoms in unit cell`,
|
|
334
|
+
},
|
|
335
|
+
]);
|
|
336
|
+
const html_to_text = (val) => {
|
|
337
|
+
if (val == null)
|
|
338
|
+
return ``;
|
|
339
|
+
if (typeof val !== `string`)
|
|
340
|
+
return String(val);
|
|
341
|
+
const temp_el = document.createElement(`div`);
|
|
342
|
+
temp_el.innerHTML = val;
|
|
343
|
+
return temp_el.textContent?.trim() ?? ``;
|
|
344
|
+
};
|
|
345
|
+
const csv_escape = (val) => /[",\n]/.test(val) ? `"${val.replaceAll(`"`, `""`)}"` : val;
|
|
346
|
+
const get_export_filename = (format) => {
|
|
347
|
+
const system = (phase_stats?.chemical_system ?? `convex-hull-stats`)
|
|
348
|
+
.toLowerCase()
|
|
349
|
+
.replaceAll(/\s+/g, `-`);
|
|
350
|
+
return `${system}.${format}`;
|
|
351
|
+
};
|
|
352
|
+
const build_export_rows = () => {
|
|
353
|
+
const column_labels = table_columns.map((col) => col.label);
|
|
354
|
+
return table_data.map((row) => Object.fromEntries(column_labels.map((label) => [html_to_text(label), html_to_text(row[label])])));
|
|
355
|
+
};
|
|
356
|
+
const download_file = (content, filename, mime_type) => {
|
|
357
|
+
const blob = new Blob([content], { type: mime_type });
|
|
358
|
+
const object_url = URL.createObjectURL(blob);
|
|
359
|
+
const link_el = document.createElement(`a`);
|
|
360
|
+
link_el.href = object_url;
|
|
361
|
+
link_el.download = filename;
|
|
362
|
+
document.body.append(link_el);
|
|
363
|
+
link_el.click();
|
|
364
|
+
link_el.remove();
|
|
365
|
+
URL.revokeObjectURL(object_url);
|
|
366
|
+
};
|
|
367
|
+
function export_table(format) {
|
|
368
|
+
const rows = build_export_rows();
|
|
369
|
+
if (format === `json`) {
|
|
370
|
+
download_file(JSON.stringify(rows, null, 2), get_export_filename(`json`), `application/json;charset=utf-8`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const headers = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
374
|
+
const csv_lines = [
|
|
375
|
+
headers.map(csv_escape).join(`,`),
|
|
376
|
+
...rows.map((row) => headers.map((header) => csv_escape(row[header] ?? ``)).join(`,`)),
|
|
377
|
+
];
|
|
378
|
+
download_file(csv_lines.join(`\n`), get_export_filename(`csv`), `text/csv;charset=utf-8`);
|
|
379
|
+
}
|
|
126
380
|
</script>
|
|
127
381
|
|
|
128
|
-
|
|
129
|
-
<h4 id="convex-hull-stats" style="margin-top: 0">Convex Hull Stats</h4>
|
|
382
|
+
{#snippet stats_panel()}
|
|
130
383
|
{#each pane_data as section, sec_idx (sec_idx)}
|
|
131
384
|
{#if sec_idx > 0}<hr />{/if}
|
|
132
385
|
<section>
|
|
@@ -142,12 +395,13 @@ let pane_data = $derived.by(() => {
|
|
|
142
395
|
onclick={() => copy_to_clipboard(item.label, String(item.value), key ?? item.label)}
|
|
143
396
|
role="button"
|
|
144
397
|
tabindex="0"
|
|
145
|
-
onkeydown={(event) =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
398
|
+
onkeydown={(event) =>
|
|
399
|
+
handle_copy_keydown(
|
|
400
|
+
event,
|
|
401
|
+
item.label,
|
|
402
|
+
String(item.value),
|
|
403
|
+
key ?? item.label,
|
|
404
|
+
)}
|
|
151
405
|
>
|
|
152
406
|
<span>{@html label}:</span>
|
|
153
407
|
<span>{@html value}</span>
|
|
@@ -161,44 +415,201 @@ let pane_data = $derived.by(() => {
|
|
|
161
415
|
</div>
|
|
162
416
|
{/each}
|
|
163
417
|
|
|
418
|
+
{#if sec_idx === 0 && subsystem_coverage}
|
|
419
|
+
<div
|
|
420
|
+
class="clickable stat-item subsystem-coverage-row"
|
|
421
|
+
data-testid="pd-binary-subsystem-coverage"
|
|
422
|
+
title="Click to copy: Binary subsystem coverage: {subsystem_coverage_summary ?? ``}"
|
|
423
|
+
onclick={() =>
|
|
424
|
+
copy_to_clipboard(
|
|
425
|
+
`Binary subsystem coverage`,
|
|
426
|
+
subsystem_coverage_summary ?? ``,
|
|
427
|
+
`binary-subsystem-coverage`,
|
|
428
|
+
)}
|
|
429
|
+
role="button"
|
|
430
|
+
tabindex="0"
|
|
431
|
+
onkeydown={(event) =>
|
|
432
|
+
handle_copy_keydown(
|
|
433
|
+
event,
|
|
434
|
+
`Binary subsystem coverage`,
|
|
435
|
+
subsystem_coverage_summary ?? ``,
|
|
436
|
+
`binary-subsystem-coverage`,
|
|
437
|
+
)}
|
|
438
|
+
>
|
|
439
|
+
<span class="subsystem-label"
|
|
440
|
+
>Binary subsystem coverage ({subsystem_coverage.length} pairs)</span>
|
|
441
|
+
<span class="subsystem-chips">
|
|
442
|
+
{#each subsystem_coverage as { pair, count } (pair)}
|
|
443
|
+
<span class="subsystem-chip" class:has-entries={count > 0}>
|
|
444
|
+
<span class="pair">{pair}</span>
|
|
445
|
+
<span class="count">{count}</span>
|
|
446
|
+
</span>
|
|
447
|
+
{/each}
|
|
448
|
+
</span>
|
|
449
|
+
{#if copied_items.has(`binary-subsystem-coverage`)}
|
|
450
|
+
<Icon
|
|
451
|
+
icon="Check"
|
|
452
|
+
style="color: var(--success-color, #10b981); width: 12px; height: 12px"
|
|
453
|
+
class="copy-checkmark"
|
|
454
|
+
/>
|
|
455
|
+
{/if}
|
|
456
|
+
</div>
|
|
457
|
+
{/if}
|
|
458
|
+
|
|
164
459
|
{#if section.title === `E<sub>form</sub> distribution` &&
|
|
165
|
-
|
|
460
|
+
e_form_data[0].y.length > 0}
|
|
166
461
|
<Histogram
|
|
462
|
+
{...histogram_props}
|
|
167
463
|
series={e_form_data}
|
|
168
|
-
bins={50}
|
|
169
464
|
x_axis={{ label: ``, format: `.2f` }}
|
|
170
|
-
y_axis={{ label: ``, ticks: 3 }}
|
|
171
|
-
show_legend={false}
|
|
172
|
-
show_controls={false}
|
|
173
|
-
padding={{ t: 5, b: 25, l: 35, r: 5 }}
|
|
174
|
-
style="height: 100px; --histogram-min-height: 100px"
|
|
175
465
|
bar={{ color: `steelblue`, opacity: 0.7 }}
|
|
176
466
|
/>
|
|
177
467
|
{/if}
|
|
178
468
|
|
|
179
469
|
{#if section.title === `E<sub>above hull</sub> distribution` &&
|
|
180
|
-
|
|
470
|
+
hull_distance_data[0].y.length > 0}
|
|
181
471
|
<Histogram
|
|
472
|
+
{...histogram_props}
|
|
182
473
|
series={hull_distance_data}
|
|
183
|
-
bins={50}
|
|
184
474
|
x_axis={{ label: ``, format: `.2f`, range: [0, null] }}
|
|
185
|
-
y_axis={{ label: ``, ticks: 3 }}
|
|
186
|
-
show_legend={false}
|
|
187
|
-
show_controls={false}
|
|
188
|
-
padding={{ t: 5, b: 25, l: 35, r: 5 }}
|
|
189
|
-
style="height: 100px; --histogram-min-height: 100px"
|
|
190
475
|
bar={{ color: `coral`, opacity: 0.7 }}
|
|
191
476
|
/>
|
|
192
477
|
{/if}
|
|
193
478
|
</section>
|
|
194
479
|
{/each}
|
|
195
|
-
|
|
480
|
+
{/snippet}
|
|
481
|
+
|
|
482
|
+
{#snippet table_panel()}
|
|
483
|
+
<div class="table-filters">
|
|
484
|
+
{#if max_n_el > 2}
|
|
485
|
+
<label>
|
|
486
|
+
Min N<sub>el</sub>:
|
|
487
|
+
<select bind:value={min_n_elements}>
|
|
488
|
+
{#each Array.from({ length: max_n_el }, (_, idx) => idx + 1) as nel (nel)}
|
|
489
|
+
<option value={nel}>{nel}{nel === 1 ? ` (all)` : ``}</option>
|
|
490
|
+
{/each}
|
|
491
|
+
</select>
|
|
492
|
+
</label>
|
|
493
|
+
{/if}
|
|
494
|
+
{#if has_polymorphs}
|
|
495
|
+
<label>
|
|
496
|
+
Polymorphs:
|
|
497
|
+
<select bind:value={formula_filter}>
|
|
498
|
+
<option value="">all</option>
|
|
499
|
+
{#each poly_formulas as [formula, count] (formula)}
|
|
500
|
+
<option value={formula}>{formula} ({count})</option>
|
|
501
|
+
{/each}
|
|
502
|
+
</select>
|
|
503
|
+
</label>
|
|
504
|
+
{/if}
|
|
505
|
+
<span class="filter-count">{visible_entries.length} entries</span>
|
|
506
|
+
<span class="filter-spacer"></span>
|
|
507
|
+
<div class="export-actions">
|
|
508
|
+
<button
|
|
509
|
+
class="icon-btn"
|
|
510
|
+
class:active={show_export_dropdown}
|
|
511
|
+
title="Export"
|
|
512
|
+
onclick={() => show_export_dropdown = !show_export_dropdown}
|
|
513
|
+
>
|
|
514
|
+
<Icon icon="Export" style="width: 14px" />
|
|
515
|
+
</button>
|
|
516
|
+
{#if show_export_dropdown}
|
|
517
|
+
<div class="export-dropdown">
|
|
518
|
+
<button
|
|
519
|
+
class="dropdown-option"
|
|
520
|
+
onclick={() => {
|
|
521
|
+
export_table(`csv`)
|
|
522
|
+
show_export_dropdown = false
|
|
523
|
+
}}
|
|
524
|
+
>
|
|
525
|
+
<Icon icon="Download" style="width: 12px" /> CSV
|
|
526
|
+
</button>
|
|
527
|
+
<button
|
|
528
|
+
class="dropdown-option"
|
|
529
|
+
onclick={() => {
|
|
530
|
+
export_table(`json`)
|
|
531
|
+
show_export_dropdown = false
|
|
532
|
+
}}
|
|
533
|
+
>
|
|
534
|
+
<Icon icon="Download" style="width: 12px" /> JSON
|
|
535
|
+
</button>
|
|
536
|
+
</div>
|
|
537
|
+
{/if}
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
<HeatmapTable
|
|
541
|
+
data={table_data}
|
|
542
|
+
columns={table_columns}
|
|
543
|
+
initial_sort={{ column: `E<sub>hull</sub>`, direction: `asc` }}
|
|
544
|
+
scroll_style={layout === `side-by-side`
|
|
545
|
+
? `flex: 1 1 0; max-width: 100%; overflow: auto`
|
|
546
|
+
: `max-height: var(--hull-stats-max-height, 500px)`}
|
|
547
|
+
style="width: 100%"
|
|
548
|
+
root_style={layout === `side-by-side`
|
|
549
|
+
? `flex: 1 1 0; min-height: 0; margin-inline: 0`
|
|
550
|
+
: undefined}
|
|
551
|
+
onrowclick={on_entry_click ? handle_row_click : undefined}
|
|
552
|
+
export_data={false}
|
|
553
|
+
/>
|
|
554
|
+
{/snippet}
|
|
555
|
+
|
|
556
|
+
{#if layout === `side-by-side`}
|
|
557
|
+
<div {...rest} class="convex-hull-stats side-by-side {rest.class ?? ``}">
|
|
558
|
+
<div class="stats-pane">
|
|
559
|
+
{@render stats_panel()}
|
|
560
|
+
</div>
|
|
561
|
+
<div class="table-pane">
|
|
562
|
+
{@render table_panel()}
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
{:else}
|
|
566
|
+
<div {...rest} class="convex-hull-stats {rest.class ?? ``}">
|
|
567
|
+
<div class="view-toggle">
|
|
568
|
+
<button class:active={view_mode === `stats`} onclick={() => view_mode = `stats`}>
|
|
569
|
+
Stats
|
|
570
|
+
</button>
|
|
571
|
+
<button class:active={view_mode === `table`} onclick={() => view_mode = `table`}>
|
|
572
|
+
Table
|
|
573
|
+
</button>
|
|
574
|
+
</div>
|
|
575
|
+
{#if view_mode === `stats`}
|
|
576
|
+
{@render stats_panel()}
|
|
577
|
+
{:else}
|
|
578
|
+
{@render table_panel()}
|
|
579
|
+
{/if}
|
|
580
|
+
</div>
|
|
581
|
+
{/if}
|
|
196
582
|
|
|
197
583
|
<style>
|
|
198
584
|
.convex-hull-stats {
|
|
199
585
|
background: var(--hull-stats-bg, var(--hull-bg));
|
|
200
586
|
border-radius: var(--hull-border-radius, var(--border-radius, 3pt));
|
|
201
|
-
padding:
|
|
587
|
+
padding: var(--hull-stats-padding, 1em);
|
|
588
|
+
}
|
|
589
|
+
.convex-hull-stats.side-by-side {
|
|
590
|
+
display: flex;
|
|
591
|
+
gap: var(--hull-stats-gap, 1.5em);
|
|
592
|
+
align-items: stretch;
|
|
593
|
+
width: fit-content;
|
|
594
|
+
max-width: 100%;
|
|
595
|
+
margin-inline: auto;
|
|
596
|
+
}
|
|
597
|
+
.stats-pane {
|
|
598
|
+
flex: 0 0 auto;
|
|
599
|
+
width: fit-content;
|
|
600
|
+
min-width: var(--hull-stats-pane-min-width, 200px);
|
|
601
|
+
max-width: var(--hull-stats-pane-max-width, 320px);
|
|
602
|
+
}
|
|
603
|
+
.table-pane {
|
|
604
|
+
flex: 1 1 0;
|
|
605
|
+
max-width: 100%;
|
|
606
|
+
min-width: 0;
|
|
607
|
+
overflow: auto;
|
|
608
|
+
display: flex;
|
|
609
|
+
flex-direction: column;
|
|
610
|
+
}
|
|
611
|
+
.convex-hull-stats :global(tbody tr[onclick]) {
|
|
612
|
+
cursor: pointer;
|
|
202
613
|
}
|
|
203
614
|
section div {
|
|
204
615
|
display: flex;
|
|
@@ -231,9 +642,167 @@ let pane_data = $derived.by(() => {
|
|
|
231
642
|
}
|
|
232
643
|
}
|
|
233
644
|
.stat-item span:first-child {
|
|
234
|
-
color: var(--text-color-muted, #666);
|
|
645
|
+
color: var(--text-color-muted, light-dark(#666, #bbb));
|
|
235
646
|
}
|
|
236
647
|
section h5 {
|
|
237
648
|
margin: 0 0 6px 0;
|
|
238
649
|
}
|
|
650
|
+
.view-toggle {
|
|
651
|
+
display: flex;
|
|
652
|
+
margin-bottom: 8pt;
|
|
653
|
+
}
|
|
654
|
+
.view-toggle button {
|
|
655
|
+
flex: 1;
|
|
656
|
+
padding: 2pt 8pt;
|
|
657
|
+
border: 1px solid
|
|
658
|
+
var(--hull-stats-border-color, color-mix(in srgb, currentColor 20%, transparent));
|
|
659
|
+
background: transparent;
|
|
660
|
+
color: inherit;
|
|
661
|
+
cursor: pointer;
|
|
662
|
+
font-size: 0.85em;
|
|
663
|
+
}
|
|
664
|
+
.view-toggle button:first-child {
|
|
665
|
+
border-radius: 4pt 0 0 4pt;
|
|
666
|
+
}
|
|
667
|
+
.view-toggle button:last-child {
|
|
668
|
+
border-radius: 0 4pt 4pt 0;
|
|
669
|
+
border-left: none;
|
|
670
|
+
}
|
|
671
|
+
.view-toggle button.active {
|
|
672
|
+
background: var(
|
|
673
|
+
--hull-stats-toggle-active-bg,
|
|
674
|
+
light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.15))
|
|
675
|
+
);
|
|
676
|
+
font-weight: 500;
|
|
677
|
+
}
|
|
678
|
+
.table-filters {
|
|
679
|
+
display: flex;
|
|
680
|
+
align-items: center;
|
|
681
|
+
flex-wrap: wrap;
|
|
682
|
+
gap: 0.75em;
|
|
683
|
+
margin-bottom: 6pt;
|
|
684
|
+
font-size: 0.85em;
|
|
685
|
+
label {
|
|
686
|
+
display: flex;
|
|
687
|
+
align-items: center;
|
|
688
|
+
gap: 0.4em;
|
|
689
|
+
sub {
|
|
690
|
+
margin-left: -0.2em;
|
|
691
|
+
font-size: 0.72em;
|
|
692
|
+
line-height: 0;
|
|
693
|
+
vertical-align: baseline;
|
|
694
|
+
position: relative;
|
|
695
|
+
top: 0.33em;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
select {
|
|
699
|
+
padding: 2pt 4pt;
|
|
700
|
+
border: 1px solid
|
|
701
|
+
var(--hull-stats-border-color, color-mix(in srgb, currentColor 20%, transparent));
|
|
702
|
+
border-radius: 3pt;
|
|
703
|
+
background: transparent;
|
|
704
|
+
color: inherit;
|
|
705
|
+
font-size: inherit;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
.filter-spacer {
|
|
709
|
+
flex: 1 1 auto;
|
|
710
|
+
}
|
|
711
|
+
.export-actions {
|
|
712
|
+
position: relative;
|
|
713
|
+
.icon-btn {
|
|
714
|
+
padding: 2pt 6pt;
|
|
715
|
+
border: 1px solid
|
|
716
|
+
var(--hull-stats-border-color, color-mix(in srgb, currentColor 20%, transparent));
|
|
717
|
+
border-radius: 3pt;
|
|
718
|
+
background: transparent;
|
|
719
|
+
color: inherit;
|
|
720
|
+
cursor: pointer;
|
|
721
|
+
display: inline-flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
justify-content: center;
|
|
724
|
+
}
|
|
725
|
+
.icon-btn:hover {
|
|
726
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
727
|
+
}
|
|
728
|
+
.icon-btn.active {
|
|
729
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
.export-dropdown {
|
|
733
|
+
position: absolute;
|
|
734
|
+
right: 0;
|
|
735
|
+
top: calc(100% + 4px);
|
|
736
|
+
display: flex;
|
|
737
|
+
flex-direction: column;
|
|
738
|
+
min-width: 88px;
|
|
739
|
+
padding: 3pt;
|
|
740
|
+
border: 1px solid
|
|
741
|
+
var(--hull-stats-border-color, color-mix(in srgb, currentColor 20%, transparent));
|
|
742
|
+
border-radius: 4pt;
|
|
743
|
+
background: var(--page-bg, Canvas);
|
|
744
|
+
z-index: 4;
|
|
745
|
+
box-shadow: 0 2px 8px color-mix(in srgb, black 20%, transparent);
|
|
746
|
+
.dropdown-option {
|
|
747
|
+
display: inline-flex;
|
|
748
|
+
align-items: center;
|
|
749
|
+
gap: 5px;
|
|
750
|
+
border: none;
|
|
751
|
+
border-radius: 3pt;
|
|
752
|
+
background: transparent;
|
|
753
|
+
color: inherit;
|
|
754
|
+
cursor: pointer;
|
|
755
|
+
text-align: left;
|
|
756
|
+
padding: 3pt 6pt;
|
|
757
|
+
}
|
|
758
|
+
.dropdown-option:hover {
|
|
759
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
.table-pane :global(.control-buttons) {
|
|
763
|
+
display: none;
|
|
764
|
+
margin: 0;
|
|
765
|
+
}
|
|
766
|
+
.filter-count {
|
|
767
|
+
color: var(--text-color-muted, light-dark(#666, #bbb));
|
|
768
|
+
font-size: 0.9em;
|
|
769
|
+
}
|
|
770
|
+
.subsystem-coverage-row {
|
|
771
|
+
flex-wrap: wrap;
|
|
772
|
+
gap: 4pt 1em;
|
|
773
|
+
justify-content: flex-start;
|
|
774
|
+
.subsystem-label {
|
|
775
|
+
color: var(--text-color-muted, light-dark(#666, #bbb));
|
|
776
|
+
font-size: 0.9em;
|
|
777
|
+
}
|
|
778
|
+
.subsystem-chips {
|
|
779
|
+
display: flex;
|
|
780
|
+
flex-wrap: wrap;
|
|
781
|
+
gap: 4pt;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
.subsystem-chip {
|
|
785
|
+
display: inline-flex;
|
|
786
|
+
align-items: center;
|
|
787
|
+
gap: 0;
|
|
788
|
+
padding: 1pt 5pt;
|
|
789
|
+
border-radius: 3pt;
|
|
790
|
+
font-size: 0.78em;
|
|
791
|
+
line-height: 1.2;
|
|
792
|
+
background: color-mix(in srgb, currentColor 5%, transparent);
|
|
793
|
+
color: var(--text-color-muted, light-dark(#666, #bbb));
|
|
794
|
+
.pair {
|
|
795
|
+
font-weight: 500;
|
|
796
|
+
}
|
|
797
|
+
.count {
|
|
798
|
+
margin-left: 3pt;
|
|
799
|
+
font-size: 0.9em;
|
|
800
|
+
font-weight: 600;
|
|
801
|
+
color: color-mix(in srgb, currentColor 70%, transparent);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
.subsystem-chip.has-entries {
|
|
805
|
+
background: color-mix(in srgb, var(--hull-stable-color, #22c55e) 15%, transparent);
|
|
806
|
+
color: inherit;
|
|
807
|
+
}
|
|
239
808
|
</style>
|