@classytic/stage 0.2.0 → 0.4.0
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/LICENSE +1 -1
- package/README.md +5 -1
- package/dist/_virtual/_rolldown/runtime.mjs +18 -0
- package/dist/assets/index.mjs +0 -1
- package/dist/atomic/aufbau.d.mts +144 -0
- package/dist/atomic/aufbau.mjs +384 -0
- package/dist/atomic/elements.d.mts +67 -0
- package/dist/atomic/elements.mjs +785 -0
- package/dist/atomic/emission.d.mts +112 -0
- package/dist/atomic/emission.mjs +179 -0
- package/dist/atomic/index.d.mts +8 -0
- package/dist/atomic/index.mjs +9 -0
- package/dist/atomic/periodic.d.mts +82 -0
- package/dist/atomic/periodic.mjs +231 -0
- package/dist/atomic/properties.d.mts +100 -0
- package/dist/atomic/properties.mjs +574 -0
- package/dist/atomic/shells.d.mts +137 -0
- package/dist/atomic/shells.mjs +170 -0
- package/dist/atomic/trends.d.mts +148 -0
- package/dist/atomic/trends.mjs +225 -0
- package/dist/builder/Palette.mjs +50 -89
- package/dist/builder/SceneBuilder.mjs +15 -73
- package/dist/chem/chromatography.d.mts +66 -0
- package/dist/chem/chromatography.mjs +63 -0
- package/dist/chem/complex.d.mts +114 -0
- package/dist/chem/complex.mjs +234 -0
- package/dist/chem/cycle.d.mts +75 -0
- package/dist/chem/cycle.mjs +145 -0
- package/dist/chem/equation.d.mts +86 -0
- package/dist/chem/equation.mjs +168 -0
- package/dist/chem/formula.d.mts +90 -0
- package/dist/chem/formula.mjs +201 -0
- package/dist/chem/functional-groups.d.mts +31 -0
- package/dist/chem/functional-groups.mjs +187 -0
- package/dist/chem/hazard.d.mts +94 -0
- package/dist/chem/hazard.mjs +329 -0
- package/dist/chem/index.d.mts +34 -33
- package/dist/chem/index.mjs +34 -28
- package/dist/chem/ionic.d.mts +98 -0
- package/dist/chem/ionic.mjs +156 -0
- package/dist/chem/layout.d.mts +46 -0
- package/dist/chem/layout.mjs +342 -0
- package/dist/chem/lewis.d.mts +79 -0
- package/dist/chem/lewis.mjs +163 -0
- package/dist/chem/molecular-orbital.d.mts +114 -0
- package/dist/chem/molecular-orbital.mjs +201 -0
- package/dist/chem/molecule.d.mts +154 -0
- package/dist/chem/molecule.mjs +282 -0
- package/dist/chem/nmr.d.mts +153 -0
- package/dist/chem/nmr.mjs +165 -0
- package/dist/chem/polymer.d.mts +65 -0
- package/dist/chem/polymer.mjs +179 -0
- package/dist/chem/redox.d.mts +138 -0
- package/dist/chem/redox.mjs +328 -0
- package/dist/chem/relations.d.mts +183 -0
- package/dist/chem/relations.mjs +299 -0
- package/dist/chem/skeleton.d.mts +51 -0
- package/dist/chem/skeleton.mjs +186 -0
- package/dist/chem/solution.d.mts +116 -0
- package/dist/chem/solution.mjs +186 -0
- package/dist/chem/species.d.mts +47 -0
- package/dist/chem/species.mjs +128 -0
- package/dist/chem/stereo.d.mts +108 -0
- package/dist/chem/stereo.mjs +286 -0
- package/dist/chem/stoichiometry.d.mts +95 -0
- package/dist/chem/stoichiometry.mjs +145 -0
- package/dist/chem/synthesis.d.mts +113 -0
- package/dist/chem/synthesis.mjs +281 -0
- package/dist/chem/vsepr.d.mts +80 -0
- package/dist/chem/vsepr.mjs +247 -0
- package/dist/circuit/ac.d.mts +163 -0
- package/dist/circuit/ac.mjs +452 -0
- package/dist/circuit/dc.d.mts +92 -0
- package/dist/circuit/dc.mjs +333 -0
- package/dist/circuit/index.d.mts +5 -92
- package/dist/circuit/index.mjs +5 -332
- package/dist/circuit/network.d.mts +99 -0
- package/dist/circuit/network.mjs +440 -0
- package/dist/circuit/topology.d.mts +102 -0
- package/dist/circuit/topology.mjs +143 -0
- package/dist/colour/absorption.d.mts +79 -0
- package/dist/colour/absorption.mjs +137 -0
- package/dist/colour/index.d.mts +6 -0
- package/dist/colour/index.mjs +7 -0
- package/dist/colour/name.d.mts +14 -0
- package/dist/colour/name.mjs +76 -0
- package/dist/colour/observer.d.mts +31 -0
- package/dist/colour/observer.mjs +46 -0
- package/dist/colour/spectrum.d.mts +61 -0
- package/dist/colour/spectrum.mjs +108 -0
- package/dist/colour/srgb.d.mts +49 -0
- package/dist/colour/srgb.mjs +108 -0
- package/dist/core/constants.d.mts +138 -0
- package/dist/core/constants.mjs +139 -0
- package/dist/core/index.d.mts +4 -1
- package/dist/core/index.mjs +4 -1
- package/dist/core/math.d.mts +26 -0
- package/dist/core/math.mjs +37 -0
- package/dist/core/vec3.d.mts +94 -0
- package/dist/core/vec3.mjs +123 -0
- package/dist/core/worked.d.mts +35 -0
- package/dist/core/worked.mjs +44 -0
- package/dist/electro/cell.d.mts +102 -0
- package/dist/electro/cell.mjs +170 -0
- package/dist/electro/conductivity.d.mts +157 -0
- package/dist/electro/conductivity.mjs +156 -0
- package/dist/electro/couples.d.mts +114 -0
- package/dist/electro/couples.mjs +334 -0
- package/dist/electro/electrolysis.d.mts +96 -0
- package/dist/electro/electrolysis.mjs +274 -0
- package/dist/electro/faraday.d.mts +150 -0
- package/dist/electro/faraday.mjs +118 -0
- package/dist/electro/index.d.mts +6 -0
- package/dist/electro/index.mjs +7 -0
- package/dist/finance/accounting/adjustments.d.mts +17 -0
- package/dist/finance/accounting/adjustments.mjs +202 -0
- package/dist/finance/accounting/analysis.d.mts +8 -0
- package/dist/finance/accounting/analysis.mjs +205 -0
- package/dist/finance/accounting/clubs.d.mts +21 -0
- package/dist/finance/accounting/clubs.mjs +217 -0
- package/dist/finance/accounting/company-statements.d.mts +7 -0
- package/dist/finance/accounting/company-statements.mjs +150 -0
- package/dist/finance/accounting/harness.d.mts +7 -0
- package/dist/finance/accounting/harness.mjs +45 -0
- package/dist/finance/accounting/index.d.mts +18 -0
- package/dist/finance/accounting/index.mjs +18 -0
- package/dist/finance/accounting/journalize.d.mts +16 -0
- package/dist/finance/accounting/journalize.mjs +128 -0
- package/dist/finance/accounting/manufacturing.d.mts +8 -0
- package/dist/finance/accounting/manufacturing.mjs +236 -0
- package/dist/finance/accounting/partnership.d.mts +7 -0
- package/dist/finance/accounting/partnership.mjs +152 -0
- package/dist/finance/accounting/period-case.d.mts +6 -0
- package/dist/finance/accounting/period-case.mjs +71 -0
- package/dist/finance/accounting/post.d.mts +9 -0
- package/dist/finance/accounting/post.mjs +84 -0
- package/dist/finance/accounting/precision.d.mts +9 -0
- package/dist/finance/accounting/precision.mjs +15 -0
- package/dist/finance/accounting/rules.d.mts +21 -0
- package/dist/finance/accounting/rules.mjs +37 -0
- package/dist/finance/accounting/solve.d.mts +28 -0
- package/dist/finance/accounting/solve.mjs +92 -0
- package/dist/finance/accounting/statements.d.mts +7 -0
- package/dist/finance/accounting/statements.mjs +142 -0
- package/dist/finance/accounting/types.d.mts +649 -0
- package/dist/finance/accounting/validate.d.mts +7 -0
- package/dist/finance/accounting/validate.mjs +67 -0
- package/dist/finance/accounting/verification.d.mts +24 -0
- package/dist/finance/accounting/verification.mjs +386 -0
- package/dist/finance/bizsim.d.mts +75 -0
- package/dist/finance/bizsim.mjs +296 -0
- package/dist/finance/index.d.mts +95 -0
- package/dist/finance/index.mjs +145 -0
- package/dist/finance/investment-math.d.mts +7 -0
- package/dist/finance/investment-math.mjs +18 -0
- package/dist/finance/management-accounting/budgeting.d.mts +7 -0
- package/dist/finance/management-accounting/budgeting.mjs +189 -0
- package/dist/finance/management-accounting/cash-budget.d.mts +7 -0
- package/dist/finance/management-accounting/cash-budget.mjs +154 -0
- package/dist/finance/management-accounting/costing.d.mts +8 -0
- package/dist/finance/management-accounting/costing.mjs +175 -0
- package/dist/finance/management-accounting/decisions.d.mts +8 -0
- package/dist/finance/management-accounting/decisions.mjs +164 -0
- package/dist/finance/management-accounting/index.d.mts +13 -0
- package/dist/finance/management-accounting/index.mjs +13 -0
- package/dist/finance/management-accounting/integrated-budget.d.mts +8 -0
- package/dist/finance/management-accounting/integrated-budget.mjs +67 -0
- package/dist/finance/management-accounting/inventory-costing.d.mts +7 -0
- package/dist/finance/management-accounting/inventory-costing.mjs +173 -0
- package/dist/finance/management-accounting/investment-appraisal.d.mts +7 -0
- package/dist/finance/management-accounting/investment-appraisal.mjs +148 -0
- package/dist/finance/management-accounting/management-case.d.mts +8 -0
- package/dist/finance/management-accounting/management-case.mjs +91 -0
- package/dist/finance/management-accounting/master-budget.d.mts +7 -0
- package/dist/finance/management-accounting/master-budget.mjs +137 -0
- package/dist/finance/management-accounting/production-costing.d.mts +8 -0
- package/dist/finance/management-accounting/production-costing.mjs +214 -0
- package/dist/finance/management-accounting/types.d.mts +644 -0
- package/dist/finance/management-accounting/variances.d.mts +7 -0
- package/dist/finance/management-accounting/variances.mjs +150 -0
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +5 -5
- package/dist/interaction/MovableDot.mjs +19 -0
- package/dist/interaction/useDraggable.mjs +24 -4
- package/dist/math/analysis.d.mts +61 -0
- package/dist/math/analysis.mjs +187 -0
- package/dist/math/answer-check.d.mts +50 -0
- package/dist/math/answer-check.mjs +106 -0
- package/dist/math/calculus.d.mts +17 -1
- package/dist/math/calculus.mjs +365 -2
- package/dist/math/complex.d.mts +93 -0
- package/dist/math/complex.mjs +239 -0
- package/dist/math/expr.d.mts +23 -0
- package/dist/math/expr.mjs +36 -0
- package/dist/math/index.d.mts +9 -23
- package/dist/math/index.mjs +9 -38
- package/dist/math/latex.mjs +10 -1
- package/dist/math/parse.mjs +1 -1
- package/dist/math/poly.d.mts +55 -0
- package/dist/math/poly.mjs +273 -0
- package/dist/math/rearrange.d.mts +14 -0
- package/dist/math/rearrange.mjs +125 -0
- package/dist/math/solve.d.mts +21 -0
- package/dist/math/solve.mjs +75 -0
- package/dist/networking/addressing.d.mts +26 -0
- package/dist/networking/addressing.mjs +33 -0
- package/dist/networking/cloud/access.d.mts +36 -0
- package/dist/networking/cloud/access.mjs +95 -0
- package/dist/networking/cloud/containers.d.mts +43 -0
- package/dist/networking/cloud/containers.mjs +54 -0
- package/dist/networking/cloud/edge.d.mts +17 -0
- package/dist/networking/cloud/edge.mjs +31 -0
- package/dist/networking/cloud/failure-domains.d.mts +19 -0
- package/dist/networking/cloud/failure-domains.mjs +22 -0
- package/dist/networking/cloud/geo-routing.d.mts +22 -0
- package/dist/networking/cloud/geo-routing.mjs +64 -0
- package/dist/networking/cloud/index.d.mts +18 -0
- package/dist/networking/cloud/index.mjs +19 -0
- package/dist/networking/cloud/inference-reliability.d.mts +30 -0
- package/dist/networking/cloud/inference-reliability.mjs +76 -0
- package/dist/networking/cloud/inference.d.mts +118 -0
- package/dist/networking/cloud/inference.mjs +179 -0
- package/dist/networking/cloud/infrastructure.d.mts +45 -0
- package/dist/networking/cloud/infrastructure.mjs +96 -0
- package/dist/networking/cloud/observability.d.mts +24 -0
- package/dist/networking/cloud/observability.mjs +34 -0
- package/dist/networking/cloud/orchestration.d.mts +32 -0
- package/dist/networking/cloud/orchestration.mjs +38 -0
- package/dist/networking/cloud/recovery.d.mts +21 -0
- package/dist/networking/cloud/recovery.mjs +27 -0
- package/dist/networking/cloud/releases.d.mts +42 -0
- package/dist/networking/cloud/releases.mjs +135 -0
- package/dist/networking/cloud/scaling.d.mts +25 -0
- package/dist/networking/cloud/scaling.mjs +65 -0
- package/dist/networking/cloud/secrets.d.mts +39 -0
- package/dist/networking/cloud/secrets.mjs +68 -0
- package/dist/networking/cloud/server-deployment.d.mts +29 -0
- package/dist/networking/cloud/server-deployment.mjs +72 -0
- package/dist/networking/cloud/storage.d.mts +16 -0
- package/dist/networking/cloud/storage.mjs +58 -0
- package/dist/networking/cloud/vpc.d.mts +18 -0
- package/dist/networking/cloud/vpc.mjs +100 -0
- package/dist/networking/delivery.d.mts +34 -0
- package/dist/networking/delivery.mjs +52 -0
- package/dist/networking/index.d.mts +47 -0
- package/dist/networking/index.mjs +47 -0
- package/dist/networking/ipv6.d.mts +28 -0
- package/dist/networking/ipv6.mjs +17 -0
- package/dist/networking/journeys.d.mts +83 -0
- package/dist/networking/journeys.mjs +161 -0
- package/dist/networking/models/airtime.d.mts +87 -0
- package/dist/networking/models/airtime.mjs +130 -0
- package/dist/networking/models/cellular.d.mts +157 -0
- package/dist/networking/models/cellular.mjs +252 -0
- package/dist/networking/models/encapsulation.d.mts +98 -0
- package/dist/networking/models/encapsulation.mjs +132 -0
- package/dist/networking/models/handover.d.mts +116 -0
- package/dist/networking/models/handover.mjs +209 -0
- package/dist/networking/models/handshake.d.mts +90 -0
- package/dist/networking/models/handshake.mjs +122 -0
- package/dist/networking/models/index.d.mts +14 -0
- package/dist/networking/models/index.mjs +15 -0
- package/dist/networking/models/media.d.mts +82 -0
- package/dist/networking/models/media.mjs +188 -0
- package/dist/networking/models/satellite.d.mts +86 -0
- package/dist/networking/models/satellite.mjs +121 -0
- package/dist/networking/models/spectrum.d.mts +83 -0
- package/dist/networking/models/spectrum.mjs +235 -0
- package/dist/networking/models/switch.d.mts +80 -0
- package/dist/networking/models/switch.mjs +155 -0
- package/dist/networking/models/vlan.d.mts +62 -0
- package/dist/networking/models/vlan.mjs +100 -0
- package/dist/networking/models/vpn.d.mts +66 -0
- package/dist/networking/models/vpn.mjs +125 -0
- package/dist/networking/models/wifi-privacy.d.mts +82 -0
- package/dist/networking/models/wifi-privacy.mjs +130 -0
- package/dist/networking/models/wifi.d.mts +240 -0
- package/dist/networking/models/wifi.mjs +562 -0
- package/dist/networking/nat-traversal.d.mts +24 -0
- package/dist/networking/nat-traversal.mjs +38 -0
- package/dist/networking/protocols.d.mts +17 -0
- package/dist/networking/protocols.mjs +57 -0
- package/dist/networking/queue.d.mts +12 -0
- package/dist/networking/queue.mjs +18 -0
- package/dist/networking/route-learning.d.mts +23 -0
- package/dist/networking/route-learning.mjs +43 -0
- package/dist/networking/routing.d.mts +11 -0
- package/dist/networking/routing.mjs +25 -0
- package/dist/networking/signals.d.mts +26 -0
- package/dist/networking/signals.mjs +35 -0
- package/dist/networking/subnet.d.mts +49 -0
- package/dist/networking/subnet.mjs +89 -0
- package/dist/networking/tcp-reliability.d.mts +24 -0
- package/dist/networking/tcp-reliability.mjs +38 -0
- package/dist/networking/topology/contract.d.mts +123 -0
- package/dist/networking/topology/index.d.mts +4 -0
- package/dist/networking/topology/index.mjs +4 -0
- package/dist/networking/topology/presets.d.mts +7 -0
- package/dist/networking/topology/presets.mjs +302 -0
- package/dist/networking/topology/simulation.d.mts +8 -0
- package/dist/networking/topology/simulation.mjs +179 -0
- package/dist/networking/wireless.d.mts +11 -0
- package/dist/networking/wireless.mjs +17 -0
- package/dist/optics/imaging.d.mts +218 -0
- package/dist/optics/imaging.mjs +276 -0
- package/dist/optics/index.d.mts +7 -0
- package/dist/optics/index.mjs +8 -0
- package/dist/optics/laws.d.mts +70 -0
- package/dist/optics/laws.mjs +195 -0
- package/dist/optics/prism.d.mts +148 -0
- package/dist/optics/prism.mjs +197 -0
- package/dist/optics/refraction.d.mts +201 -0
- package/dist/optics/refraction.mjs +270 -0
- package/dist/optics/wave-optics.d.mts +171 -0
- package/dist/optics/wave-optics.mjs +259 -0
- package/dist/physics/ac/index.d.mts +183 -0
- package/dist/physics/ac/index.mjs +203 -0
- package/dist/physics/dynamics/forces.d.mts +98 -0
- package/dist/physics/dynamics/forces.mjs +153 -0
- package/dist/physics/dynamics/friction.d.mts +76 -0
- package/dist/physics/dynamics/friction.mjs +119 -0
- package/dist/physics/dynamics/system.d.mts +68 -0
- package/dist/physics/dynamics/system.mjs +125 -0
- package/dist/physics/electrostatics/index.d.mts +169 -0
- package/dist/physics/electrostatics/index.mjs +249 -0
- package/dist/physics/energy/centre-of-mass.d.mts +115 -0
- package/dist/physics/energy/centre-of-mass.mjs +180 -0
- package/dist/physics/energy/conservation.d.mts +81 -0
- package/dist/physics/energy/conservation.mjs +129 -0
- package/dist/physics/energy/power.d.mts +90 -0
- package/dist/physics/energy/power.mjs +167 -0
- package/dist/physics/energy/spring.d.mts +73 -0
- package/dist/physics/energy/spring.mjs +129 -0
- package/dist/physics/energy/store.d.mts +63 -0
- package/dist/physics/energy/store.mjs +108 -0
- package/dist/physics/energy/theorem.d.mts +65 -0
- package/dist/physics/energy/theorem.mjs +113 -0
- package/dist/physics/energy/work.d.mts +114 -0
- package/dist/physics/energy/work.mjs +219 -0
- package/dist/physics/gravitation/body.d.mts +85 -0
- package/dist/physics/gravitation/body.mjs +168 -0
- package/dist/physics/gravitation/kepler.d.mts +127 -0
- package/dist/physics/gravitation/kepler.mjs +216 -0
- package/dist/physics/gravitation/law.d.mts +117 -0
- package/dist/physics/gravitation/law.mjs +175 -0
- package/dist/physics/gravitation/orbit.d.mts +119 -0
- package/dist/physics/gravitation/orbit.mjs +202 -0
- package/dist/physics/gravitation/potential.d.mts +114 -0
- package/dist/physics/gravitation/potential.mjs +202 -0
- package/dist/physics/gravitation/variation.d.mts +97 -0
- package/dist/physics/gravitation/variation.mjs +175 -0
- package/dist/physics/index.d.mts +35 -0
- package/dist/physics/index.mjs +36 -0
- package/dist/physics/integral-sweep.d.mts +63 -0
- package/dist/physics/integral-sweep.mjs +126 -0
- package/dist/physics/kinematics/equations.d.mts +47 -0
- package/dist/physics/kinematics/equations.mjs +126 -0
- package/dist/physics/kinematics/free-fall.d.mts +53 -0
- package/dist/physics/kinematics/free-fall.mjs +71 -0
- package/dist/physics/kinematics/given.d.mts +16 -0
- package/dist/physics/kinematics/given.mjs +55 -0
- package/dist/physics/kinematics/roots.d.mts +41 -0
- package/dist/physics/kinematics/roots.mjs +72 -0
- package/dist/physics/kinematics/solve.d.mts +52 -0
- package/dist/physics/kinematics/solve.mjs +172 -0
- package/dist/physics/magnetism/biot-savart.d.mts +161 -0
- package/dist/physics/magnetism/biot-savart.mjs +353 -0
- package/dist/physics/magnetism/flux.d.mts +94 -0
- package/dist/physics/magnetism/flux.mjs +313 -0
- package/dist/physics/magnetism/geomagnetism.d.mts +151 -0
- package/dist/physics/magnetism/geomagnetism.mjs +184 -0
- package/dist/physics/magnetism/index.d.mts +12 -0
- package/dist/physics/magnetism/index.mjs +74 -0
- package/dist/physics/magnetism/lorentz.d.mts +28 -0
- package/dist/physics/magnetism/lorentz.mjs +88 -0
- package/dist/physics/magnetism/magnetic-force.d.mts +102 -0
- package/dist/physics/magnetism/magnetic-force.mjs +169 -0
- package/dist/physics/momentum/collide.d.mts +77 -0
- package/dist/physics/momentum/collide.mjs +115 -0
- package/dist/physics/momentum/impulse.d.mts +89 -0
- package/dist/physics/momentum/impulse.mjs +159 -0
- package/dist/physics/momentum/planar.d.mts +60 -0
- package/dist/physics/momentum/planar.mjs +110 -0
- package/dist/physics/projectile.d.mts +107 -0
- package/dist/physics/projectile.mjs +185 -0
- package/dist/physics/rotation/angular.d.mts +85 -0
- package/dist/physics/rotation/angular.mjs +130 -0
- package/dist/physics/rotation/circular.d.mts +79 -0
- package/dist/physics/rotation/circular.mjs +131 -0
- package/dist/physics/rotation/inertia.d.mts +164 -0
- package/dist/physics/rotation/inertia.mjs +332 -0
- package/dist/physics/rotation/torque.d.mts +65 -0
- package/dist/physics/rotation/torque.mjs +134 -0
- package/dist/physics/vectors.d.mts +64 -0
- package/dist/physics/vectors.mjs +114 -0
- package/dist/primitives/Dot.d.mts +2 -15
- package/dist/primitives/Dot.mjs +6 -4
- package/dist/primitives/Grid.d.mts +33 -17
- package/dist/primitives/Grid.mjs +89 -17
- package/dist/primitives/Label.d.mts +1 -14
- package/dist/primitives/Label.mjs +3 -2
- package/dist/primitives/Lines.d.mts +4 -32
- package/dist/primitives/Lines.mjs +10 -8
- package/dist/primitives/Shapes.d.mts +5 -43
- package/dist/primitives/Shapes.mjs +12 -10
- package/dist/primitives/index.d.mts +2 -2
- package/dist/primitives/index.mjs +2 -2
- package/dist/primitives/props.mjs +31 -0
- package/dist/quantity/index.d.mts +119 -0
- package/dist/quantity/index.mjs +417 -0
- package/dist/relation/derive.d.mts +74 -0
- package/dist/relation/derive.mjs +73 -0
- package/dist/relation/index.d.mts +56 -0
- package/dist/relation/index.mjs +234 -0
- package/dist/scene/Scene.d.mts +6 -1
- package/dist/scene/Scene.mjs +15 -40
- package/dist/scene/evaluators.mjs +1 -1
- package/dist/sim/thermal.mjs +45 -20
- package/dist/thermo/calorimetry.d.mts +207 -0
- package/dist/thermo/calorimetry.mjs +345 -0
- package/dist/thermo/cycles.d.mts +142 -0
- package/dist/thermo/cycles.mjs +256 -0
- package/dist/thermo/entropy.d.mts +87 -0
- package/dist/thermo/entropy.mjs +140 -0
- package/dist/thermo/expansion.d.mts +130 -0
- package/dist/thermo/expansion.mjs +178 -0
- package/dist/thermo/gas.d.mts +197 -0
- package/dist/thermo/gas.mjs +279 -0
- package/dist/thermo/heat-transfer.d.mts +164 -0
- package/dist/thermo/heat-transfer.mjs +217 -0
- package/dist/thermo/index.d.mts +9 -82
- package/dist/thermo/index.mjs +9 -132
- package/dist/thermo/processes.d.mts +95 -0
- package/dist/thermo/processes.mjs +154 -0
- package/dist/thermo/temperature.d.mts +174 -0
- package/dist/thermo/temperature.mjs +236 -0
- package/dist/view/Stage.mjs +4 -11
- package/dist/waves/damping.d.mts +224 -0
- package/dist/waves/damping.mjs +317 -0
- package/dist/waves/doppler.d.mts +72 -0
- package/dist/waves/doppler.mjs +125 -0
- package/dist/waves/index.d.mts +10 -0
- package/dist/waves/index.mjs +10 -0
- package/dist/waves/pendulum.d.mts +130 -0
- package/dist/waves/pendulum.mjs +211 -0
- package/dist/waves/pitch.d.mts +125 -0
- package/dist/waves/pitch.mjs +158 -0
- package/dist/waves/shm.d.mts +170 -0
- package/dist/waves/shm.mjs +279 -0
- package/dist/waves/springs.d.mts +103 -0
- package/dist/waves/springs.mjs +176 -0
- package/dist/waves/superposition.d.mts +124 -0
- package/dist/waves/superposition.mjs +196 -0
- package/dist/waves/wave.d.mts +154 -0
- package/dist/waves/wave.mjs +178 -0
- package/package.json +103 -23
- package/styles.css +125 -8
- package/dist/assets/kit/index.mjs +0 -4
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/chem/ionic.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ions, and the formula two of them make.
|
|
4
|
+
*
|
|
5
|
+
* ## The one rule, which is usually taught as a trick
|
|
6
|
+
*
|
|
7
|
+
* "Write the formula of aluminium oxide" is answered in most classrooms by the crossover trick:
|
|
8
|
+
* write the charges, swap them over, drop the signs. It gives Al2O3, and it gives the wrong answer
|
|
9
|
+
* for magnesium oxide, where crossing over 2 and 2 gives Mg2O2 and the compound is MgO. The trick
|
|
10
|
+
* then acquires a patch: "and cancel down if you can".
|
|
11
|
+
*
|
|
12
|
+
* There is no trick. A compound has no overall charge, so the positive and negative charges in it
|
|
13
|
+
* must cancel exactly, and the formula is the SMALLEST whole number of each ion that makes them.
|
|
14
|
+
* That is a lowest common multiple, and it gets magnesium oxide right without a patch because the
|
|
15
|
+
* smallest multiple of 2 that is also a multiple of 2 is 2, not 4.
|
|
16
|
+
*
|
|
17
|
+
* {@link neutralRatio} computes it that way, so the reasoning a learner is shown is the reasoning
|
|
18
|
+
* the engine uses.
|
|
19
|
+
*
|
|
20
|
+
* ## Charges are data, and the ones worth knowing are here
|
|
21
|
+
*
|
|
22
|
+
* Which charge an ion carries is not derivable for a transition metal, and only roughly derivable
|
|
23
|
+
* from the group for everything else. So the common ones are listed, with their names, because
|
|
24
|
+
* "what is the formula of iron(III) sulfate" needs both the charge and the fact that sulfate is one
|
|
25
|
+
* unit that keeps its brackets.
|
|
26
|
+
*
|
|
27
|
+
* Pure: no React, no pixels.
|
|
28
|
+
*/
|
|
29
|
+
interface Ion {
|
|
30
|
+
/** As written in a formula, without the charge: "SO4", "Al", "NH4". */
|
|
31
|
+
readonly formula: string;
|
|
32
|
+
/** Signed: +3 for aluminium, -2 for sulfate. */
|
|
33
|
+
readonly charge: number;
|
|
34
|
+
/** The name it takes in a compound: "aluminium", "sulfate". */
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/**
|
|
37
|
+
* Whether it is more than one atom, and so needs brackets when there is more than one of it.
|
|
38
|
+
*
|
|
39
|
+
* Ca(NO3)2 needs them and CaCl2 does not, and the difference is exactly this. Without brackets
|
|
40
|
+
* the formula reads as one nitrogen and thirty-two oxygens.
|
|
41
|
+
*/
|
|
42
|
+
readonly polyatomic: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** The cations a syllabus expects to be known. */
|
|
45
|
+
declare const CATIONS: readonly Ion[];
|
|
46
|
+
/** The anions a syllabus expects to be known. */
|
|
47
|
+
declare const ANIONS: readonly Ion[];
|
|
48
|
+
/** Every ion listed here, cations and anions together. */
|
|
49
|
+
declare const IONS: readonly Ion[];
|
|
50
|
+
/**
|
|
51
|
+
* Find an ion by the name it takes in a compound.
|
|
52
|
+
*
|
|
53
|
+
* Matched case-insensitively on BOTH sides. Lowercasing only the input looked right and failed for
|
|
54
|
+
* every ion whose name carries a Roman numeral, since "iron(iii)" never matches "iron(III)".
|
|
55
|
+
*/
|
|
56
|
+
declare const ionByName: (name: string) => Ion | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* How many of each ion make a compound with no overall charge, in the smallest whole numbers.
|
|
59
|
+
*
|
|
60
|
+
* The lowest common multiple of the two charge sizes, divided by each. Magnesium and oxide both
|
|
61
|
+
* carry two, their lowest common multiple is two, and one of each is the answer: MgO. The crossover
|
|
62
|
+
* trick would have given Mg2O2 and needed a separate rule to cancel it down.
|
|
63
|
+
*/
|
|
64
|
+
declare function neutralRatio(cationCharge: number, anionCharge: number): [number, number];
|
|
65
|
+
/** One ion written out with its count, bracketed when it needs to be. */
|
|
66
|
+
declare function writeIon(part: Ion, count: number): string;
|
|
67
|
+
interface IonicCompound {
|
|
68
|
+
readonly formula: string;
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly cation: Ion;
|
|
71
|
+
readonly anion: Ion;
|
|
72
|
+
readonly cationCount: number;
|
|
73
|
+
readonly anionCount: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The compound two ions form.
|
|
77
|
+
*
|
|
78
|
+
* `ionicFormula(aluminium, oxide)` gives Al2O3; `ionicFormula(magnesium, oxide)` gives MgO, not
|
|
79
|
+
* Mg2O2; `ionicFormula(calcium, nitrate)` gives Ca(NO3)2 with the brackets that stop it being read
|
|
80
|
+
* as thirty-two oxygens.
|
|
81
|
+
*/
|
|
82
|
+
declare function ionicCompound(cation: Ion, anion: Ion): IonicCompound;
|
|
83
|
+
/** The same, by name: `compoundOf('calcium', 'carbonate')`. */
|
|
84
|
+
declare function compoundOf(cationName: string, anionName: string): IonicCompound;
|
|
85
|
+
/**
|
|
86
|
+
* Whether a proposed pair of counts makes a neutral compound, and whether it is the simplest one.
|
|
87
|
+
*
|
|
88
|
+
* Both answers matter and they are different. Mg2O2 IS neutral and is not a formula anyone writes,
|
|
89
|
+
* so a learner offering it has understood the charge balance and not the "smallest" part, and
|
|
90
|
+
* telling them only that it is wrong would hide which half they have.
|
|
91
|
+
*/
|
|
92
|
+
declare function assessCounts(cation: Ion, anion: Ion, cationCount: number, anionCount: number): {
|
|
93
|
+
totalCharge: number;
|
|
94
|
+
neutral: boolean;
|
|
95
|
+
simplest: boolean;
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { ANIONS, CATIONS, IONS, Ion, IonicCompound, assessCounts, compoundOf, ionByName, ionicCompound, neutralRatio, writeIon };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { parseFormula } from "./formula.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/ionic.ts
|
|
4
|
+
/**
|
|
5
|
+
* Ions, and the formula two of them make.
|
|
6
|
+
*
|
|
7
|
+
* ## The one rule, which is usually taught as a trick
|
|
8
|
+
*
|
|
9
|
+
* "Write the formula of aluminium oxide" is answered in most classrooms by the crossover trick:
|
|
10
|
+
* write the charges, swap them over, drop the signs. It gives Al2O3, and it gives the wrong answer
|
|
11
|
+
* for magnesium oxide, where crossing over 2 and 2 gives Mg2O2 and the compound is MgO. The trick
|
|
12
|
+
* then acquires a patch: "and cancel down if you can".
|
|
13
|
+
*
|
|
14
|
+
* There is no trick. A compound has no overall charge, so the positive and negative charges in it
|
|
15
|
+
* must cancel exactly, and the formula is the SMALLEST whole number of each ion that makes them.
|
|
16
|
+
* That is a lowest common multiple, and it gets magnesium oxide right without a patch because the
|
|
17
|
+
* smallest multiple of 2 that is also a multiple of 2 is 2, not 4.
|
|
18
|
+
*
|
|
19
|
+
* {@link neutralRatio} computes it that way, so the reasoning a learner is shown is the reasoning
|
|
20
|
+
* the engine uses.
|
|
21
|
+
*
|
|
22
|
+
* ## Charges are data, and the ones worth knowing are here
|
|
23
|
+
*
|
|
24
|
+
* Which charge an ion carries is not derivable for a transition metal, and only roughly derivable
|
|
25
|
+
* from the group for everything else. So the common ones are listed, with their names, because
|
|
26
|
+
* "what is the formula of iron(III) sulfate" needs both the charge and the fact that sulfate is one
|
|
27
|
+
* unit that keeps its brackets.
|
|
28
|
+
*
|
|
29
|
+
* Pure: no React, no pixels.
|
|
30
|
+
*/
|
|
31
|
+
const ion = (formula, charge, name) => ({
|
|
32
|
+
formula,
|
|
33
|
+
charge,
|
|
34
|
+
name,
|
|
35
|
+
polyatomic: parseFormula(formula).reduce((total, e) => total + e.count, 0) > 1
|
|
36
|
+
});
|
|
37
|
+
/** The cations a syllabus expects to be known. */
|
|
38
|
+
const CATIONS = [
|
|
39
|
+
ion("H", 1, "hydrogen"),
|
|
40
|
+
ion("Li", 1, "lithium"),
|
|
41
|
+
ion("Na", 1, "sodium"),
|
|
42
|
+
ion("K", 1, "potassium"),
|
|
43
|
+
ion("Ag", 1, "silver"),
|
|
44
|
+
ion("NH4", 1, "ammonium"),
|
|
45
|
+
ion("Mg", 2, "magnesium"),
|
|
46
|
+
ion("Ca", 2, "calcium"),
|
|
47
|
+
ion("Ba", 2, "barium"),
|
|
48
|
+
ion("Zn", 2, "zinc"),
|
|
49
|
+
ion("Cu", 2, "copper(II)"),
|
|
50
|
+
ion("Fe", 2, "iron(II)"),
|
|
51
|
+
ion("Pb", 2, "lead(II)"),
|
|
52
|
+
ion("Al", 3, "aluminium"),
|
|
53
|
+
ion("Cr", 3, "chromium(III)")
|
|
54
|
+
];
|
|
55
|
+
/** Iron and copper each appear twice, so the ion has to be named rather than the element. */
|
|
56
|
+
const IRON_III = ion("Fe", 3, "iron(III)");
|
|
57
|
+
const COPPER_I = ion("Cu", 1, "copper(I)");
|
|
58
|
+
/** The anions a syllabus expects to be known. */
|
|
59
|
+
const ANIONS = [
|
|
60
|
+
ion("F", -1, "fluoride"),
|
|
61
|
+
ion("Cl", -1, "chloride"),
|
|
62
|
+
ion("Br", -1, "bromide"),
|
|
63
|
+
ion("I", -1, "iodide"),
|
|
64
|
+
ion("OH", -1, "hydroxide"),
|
|
65
|
+
ion("NO3", -1, "nitrate"),
|
|
66
|
+
ion("HCO3", -1, "hydrogencarbonate"),
|
|
67
|
+
ion("O", -2, "oxide"),
|
|
68
|
+
ion("S", -2, "sulfide"),
|
|
69
|
+
ion("SO4", -2, "sulfate"),
|
|
70
|
+
ion("CO3", -2, "carbonate"),
|
|
71
|
+
ion("PO4", -3, "phosphate")
|
|
72
|
+
];
|
|
73
|
+
/** Every ion listed here, cations and anions together. */
|
|
74
|
+
const IONS = [
|
|
75
|
+
...CATIONS,
|
|
76
|
+
IRON_III,
|
|
77
|
+
COPPER_I,
|
|
78
|
+
...ANIONS
|
|
79
|
+
];
|
|
80
|
+
/**
|
|
81
|
+
* Find an ion by the name it takes in a compound.
|
|
82
|
+
*
|
|
83
|
+
* Matched case-insensitively on BOTH sides. Lowercasing only the input looked right and failed for
|
|
84
|
+
* every ion whose name carries a Roman numeral, since "iron(iii)" never matches "iron(III)".
|
|
85
|
+
*/
|
|
86
|
+
const ionByName = (name) => {
|
|
87
|
+
const wanted = name.trim().toLowerCase();
|
|
88
|
+
return IONS.find((i) => i.name.toLowerCase() === wanted);
|
|
89
|
+
};
|
|
90
|
+
const gcd = (a, b) => b === 0 ? Math.abs(a) : gcd(b, a % b);
|
|
91
|
+
/**
|
|
92
|
+
* How many of each ion make a compound with no overall charge, in the smallest whole numbers.
|
|
93
|
+
*
|
|
94
|
+
* The lowest common multiple of the two charge sizes, divided by each. Magnesium and oxide both
|
|
95
|
+
* carry two, their lowest common multiple is two, and one of each is the answer: MgO. The crossover
|
|
96
|
+
* trick would have given Mg2O2 and needed a separate rule to cancel it down.
|
|
97
|
+
*/
|
|
98
|
+
function neutralRatio(cationCharge, anionCharge) {
|
|
99
|
+
if (cationCharge <= 0) throw new Error(`a cation carries a positive charge, not ${cationCharge}`);
|
|
100
|
+
if (anionCharge >= 0) throw new Error(`an anion carries a negative charge, not ${anionCharge}`);
|
|
101
|
+
const size = Math.abs(anionCharge);
|
|
102
|
+
const lcm = cationCharge * size / gcd(cationCharge, size);
|
|
103
|
+
return [lcm / cationCharge, lcm / size];
|
|
104
|
+
}
|
|
105
|
+
/** One ion written out with its count, bracketed when it needs to be. */
|
|
106
|
+
function writeIon(part, count) {
|
|
107
|
+
if (count === 1) return part.formula;
|
|
108
|
+
return part.polyatomic ? `(${part.formula})${count}` : `${part.formula}${count}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The compound two ions form.
|
|
112
|
+
*
|
|
113
|
+
* `ionicFormula(aluminium, oxide)` gives Al2O3; `ionicFormula(magnesium, oxide)` gives MgO, not
|
|
114
|
+
* Mg2O2; `ionicFormula(calcium, nitrate)` gives Ca(NO3)2 with the brackets that stop it being read
|
|
115
|
+
* as thirty-two oxygens.
|
|
116
|
+
*/
|
|
117
|
+
function ionicCompound(cation, anion) {
|
|
118
|
+
const [cationCount, anionCount] = neutralRatio(cation.charge, anion.charge);
|
|
119
|
+
return {
|
|
120
|
+
formula: `${writeIon(cation, cationCount)}${writeIon(anion, anionCount)}`,
|
|
121
|
+
name: `${cation.name} ${anion.name}`,
|
|
122
|
+
cation,
|
|
123
|
+
anion,
|
|
124
|
+
cationCount,
|
|
125
|
+
anionCount
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** The same, by name: `compoundOf('calcium', 'carbonate')`. */
|
|
129
|
+
function compoundOf(cationName, anionName) {
|
|
130
|
+
const cation = ionByName(cationName);
|
|
131
|
+
const anion = ionByName(anionName);
|
|
132
|
+
if (!cation) throw new Error(`no ion named "${cationName}" in this table`);
|
|
133
|
+
if (!anion) throw new Error(`no ion named "${anionName}" in this table`);
|
|
134
|
+
if (cation.charge <= 0) throw new Error(`"${cationName}" is not a cation`);
|
|
135
|
+
if (anion.charge >= 0) throw new Error(`"${anionName}" is not an anion`);
|
|
136
|
+
return ionicCompound(cation, anion);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether a proposed pair of counts makes a neutral compound, and whether it is the simplest one.
|
|
140
|
+
*
|
|
141
|
+
* Both answers matter and they are different. Mg2O2 IS neutral and is not a formula anyone writes,
|
|
142
|
+
* so a learner offering it has understood the charge balance and not the "smallest" part, and
|
|
143
|
+
* telling them only that it is wrong would hide which half they have.
|
|
144
|
+
*/
|
|
145
|
+
function assessCounts(cation, anion, cationCount, anionCount) {
|
|
146
|
+
const totalCharge = cation.charge * cationCount + anion.charge * anionCount;
|
|
147
|
+
const [wantCation, wantAnion] = neutralRatio(cation.charge, anion.charge);
|
|
148
|
+
return {
|
|
149
|
+
totalCharge,
|
|
150
|
+
neutral: totalCharge === 0,
|
|
151
|
+
simplest: cationCount === wantCation && anionCount === wantAnion
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { ANIONS, CATIONS, IONS, assessCounts, compoundOf, ionByName, ionicCompound, neutralRatio, writeIon };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Structure } from "./molecule.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/layout.d.ts
|
|
4
|
+
interface Point {
|
|
5
|
+
readonly x: number;
|
|
6
|
+
readonly y: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Where every atom goes, in bond lengths, with y downward.
|
|
10
|
+
*
|
|
11
|
+
* A disconnected structure lays each piece out and offsets it, so nothing overlaps: that happens
|
|
12
|
+
* when a reaction has produced a small molecule alongside the main one.
|
|
13
|
+
*/
|
|
14
|
+
declare function layout(structure: Structure): Map<number, Point>;
|
|
15
|
+
interface LaidOut {
|
|
16
|
+
readonly positions: ReadonlyMap<number, Point>;
|
|
17
|
+
/** Corners of the drawing, in bond lengths, so a caller can centre and scale it. */
|
|
18
|
+
readonly bounds: {
|
|
19
|
+
readonly minX: number;
|
|
20
|
+
readonly minY: number;
|
|
21
|
+
readonly maxX: number;
|
|
22
|
+
readonly maxY: number;
|
|
23
|
+
};
|
|
24
|
+
/** Ring systems, in bonded order, so an aromatic circle can be drawn in the right place. */
|
|
25
|
+
readonly rings: readonly (readonly number[])[];
|
|
26
|
+
}
|
|
27
|
+
/** The layout plus everything a figure needs to place it on a page. */
|
|
28
|
+
declare function layoutOf(structure: Structure): LaidOut;
|
|
29
|
+
/** The centre of a ring, for the delocalisation circle. */
|
|
30
|
+
declare function ringCentre(ring: readonly number[], positions: ReadonlyMap<number, Point>): Point;
|
|
31
|
+
/**
|
|
32
|
+
* Several free directions at an atom, for drawing its hydrogens in a displayed formula.
|
|
33
|
+
*
|
|
34
|
+
* Returned widest-gap first and spread apart, so a CH2 puts its two hydrogens on opposite sides
|
|
35
|
+
* rather than side by side where they would touch.
|
|
36
|
+
*/
|
|
37
|
+
declare function freeDirections(structure: Structure, id: number, positions: ReadonlyMap<number, Point>, count: number): Point[];
|
|
38
|
+
/**
|
|
39
|
+
* A free direction at an atom, for drawing an open valence or an implicit-hydrogen label.
|
|
40
|
+
*
|
|
41
|
+
* Returns the lattice direction furthest from every bond the atom already has, which is where a
|
|
42
|
+
* chemist would put it.
|
|
43
|
+
*/
|
|
44
|
+
declare function freeDirection(structure: Structure, id: number, positions: ReadonlyMap<number, Point>): Point;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { LaidOut, Point, freeDirection, freeDirections, layout, layoutOf, ringCentre };
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { bondsAt, otherEnd } from "./molecule.mjs";
|
|
2
|
+
import { ringSystems } from "./skeleton.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/chem/layout.ts
|
|
5
|
+
/**
|
|
6
|
+
* layout, where every atom of a structure goes on the page.
|
|
7
|
+
*
|
|
8
|
+
* WHAT THIS IS FOR. Drawing a molecule from a graph needs coordinates, and until now the corpus got
|
|
9
|
+
* them by having an author place a chain by hand. That works for a hand-written structure and cannot
|
|
10
|
+
* work for a computed one: a polymer repeat unit is built by the kernel, and nobody is there to
|
|
11
|
+
* place it. It also cannot express the shape that broke the polymer lab, a ring with the chain
|
|
12
|
+
* running THROUGH it, because a hand-placed chain is a chain or a ring and never both.
|
|
13
|
+
*
|
|
14
|
+
* So positions are computed. Coordinates come back in BOND LENGTHS, with y increasing downward to
|
|
15
|
+
* match SVG, and the caller scales and centres them.
|
|
16
|
+
*
|
|
17
|
+
* ## The lattice, and why 30 degrees
|
|
18
|
+
*
|
|
19
|
+
* Every bond points along one of twelve directions, 30 degrees apart. That single choice gives both
|
|
20
|
+
* shapes chemistry needs: a zig-zag chain is bonds alternating at plus and minus 30 degrees from
|
|
21
|
+
* horizontal, which is a 120 degree bond angle, and a hexagon's edges are at 30 plus multiples of
|
|
22
|
+
* 60. A finer lattice would let bonds land at angles no structure drawing uses; a coarser one cannot
|
|
23
|
+
* draw both shapes at once.
|
|
24
|
+
*
|
|
25
|
+
* ## Rings are placed first, and everything else grows off them
|
|
26
|
+
*
|
|
27
|
+
* A ring's geometry is fixed and a chain's is not, so the rings are laid down as regular polygons
|
|
28
|
+
* and the acyclic atoms are then walked outward from whatever is already placed. Doing it the other
|
|
29
|
+
* way round means discovering halfway along a chain that it has to close into a hexagon, and moving
|
|
30
|
+
* everything already placed.
|
|
31
|
+
*
|
|
32
|
+
* ## How a direction is chosen
|
|
33
|
+
*
|
|
34
|
+
* From the direction the walk arrived, turn 60 degrees, alternating which way so a chain zig-zags
|
|
35
|
+
* instead of curling into a circle. If that position is occupied, try the other way, then wider
|
|
36
|
+
* angles. The occupancy test is what stops a branch being drawn on top of the chain it hangs from,
|
|
37
|
+
* and it is why this is a search rather than a formula.
|
|
38
|
+
*
|
|
39
|
+
* Pure: no React, no pixels.
|
|
40
|
+
*/
|
|
41
|
+
/** Twelve directions, 30 degrees apart, as unit vectors. */
|
|
42
|
+
const STEP = Math.PI / 6;
|
|
43
|
+
const DIRECTIONS = Array.from({ length: 12 }, (_, k) => ({
|
|
44
|
+
x: Math.cos(k * STEP),
|
|
45
|
+
y: Math.sin(k * STEP)
|
|
46
|
+
}));
|
|
47
|
+
/** The lattice direction closest to an angle, as an index into {@link DIRECTIONS}. */
|
|
48
|
+
const snap = (angle) => (Math.round(angle / STEP) % 12 + 12) % 12;
|
|
49
|
+
const angleOf = (from, to) => Math.atan2(to.y - from.y, to.x - from.x);
|
|
50
|
+
/** Two atoms are on top of each other if they are closer than this, in bond lengths. */
|
|
51
|
+
const TOO_CLOSE = .55;
|
|
52
|
+
const linked = (structure, id) => bondsAt(structure, id).map((b) => otherEnd(b, id));
|
|
53
|
+
/**
|
|
54
|
+
* How many atoms lie beyond a neighbour, without coming back through this one.
|
|
55
|
+
*
|
|
56
|
+
* Used to decide which way the CHAIN goes. At a branching atom the neighbour leading to the most
|
|
57
|
+
* atoms is the backbone and takes the zig-zag; the small ones hanging off it are substituents and
|
|
58
|
+
* are placed wherever there is room. Without this the order the bonds happen to be declared in
|
|
59
|
+
* decides, so ethyl ethanoate gave its chain direction to the carbonyl oxygen and drew itself
|
|
60
|
+
* vertically with the chain hanging off the side.
|
|
61
|
+
*/
|
|
62
|
+
function reachBeyond(structure, from, through) {
|
|
63
|
+
const seen = new Set([from, through]);
|
|
64
|
+
const stack = [through];
|
|
65
|
+
let count = 0;
|
|
66
|
+
while (stack.length) {
|
|
67
|
+
const at = stack.pop();
|
|
68
|
+
count += 1;
|
|
69
|
+
for (const n of linked(structure, at)) {
|
|
70
|
+
if (seen.has(n)) continue;
|
|
71
|
+
seen.add(n);
|
|
72
|
+
stack.push(n);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return count;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Where every atom goes, in bond lengths, with y downward.
|
|
79
|
+
*
|
|
80
|
+
* A disconnected structure lays each piece out and offsets it, so nothing overlaps: that happens
|
|
81
|
+
* when a reaction has produced a small molecule alongside the main one.
|
|
82
|
+
*/
|
|
83
|
+
function layout(structure) {
|
|
84
|
+
const placed = /* @__PURE__ */ new Map();
|
|
85
|
+
const rings = ringSystems(structure);
|
|
86
|
+
const occupied = (p, ignore) => [...placed].some(([id, q]) => id !== ignore && Math.hypot(p.x - q.x, p.y - q.y) < TOO_CLOSE);
|
|
87
|
+
/**
|
|
88
|
+
* Put a ring down as a regular polygon, turned so its substituents run across the page.
|
|
89
|
+
*
|
|
90
|
+
* A bare ring gets a vertex at the top, which is how benzene is printed. A SUBSTITUTED ring is
|
|
91
|
+
* turned so the first substituted vertex sits on the left, which for a para-disubstituted ring
|
|
92
|
+
* puts the other one on the right and lets the chain run horizontally through it. Without that,
|
|
93
|
+
* terephthalic acid came out six bond lengths tall and two wide, with its two carboxyls stacked.
|
|
94
|
+
*/
|
|
95
|
+
const placeRing = (ring, centre) => {
|
|
96
|
+
const n = ring.length;
|
|
97
|
+
const radius = 1 / (2 * Math.sin(Math.PI / n));
|
|
98
|
+
const ordered = orderRing(structure, ring);
|
|
99
|
+
const inRing = new Set(ordered);
|
|
100
|
+
const substituted = ordered.findIndex((id) => linked(structure, id).some((nb) => !inRing.has(nb)));
|
|
101
|
+
const turn = substituted >= 0 ? Math.PI - substituted * 2 * Math.PI / n : -Math.PI / 2;
|
|
102
|
+
ordered.forEach((id, i) => {
|
|
103
|
+
const a = turn + i * 2 * Math.PI / n;
|
|
104
|
+
placed.set(id, {
|
|
105
|
+
x: centre.x + radius * Math.cos(a),
|
|
106
|
+
y: centre.y + radius * Math.sin(a)
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
if (rings[0]?.length) placeRing(rings[0], {
|
|
111
|
+
x: 0,
|
|
112
|
+
y: 0
|
|
113
|
+
});
|
|
114
|
+
else if (structure.atoms[0]) placed.set(structure.atoms[0].id, {
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0
|
|
117
|
+
});
|
|
118
|
+
/** Directions already used by bonds at an atom, as angles. */
|
|
119
|
+
const usedAngles = (id) => linked(structure, id).filter((n) => placed.has(n)).map((n) => angleOf(placed.get(id), placed.get(n)));
|
|
120
|
+
/**
|
|
121
|
+
* The direction a bond leaves an atom in.
|
|
122
|
+
*
|
|
123
|
+
* CONTINUING A CHAIN is a reflection, not a rotation. A zig-zag is bonds alternating between plus
|
|
124
|
+
* and minus 30 degrees, which is exactly "mirror the direction you arrived on in the horizontal",
|
|
125
|
+
* so the chain travels along the page instead of turning. Rotating by a fixed 60 degrees each
|
|
126
|
+
* step was the first version and it curls: six carbons came out as a hexagon the molecule does not
|
|
127
|
+
* have, and hexanedioic acid was drawn taller than it was wide.
|
|
128
|
+
*
|
|
129
|
+
* A BRANCH has no direction to reflect, so it takes the lattice direction furthest from every bond
|
|
130
|
+
* the atom already has, which is where a chemist puts it and which is also what keeps a
|
|
131
|
+
* substituent off a ring's own edges.
|
|
132
|
+
*/
|
|
133
|
+
const directionFrom = (id, incoming) => {
|
|
134
|
+
const used = usedAngles(id);
|
|
135
|
+
if (incoming !== void 0 && used.length <= 1) {
|
|
136
|
+
const candidate = DIRECTIONS[snap(snap(-incoming) === snap(incoming) ? incoming + STEP : -incoming)];
|
|
137
|
+
if (!occupied({
|
|
138
|
+
x: placed.get(id).x + candidate.x,
|
|
139
|
+
y: placed.get(id).y + candidate.y
|
|
140
|
+
})) return candidate;
|
|
141
|
+
}
|
|
142
|
+
let best;
|
|
143
|
+
let bestGap = -1;
|
|
144
|
+
for (const d of DIRECTIONS) {
|
|
145
|
+
const here = placed.get(id);
|
|
146
|
+
if (occupied({
|
|
147
|
+
x: here.x + d.x,
|
|
148
|
+
y: here.y + d.y
|
|
149
|
+
})) continue;
|
|
150
|
+
const a = Math.atan2(d.y, d.x);
|
|
151
|
+
const gap = used.length ? Math.min(...used.map((u) => Math.abs(Math.atan2(Math.sin(a - u), Math.cos(a - u))))) : Math.PI;
|
|
152
|
+
if (gap > bestGap) {
|
|
153
|
+
bestGap = gap;
|
|
154
|
+
best = d;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return best ?? DIRECTIONS[0];
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Walk outward from everything placed, breadth first, carrying the direction each step arrived on.
|
|
161
|
+
*
|
|
162
|
+
* Breadth first rather than depth first so a long chain is laid down before its branches, which
|
|
163
|
+
* means the branches see the chain already in place and step around it.
|
|
164
|
+
*/
|
|
165
|
+
const grow = () => {
|
|
166
|
+
const queue = [...placed.keys()].map((id) => ({
|
|
167
|
+
id,
|
|
168
|
+
incoming: void 0
|
|
169
|
+
}));
|
|
170
|
+
let progress = false;
|
|
171
|
+
const seen = new Set(queue.map((q) => q.id));
|
|
172
|
+
const flat = structure.atoms.length <= 2;
|
|
173
|
+
let seeded = placed.size > 1;
|
|
174
|
+
while (queue.length) {
|
|
175
|
+
const { id, incoming } = queue.shift();
|
|
176
|
+
let continued = false;
|
|
177
|
+
const outward = [...linked(structure, id)].sort((a, b) => reachBeyond(structure, id, b) - reachBeyond(structure, id, a));
|
|
178
|
+
for (const next of outward) {
|
|
179
|
+
if (placed.has(next)) {
|
|
180
|
+
if (!seen.has(next)) {
|
|
181
|
+
seen.add(next);
|
|
182
|
+
queue.push({
|
|
183
|
+
id: next,
|
|
184
|
+
incoming: angleOf(placed.get(id), placed.get(next))
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const direction = !seeded ? DIRECTIONS[flat ? 0 : snap(STEP)] : directionFrom(id, continued ? void 0 : incoming);
|
|
190
|
+
seeded = true;
|
|
191
|
+
continued = true;
|
|
192
|
+
const here = placed.get(id);
|
|
193
|
+
placed.set(next, {
|
|
194
|
+
x: here.x + direction.x,
|
|
195
|
+
y: here.y + direction.y
|
|
196
|
+
});
|
|
197
|
+
seen.add(next);
|
|
198
|
+
queue.push({
|
|
199
|
+
id: next,
|
|
200
|
+
incoming: angleOf(here, placed.get(next))
|
|
201
|
+
});
|
|
202
|
+
progress = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return progress;
|
|
206
|
+
};
|
|
207
|
+
let changed = true;
|
|
208
|
+
while (changed) {
|
|
209
|
+
changed = grow();
|
|
210
|
+
for (const ring of rings.slice(1)) {
|
|
211
|
+
if (ring.every((id) => placed.has(id))) continue;
|
|
212
|
+
const anchor = ring.find((id) => placed.has(id));
|
|
213
|
+
if (anchor === void 0) continue;
|
|
214
|
+
const here = placed.get(anchor);
|
|
215
|
+
const away = usedAngles(anchor);
|
|
216
|
+
const outward = away.length ? away.reduce((a, b) => a + b, 0) / away.length + Math.PI : 0;
|
|
217
|
+
const radius = 1 / (2 * Math.sin(Math.PI / ring.length));
|
|
218
|
+
placeRing(ring, {
|
|
219
|
+
x: here.x + radius * Math.cos(outward),
|
|
220
|
+
y: here.y + radius * Math.sin(outward)
|
|
221
|
+
});
|
|
222
|
+
changed = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const missing = structure.atoms.filter((a) => !placed.has(a.id));
|
|
226
|
+
if (missing.length) {
|
|
227
|
+
const right = Math.max(...[...placed.values()].map((p) => p.x), 0) + 2;
|
|
228
|
+
missing.forEach((atom, i) => placed.set(atom.id, {
|
|
229
|
+
x: right + i,
|
|
230
|
+
y: 0
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
return placed;
|
|
234
|
+
}
|
|
235
|
+
/** Put a ring's atoms in bonded order, so a polygon can be walked round. */
|
|
236
|
+
function orderRing(structure, ring) {
|
|
237
|
+
const inRing = new Set(ring);
|
|
238
|
+
const ordered = [ring[0]];
|
|
239
|
+
const seen = new Set(ordered);
|
|
240
|
+
while (ordered.length < ring.length) {
|
|
241
|
+
const at = ordered[ordered.length - 1];
|
|
242
|
+
const next = linked(structure, at).find((n) => inRing.has(n) && !seen.has(n));
|
|
243
|
+
if (next === void 0) break;
|
|
244
|
+
seen.add(next);
|
|
245
|
+
ordered.push(next);
|
|
246
|
+
}
|
|
247
|
+
return ordered;
|
|
248
|
+
}
|
|
249
|
+
/** The layout plus everything a figure needs to place it on a page. */
|
|
250
|
+
function layoutOf(structure) {
|
|
251
|
+
const positions = layout(structure);
|
|
252
|
+
const xs = [...positions.values()].map((p) => p.x);
|
|
253
|
+
const ys = [...positions.values()].map((p) => p.y);
|
|
254
|
+
return {
|
|
255
|
+
positions,
|
|
256
|
+
bounds: {
|
|
257
|
+
minX: Math.min(...xs, 0),
|
|
258
|
+
minY: Math.min(...ys, 0),
|
|
259
|
+
maxX: Math.max(...xs, 0),
|
|
260
|
+
maxY: Math.max(...ys, 0)
|
|
261
|
+
},
|
|
262
|
+
rings: ringSystems(structure).map((ring) => orderRing(structure, ring))
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/** The centre of a ring, for the delocalisation circle. */
|
|
266
|
+
function ringCentre(ring, positions) {
|
|
267
|
+
const pts = ring.map((id) => positions.get(id)).filter(Boolean);
|
|
268
|
+
return {
|
|
269
|
+
x: pts.reduce((s, p) => s + p.x, 0) / pts.length,
|
|
270
|
+
y: pts.reduce((s, p) => s + p.y, 0) / pts.length
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Several free directions at an atom, for drawing its hydrogens in a displayed formula.
|
|
275
|
+
*
|
|
276
|
+
* Returned widest-gap first and spread apart, so a CH2 puts its two hydrogens on opposite sides
|
|
277
|
+
* rather than side by side where they would touch.
|
|
278
|
+
*/
|
|
279
|
+
function freeDirections(structure, id, positions, count) {
|
|
280
|
+
const here = positions.get(id);
|
|
281
|
+
if (!here || count <= 0) return [];
|
|
282
|
+
const taken = [...linked(structure, id).map((n) => positions.get(n)).filter((p) => Boolean(p)).map((p) => angleOf(here, p))];
|
|
283
|
+
const out = [];
|
|
284
|
+
for (let k = 0; k < count; k += 1) {
|
|
285
|
+
let best = DIRECTIONS[0];
|
|
286
|
+
let bestGap = -1;
|
|
287
|
+
for (const d of DIRECTIONS) {
|
|
288
|
+
const a = Math.atan2(d.y, d.x);
|
|
289
|
+
const gap = taken.length ? Math.min(...taken.map((u) => Math.abs(Math.atan2(Math.sin(a - u), Math.cos(a - u))))) : Math.PI;
|
|
290
|
+
if (gap > bestGap) {
|
|
291
|
+
bestGap = gap;
|
|
292
|
+
best = d;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
out.push(best);
|
|
296
|
+
taken.push(Math.atan2(best.y, best.x));
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* A free direction at an atom, for drawing an open valence or an implicit-hydrogen label.
|
|
302
|
+
*
|
|
303
|
+
* Returns the lattice direction furthest from every bond the atom already has, which is where a
|
|
304
|
+
* chemist would put it.
|
|
305
|
+
*/
|
|
306
|
+
function freeDirection(structure, id, positions) {
|
|
307
|
+
const here = positions.get(id);
|
|
308
|
+
if (!here) return {
|
|
309
|
+
x: 1,
|
|
310
|
+
y: 0
|
|
311
|
+
};
|
|
312
|
+
const used = linked(structure, id).map((n) => positions.get(n)).filter((p) => Boolean(p)).map((p) => angleOf(here, p));
|
|
313
|
+
if (used.length === 0) return {
|
|
314
|
+
x: 1,
|
|
315
|
+
y: 0
|
|
316
|
+
};
|
|
317
|
+
const all = [...positions.values()];
|
|
318
|
+
const centroidX = all.reduce((sum, p) => sum + p.x, 0) / all.length;
|
|
319
|
+
const outward = here.x >= centroidX ? {
|
|
320
|
+
x: 1,
|
|
321
|
+
y: 0
|
|
322
|
+
} : {
|
|
323
|
+
x: -1,
|
|
324
|
+
y: 0
|
|
325
|
+
};
|
|
326
|
+
const outwardAngle = Math.atan2(outward.y, outward.x);
|
|
327
|
+
if (!used.some((u) => Math.abs(Math.atan2(Math.sin(outwardAngle - u), Math.cos(outwardAngle - u))) < STEP - 1e-6)) return outward;
|
|
328
|
+
let best = DIRECTIONS[0];
|
|
329
|
+
let bestGap = -1;
|
|
330
|
+
for (const d of DIRECTIONS) {
|
|
331
|
+
const a = Math.atan2(d.y, d.x);
|
|
332
|
+
const gap = Math.min(...used.map((u) => Math.abs(Math.atan2(Math.sin(a - u), Math.cos(a - u)))));
|
|
333
|
+
if (gap > bestGap) {
|
|
334
|
+
bestGap = gap;
|
|
335
|
+
best = d;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return best;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
export { freeDirection, freeDirections, layout, layoutOf, ringCentre };
|