diff --git a/bands-visualiser-0.0.1.tgz b/bands-visualiser-0.0.1.tgz new file mode 100644 index 0000000..eec318b Binary files /dev/null and b/bands-visualiser-0.0.1.tgz differ diff --git a/package-lock.json b/package-lock.json index 8e01ca5..f1aa661 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "name": "discover-mc2d-react", "version": "0.0.0", "dependencies": { + "bands-visualiser": "file:bands-visualiser-0.0.1.tgz", "better-react-mathjax": "^2.0.3", "bootstrap": "^5.3.2", - "chart.js": "^4.4.1", "chartjs-plugin-annotation": "^3.0.1", "chartjs-plugin-zoom": "^2.0.1", "mathjs": "^13.2.0", @@ -3932,7 +3932,8 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", @@ -7322,6 +7323,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bands-visualiser": { + "version": "0.0.1", + "resolved": "file:bands-visualiser-0.0.1.tgz", + "integrity": "sha512-JcY8aPw8mgFxqPShqeowITfVqap6ReMp3TyXwnlImb09N0c1WkS2DQVGBTcWyyxJnRlDPkBlxNeLjaD1BC3duA==", + "dependencies": { + "deepmerge": "^4.3.1", + "plotly.js-basic-dist": "^3.0.1" + } + }, "node_modules/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -7770,10 +7780,11 @@ } }, "node_modules/chart.js": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", - "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -17521,6 +17532,12 @@ "world-calendars": "^1.0.3" } }, + "node_modules/plotly.js-basic-dist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/plotly.js-basic-dist/-/plotly.js-basic-dist-3.0.1.tgz", + "integrity": "sha512-TFhJjbCdOSp8IhxThlcaE9doaiBQ8gdFkBoyJ1UrsnLjJzsSGkNfVd2jl51WtOMiAxMSW0F1dtELEju4/cicnQ==", + "license": "MIT" + }, "node_modules/point-in-polygon": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", diff --git a/package.json b/package.json index 24b1f62..5d68917 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "preview": "vite preview" }, "dependencies": { + "bands-visualiser": "file:bands-visualiser-0.0.1.tgz", "better-react-mathjax": "^2.0.3", "bootstrap": "^5.3.2", - "chart.js": "^4.4.1", "chartjs-plugin-annotation": "^3.0.1", "chartjs-plugin-zoom": "^2.0.1", "mathjs": "^13.2.0", diff --git a/src/App.jsx b/src/App.jsx index 1e0e4fd..4b14e48 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,13 +5,6 @@ import { Routes, Route, HashRouter } from "react-router-dom"; import MainPage from "./MainPage"; import DetailPage from "./DetailPage"; -// Chart.js plugins need to be registered outside the library -import Chart from "chart.js/auto"; -import zoomPlugin from "chartjs-plugin-zoom"; -import annotationPlugin from "chartjs-plugin-annotation"; -Chart.register(zoomPlugin); -Chart.register(annotationPlugin); - function App() { return ( diff --git a/src/DetailPage/ElectronicSection/index.jsx b/src/DetailPage/ElectronicSection/index.jsx index 19cafed..b61ecc3 100644 --- a/src/DetailPage/ElectronicSection/index.jsx +++ b/src/DetailPage/ElectronicSection/index.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { McloudSpinner } from "mc-react-library"; @@ -6,7 +6,7 @@ import { MCInfoBox } from "../components/MCInfoBox"; import { Container, Row, Col } from "react-bootstrap"; -import BandsVisualizer from "mc-react-bands"; +//import BandsVisualizer from "mc-react-bands"; import { ExploreButton } from "mc-react-library"; @@ -14,6 +14,7 @@ import { loadAiidaBands } from "../../common/restApiUtils"; import { AIIDA_REST_API_URL, EXPLORE_URL } from "../../common/restApiUtils"; +import { getXYData } from "bands-visualiser"; import * as math from "mathjs"; import { formatAiidaProp } from "../utils"; @@ -28,18 +29,32 @@ function shiftBands(bandsData, shift) { }); } -function ElectronicInfoBox({ electronicData, metadata }) { +function ElectronicInfoBox({ electronicData, metadata, cbm = 0, vbm = 0 }) { let magStateStr = electronicData.magnetic_state; if (magStateStr == null) { magStateStr = "non-magnetic calculation; magnetic state untested"; } + console.log("CBM and VBM data:"); + console.log(cbm); + console.log(vbm); + return (
General info
  • Band gap: {formatAiidaProp(electronicData.band_gap, "eV")}
  • + {cbm?.value != null && vbm?.value != null && ( + <> +
  • + CBM: {cbm.value.toFixed(3)} eV at k = {cbm.x.toFixed(3)} +
  • +
  • + VBM: {vbm.value.toFixed(3)} eV at k = {vbm.x.toFixed(3)} +
  • + + )}
  • Magnetic state: {magStateStr}
  • Total magnetization:{" "} @@ -59,91 +74,149 @@ function ElectronicInfoBox({ electronicData, metadata }) { ); } -const ElectronicSection = (props) => { +//method to calculate the position of CBM and VBM +function calculateBMs(bandsData) { + const allPoints = []; + + bandsData.paths.forEach((obj) => { + const { x, values } = obj; + values.forEach((band) => { + band.forEach((val, idx) => { + allPoints.push({ x: x[idx], value: val }); + }); + }); + }); + + const positivePoints = allPoints.filter((p) => p.value > 0); + const negativePoints = allPoints.filter((p) => p.value < 0); + + const cbmPoint = positivePoints.reduce( + (minPoint, p) => (p.value < minPoint.value ? p : minPoint), + positivePoints[0], + ); + + const vbmPoint = negativePoints.reduce( + (maxPoint, p) => (p.value > maxPoint.value ? p : maxPoint), + negativePoints[0], + ); + + return { cbm: cbmPoint, vbm: vbmPoint }; +} + +// Lazy loading BandComponent. +const BandComponent = ({ + bandsData, + yRange = [-6.4, 6.4], + customTraces, + style, +}) => { + const containerRef = useRef(null); + + console.log("custom traces", customTraces); + + useEffect(() => { + if (!containerRef.current || !bandsData) return; + import("bands-visualiser").then(({ BandsVisualiser }) => { + const bandsDataArray = [ + { + bandsData, + traceFormat: { + showlegend: false, + line: { width: 2.0, color: "#636EFA" }, + }, + }, + ]; + + BandsVisualiser(containerRef.current, { + bandsDataArray, + settings: { yaxis: { range: yRange } }, + customTraces: customTraces || [], + }); + }); + }, [bandsData]); + + return
    ; +}; + +const ElectronicSection = ({ loadedData }) => { + const electronicData = loadedData.details.electronic; const [bandsData, setBandsData] = useState(null); const [loadingBands, setLoadingBands] = useState(true); + const [cbm, setCbm] = useState(null); + const [vbm, setVbm] = useState(null); - let electronicData = props.loadedData.details.electronic; - console.log("electronicData", electronicData); - - // check if we can display bands - let bandsAvailable = true; - if ( - electronicData.bands_uuid == null || - electronicData.fermi_energy.value == null || - electronicData.band_gap.value == null - ) { - bandsAvailable = false; - } + const bandsAvailable = + electronicData.bands_uuid != null && + electronicData.fermi_energy?.value != null && + electronicData.band_gap?.value != null; - let bandShift = 0.0; - if (bandsAvailable) { - // Shifting the bands such that Fermi energy is 0: - // It looks like the fermi energy currently gives us the top of the conduction band - // instead of the middle of the band gap. Therefore, shift additionally by half the gap. - // Note: for spin-polarized calculations, there are 2 Fermi energies. Taking the maximum - // here seems to work best to align 0 to the middle of the gap (although not stricly correct). - bandShift = -math.max(electronicData.fermi_energy.value); - bandShift -= electronicData.band_gap.value / 2; - } + // Compute band shift + const bandShift = bandsAvailable + ? -Math.max(electronicData.fermi_energy.value) - + electronicData.band_gap.value / 2 + : 0; + // Load band data if available useEffect(() => { - setBandsData(null); - if (bandsAvailable) { - loadAiidaBands(electronicData.bands_uuid).then((bands) => { - shiftBands(bands, bandShift); - setBandsData(bands); - setLoadingBands(false); - }); - } else { + if (!bandsAvailable) { setLoadingBands(false); + return; } - }, []); - - let bandsJsx = ""; - if (!bandsAvailable) { - bandsJsx = ( - Electronic bands are not available for this material. - ); - } else if (loadingBands) { - bandsJsx = ( -
    - -
    - ); - } else { - bandsJsx = ( - <> -
    - Electronic band structure{" "} - -
    - - - ); - } + + setBandsData(null); + setLoadingBands(true); + + loadAiidaBands(electronicData.bands_uuid).then((bands) => { + shiftBands(bands, bandShift); //shift before passing + + if (electronicData.band_gap.value > 0) { + const { cbm: cbmPoint, vbm: vbmPoint } = calculateBMs(bands); + setCbm(cbmPoint); + setVbm(vbmPoint); + } + + setBandsData(bands); + setLoadingBands(false); + }); + }, [electronicData.bands_uuid, bandsAvailable, bandShift]); return (
    Electronic properties
    - {bandsJsx} + + {loadingBands ? ( +
    + +
    + ) : !bandsAvailable ? ( + Electronic bands are not available for this material. + ) : ( + <> +
    + Electronic band structure{" "} + +
    + + + )} +
    diff --git a/src/DetailPage/VibrationalSection/index.jsx b/src/DetailPage/VibrationalSection/index.jsx index 4199c27..534174a 100644 --- a/src/DetailPage/VibrationalSection/index.jsx +++ b/src/DetailPage/VibrationalSection/index.jsx @@ -1,30 +1,50 @@ -import React, { useState, useEffect } from "react"; - -import { McloudSpinner } from "mc-react-library"; - -import { MCInfoBox } from "../components/MCInfoBox"; - +import React, { useState, useEffect, useRef, Suspense, lazy } from "react"; +import { McloudSpinner, ExploreButton } from "mc-react-library"; import { Container, Row, Col } from "react-bootstrap"; -import BandsVisualizer from "mc-react-bands"; +import { loadAiidaBands, loadPhononVis } from "../../common/restApiUtils"; +import { EXPLORE_URL } from "../../common/restApiUtils"; -import { ExploreButton } from "mc-react-library"; +//import PhononVisualizer from "mc-react-phonon-visualizer"; -import { loadAiidaBands, loadPhononVis } from "../../common/restApiUtils"; +// Lazy load of PhononVis seems to improve performance on bad networks. +// It might be worth implmenting this on the component level? +const LazyPhononVisualizer = lazy(() => import("mc-react-phonon-visualizer")); -import { AIIDA_REST_API_URL, EXPLORE_URL } from "../../common/restApiUtils"; +// Component that wraps the BandsVisualiser into a React component +const BandComponent = ({ bandsData, style }) => { + const containerRef = useRef(null); -import PhononVisualizer from "mc-react-phonon-visualizer"; + useEffect(() => { + if (!containerRef.current || !bandsData) return; + import("bands-visualiser").then(({ BandsVisualiser }) => { + const bandsDataArray = [ + { + bandsData, + traceFormat: { + showlegend: false, + line: { width: 2.0, color: "#636EFA" }, + }, + }, + ]; + + BandsVisualiser(containerRef.current, { + bandsDataArray, + settings: { yaxis: { title: { text: "Phonon bands [Thz]" } } }, + }); + }); + }, [bandsData]); + + return
    ; +}; -const VibrationalSection = (props) => { +const VibrationalSection = ({ loadedData, params }) => { const [bandsData, setBandsData] = useState(null); const [loadingBands, setLoadingBands] = useState(true); const [phononVisData, setPhononVisData] = useState(null); - let vibrationalData = props.loadedData.details.vibrational; - console.log("vibrationalData", vibrationalData); - - let bandsUuid = vibrationalData.phonon_bands_uuid; + const vibrationalData = loadedData.details.vibrational; + const bandsUuid = vibrationalData.phonon_bands_uuid; useEffect(() => { setBandsData(null); @@ -37,59 +57,18 @@ const VibrationalSection = (props) => { setLoadingBands(false); } - loadPhononVis(props.params.id).then((data) => { + loadPhononVis(params.id).then((data) => { setPhononVisData(data); console.log("Phonon visualizer data", data); }); - }, []); - - let bandsAvailable = bandsData != null; - let bandsJsx = ""; - if (bandsAvailable) { - bandsJsx = ( - - -
    - Phonon band structure{" "} - -
    - - - -
    - ); - } - - let phononVisAvailable = phononVisData != null; - let phononVisJsx = ""; - if (phononVisAvailable) { - // NOTE: The PhononVisualizer plotly clicking doesn't seem to work - // inside bootstrap ! Keep it outside. - phononVisJsx = ( -
    -
    -
    - Interactive phonon visualizer{" "} - -
    -
    - -
    - ); - } + }, [bandsUuid, params.id]); + + const bandsAvailable = bandsData != null; return (
    Vibrational properties
    + {loadingBands ? (
    @@ -98,10 +77,39 @@ const VibrationalSection = (props) => { ) : !bandsAvailable ? ( Vibrational properties not available for this structure. ) : ( - <>{bandsJsx} + + +
    + Phonon band structure{" "} + +
    + + + +
    )} - {phononVisJsx} + + {phononVisData && ( +
    +
    +
    + Interactive phonon visualizer{" "} + +
    +
    +
    + }> + + +
    +
    + )}
    ); };