Skip to content

Commit 3f18420

Browse files
feat: Implement AtomicRadii Integration for Enhanced Element Properties (#11)
## Summary Implements atomic radii integration across the application, displaying all four atomic radius types (empirical, calculated, van der Waals, covalent) from the `AtomicRadii` database table. This enhancement also enables simultaneous display of element and nuclide details on query pages. Closes #4 ## Changes ### Core Implementation 1. **Query Service** (`src/services/queryService.ts`) - Added `getAtomicRadii()` function to fetch all four radius types from the AtomicRadii table - Returns `AtomicRadiiData` with null handling for missing values 2. **Type Definitions** (`src/types/index.ts`) - Added `AtomicRadiiData` interface with four radius properties 3. **Component Updates** - **ShowElementData** (`src/pages/ShowElementData.tsx`) - Replaced single ARadius field with dedicated Atomic Radii card - Changed layout to 3-column grid (Thermal | Physical | Atomic Radii) - Added explanatory tooltips for each radius type - **ElementDetailsCard** (`src/components/ElementDetailsCard.tsx`) - Added `atomicRadii` prop to accept atomic radii data - Removed old single `ARadius` field from Atomic Properties - Added new Atomic Radii section with all 4 types - Updated layout to 3-column grid for better organization 4. **Query Pages** (FusionQuery, FissionQuery, TwoToTwoQuery) - Added atomic radii fetching and display for element details - **Breaking change**: Element and nuclide details now show simultaneously instead of being mutually exclusive - Users can now pin both an element and a nuclide at the same time - Single placeholder card still appears when neither is selected ### Database Updates 5. **Database Schema** (`public/parkhomov.db`) - Cleaned up "null" string values in AtomicRadii table (replaced with SQL NULL) - Updated database version from 1.2.0 to 1.2.1 - Updated checksum in metadata file 6. **Documentation** - Updated all database size references from 154 MB to 161 MB across: - README.md - CLAUDE.md - CONTRIBUTING.md - And 6 other documentation files ## UI Changes ### ShowElementData Page **Before**: Single "Atomic Radius" field in Atomic Properties section **After**: Dedicated "Atomic Radii" card showing all 4 types with explanations ### Query Pages (Fusion/Fission/TwoToTwo) **Before**: Element OR nuclide details (mutually exclusive) **After**: Element AND nuclide details can be displayed simultaneously ## Technical Details ### Atomic Radii Display Format ``` Atomic Radii ├── Empirical: 134 pm ├── Calculated: 128 pm ├── Van der Waals: 192 pm └── Covalent: 77 pm ℹ️ Explanations: • Empirical: Experimentally measured • Calculated: Theoretically derived • Van der Waals: Non-bonded atom radius • Covalent: Bonded atom radius ``` ### Null Handling - Gracefully handles missing radius values (some elements lack certain types) - Only displays available radius types - Shows "N/A" or omits missing fields ### Database Impact - Size: 154 MB → 161 MB (+7 MB from schema cleanup) - Utilizes existing AtomicRadii table (4 KB, 94 rows) - No performance impact on queries ## Testing ✅ Build passes successfully ✅ TypeScript compilation succeeds ✅ All query pages tested in browser ✅ ShowElementData page displays correctly ✅ Atomic radii show for elements that have data ✅ Missing data handled gracefully ✅ Element and nuclide details display simultaneously on query pages ## Breaking Changes ⚠️ **Query Pages Behavior Change**: - Previously: Clicking an element would unpin any selected nuclide (and vice versa) - Now: Both element and nuclide can be pinned simultaneously - Impact: Users can now see more information at once - this is an enhancement, not a regression ## Screenshots _Would add screenshots here showing:_ 1. ShowElementData page with new Atomic Radii card 2. Query page showing both element and nuclide details simultaneously 3. Atomic radii tooltips in action ## Database Version - **Previous**: 1.2.0 - **Current**: 1.2.1 - **Checksum**: dc004bd2d9ce6a204aa1d05d05b57ca4 - **Size**: 161,046,528 bytes ## Related - Issue #4: Implement AtomicRadii Integration - Database stored in S3: `s3://db.lenr.academy/1.2.1/parkhomov.db`
1 parent 8849477 commit 3f18420

11 files changed

Lines changed: 326 additions & 166 deletions

e2e/tests/element-data.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,48 @@ test.describe('Element Data Page', () => {
131131
await expect(electronegativity).toBeVisible();
132132
});
133133

134+
test('should display atomic radii data', async ({ page }) => {
135+
// Navigate to Gold (has all 4 radius types)
136+
await page.goto('/element-data?Z=79');
137+
await waitForDatabaseReady(page);
138+
139+
// Should show "Atomic Radii" heading
140+
await expect(page.getByText(/Atomic Radii \(pm\)/i)).toBeVisible();
141+
142+
// Should show all 4 radius types with actual values (Gold has all 4)
143+
// Check for the grid containing radii data
144+
const radiiGrid = page.locator('div:has-text("Empirical:")').first();
145+
await expect(radiiGrid).toBeVisible();
146+
147+
// Verify the data grid contains the radius labels and values
148+
await expect(page.locator('dt:has-text("Empirical:")')).toBeVisible();
149+
await expect(page.locator('dt:has-text("Calculated:")')).toBeVisible();
150+
await expect(page.locator('dt:has-text("Van der Waals:")')).toBeVisible();
151+
await expect(page.locator('dt:has-text("Covalent:")')).toBeVisible();
152+
153+
// Should show explanatory help text
154+
await expect(page.getByText(/Measured.*Theoretical/i)).toBeVisible();
155+
});
156+
157+
test('should handle partial atomic radii data', async ({ page }) => {
158+
// Navigate to Carbon (has 3 out of 4 radius types)
159+
await page.goto('/element-data?Z=6');
160+
await waitForDatabaseReady(page);
161+
162+
// Should show "Atomic Radii (pm)" heading
163+
await expect(page.getByText(/Atomic Radii \(pm\)/i)).toBeVisible();
164+
165+
// Carbon should have 3 types of radii displayed
166+
// Just verify that the Atomic Radii section contains the key labels
167+
const atomicRadiiSection = page.locator('text=Atomic Radii (pm)').locator('..').locator('..');
168+
await expect(atomicRadiiSection).toContainText('Empirical:');
169+
await expect(atomicRadiiSection).toContainText('Calculated:');
170+
await expect(atomicRadiiSection).toContainText('Van der Waals:');
171+
172+
// Should show the explanatory help text
173+
await expect(page.getByText(/Measured.*Theoretical/i)).toBeVisible();
174+
});
175+
134176
test('should update URL when selecting element', async ({ page }) => {
135177
// Start at base page
136178
await page.goto('/element-data');

e2e/tests/fission-query.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ test.describe('Fission Query Page', () => {
163163
}
164164
});
165165

166-
test('should deselect element when selecting nuclide (and vice versa)', async ({ page }) => {
166+
test('should allow both element and nuclide to be pinned simultaneously', async ({ page }) => {
167167
// Wait for default query results to load
168168
await page.waitForFunction(
169169
() => document.querySelector('table') !== null,
@@ -184,16 +184,16 @@ test.describe('Fission Query Page', () => {
184184
// Verify nuclide is now pinned
185185
await expect(nuclideCard).toHaveClass(/ring-2.*ring-blue-400/);
186186

187-
// Verify element is NO LONGER pinned
188-
await expect(elementCard).not.toHaveClass(/ring-2.*ring-blue-400/);
187+
// Verify element is STILL pinned (both can be pinned simultaneously)
188+
await expect(elementCard).toHaveClass(/ring-2.*ring-blue-400/);
189189

190-
// Click element again
190+
// Click element again to unpin it
191191
await elementCard.click();
192192

193-
// Verify element is pinned again
194-
await expect(elementCard).toHaveClass(/ring-2.*ring-blue-400/);
193+
// Verify element is no longer pinned
194+
await expect(elementCard).not.toHaveClass(/ring-2.*ring-blue-400/);
195195

196-
// Verify nuclide is NO LONGER pinned
197-
await expect(nuclideCard).not.toHaveClass(/ring-2.*ring-blue-400/);
196+
// Verify nuclide is STILL pinned
197+
await expect(nuclideCard).toHaveClass(/ring-2.*ring-blue-400/);
198198
});
199199
});

e2e/tests/fusion-query.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ test.describe('Fusion Query Page', () => {
293293
}
294294
});
295295

296-
test('should deselect element when selecting nuclide (and vice versa)', async ({ page }) => {
296+
test('should allow both element and nuclide to be pinned simultaneously', async ({ page }) => {
297297
// Wait for default query results to load
298298
await page.waitForFunction(
299299
() => document.querySelector('table') !== null,
@@ -314,17 +314,17 @@ test.describe('Fusion Query Page', () => {
314314
// Verify nuclide is now pinned
315315
await expect(nuclideCard).toHaveClass(/ring-2.*ring-blue-400/);
316316

317-
// Verify element is NO LONGER pinned
318-
await expect(elementCard).not.toHaveClass(/ring-2.*ring-blue-400/);
317+
// Verify element is STILL pinned (both can be pinned simultaneously)
318+
await expect(elementCard).toHaveClass(/ring-2.*ring-blue-400/);
319319

320-
// Click element again
320+
// Click element again to unpin it
321321
await elementCard.click();
322322

323-
// Verify element is pinned again
324-
await expect(elementCard).toHaveClass(/ring-2.*ring-blue-400/);
323+
// Verify element is no longer pinned
324+
await expect(elementCard).not.toHaveClass(/ring-2.*ring-blue-400/);
325325

326-
// Verify nuclide is NO LONGER pinned
327-
await expect(nuclideCard).not.toHaveClass(/ring-2.*ring-blue-400/);
326+
// Verify nuclide is STILL pinned
327+
await expect(nuclideCard).toHaveClass(/ring-2.*ring-blue-400/);
328328
});
329329
});
330330

e2e/tests/twotwo-query.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ test.describe('Two-to-Two Query Page', () => {
181181
await expect(limitInput).toHaveValue('25');
182182
});
183183

184-
test('should deselect element when selecting nuclide (and vice versa)', async ({ page }) => {
184+
test('should allow both element and nuclide to be pinned simultaneously', async ({ page }) => {
185185
// Wait for default query results to load
186186
await page.waitForFunction(
187187
() => document.querySelector('table') !== null,
@@ -202,17 +202,17 @@ test.describe('Two-to-Two Query Page', () => {
202202
// Verify nuclide is now pinned
203203
await expect(nuclideCard).toHaveClass(/ring-2.*ring-blue-400/);
204204

205-
// Verify element is NO LONGER pinned
206-
await expect(elementCard).not.toHaveClass(/ring-2.*ring-blue-400/);
205+
// Verify element is STILL pinned (both can be pinned simultaneously)
206+
await expect(elementCard).toHaveClass(/ring-2.*ring-blue-400/);
207207

208-
// Click element again
208+
// Click element again to unpin it
209209
await elementCard.click();
210210

211-
// Verify element is pinned again
212-
await expect(elementCard).toHaveClass(/ring-2.*ring-blue-400/);
211+
// Verify element is no longer pinned
212+
await expect(elementCard).not.toHaveClass(/ring-2.*ring-blue-400/);
213213

214-
// Verify nuclide is NO LONGER pinned
215-
await expect(nuclideCard).not.toHaveClass(/ring-2.*ring-blue-400/);
214+
// Verify nuclide is STILL pinned
215+
await expect(nuclideCard).toHaveClass(/ring-2.*ring-blue-400/);
216216
});
217217
});
218218

src/components/ElementDetailsCard.tsx

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { X } from 'lucide-react'
2-
import type { Element } from '../types'
2+
import type { Element, AtomicRadiiData } from '../types'
33

44
interface ElementDetailsCardProps {
55
element: Element | null
6+
atomicRadii?: AtomicRadiiData | null
67
onClose?: () => void
78
}
89

9-
export default function ElementDetailsCard({ element, onClose }: ElementDetailsCardProps) {
10+
export default function ElementDetailsCard({ element, atomicRadii, onClose }: ElementDetailsCardProps) {
1011
if (!element) return null
1112

1213
return (
@@ -65,12 +66,6 @@ export default function ElementDetailsCard({ element, onClose }: ElementDetailsC
6566
Atomic Properties
6667
</h3>
6768
<dl className="space-y-2 text-sm">
68-
{typeof element.ARadius === 'number' && (
69-
<div className="flex justify-between">
70-
<dt className="text-gray-600 dark:text-gray-400">Atomic Radius:</dt>
71-
<dd className="font-medium text-gray-900 dark:text-gray-100">{element.ARadius} pm</dd>
72-
</div>
73-
)}
7469
{typeof element.Valence === 'number' && (
7570
<div className="flex justify-between">
7671
<dt className="text-gray-600 dark:text-gray-400">Valence:</dt>
@@ -117,7 +112,7 @@ export default function ElementDetailsCard({ element, onClose }: ElementDetailsC
117112
</div>
118113
</div>
119114

120-
<div className="grid md:grid-cols-2 gap-6">
115+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
121116
<div>
122117
<h3 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm uppercase tracking-wide">
123118
Thermal Properties
@@ -195,6 +190,44 @@ export default function ElementDetailsCard({ element, onClose }: ElementDetailsC
195190
)}
196191
</dl>
197192
</div>
193+
194+
{atomicRadii && (
195+
<div>
196+
<h3 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm uppercase tracking-wide">
197+
Atomic Radii (pm)
198+
</h3>
199+
<dl className="space-y-2 text-sm">
200+
{atomicRadii.empirical !== null && (
201+
<div className="flex justify-between">
202+
<dt className="text-gray-600 dark:text-gray-400">Empirical:</dt>
203+
<dd className="font-medium text-gray-900 dark:text-gray-100">{atomicRadii.empirical} pm</dd>
204+
</div>
205+
)}
206+
{atomicRadii.calculated !== null && (
207+
<div className="flex justify-between">
208+
<dt className="text-gray-600 dark:text-gray-400">Calculated:</dt>
209+
<dd className="font-medium text-gray-900 dark:text-gray-100">{atomicRadii.calculated} pm</dd>
210+
</div>
211+
)}
212+
{atomicRadii.vanDerWaals !== null && (
213+
<div className="flex justify-between">
214+
<dt className="text-gray-600 dark:text-gray-400">Van der Waals:</dt>
215+
<dd className="font-medium text-gray-900 dark:text-gray-100">{atomicRadii.vanDerWaals} pm</dd>
216+
</div>
217+
)}
218+
{atomicRadii.covalent !== null && (
219+
<div className="flex justify-between">
220+
<dt className="text-gray-600 dark:text-gray-400">Covalent:</dt>
221+
<dd className="font-medium text-gray-900 dark:text-gray-100">{atomicRadii.covalent} pm</dd>
222+
</div>
223+
)}
224+
</dl>
225+
<div className="mt-3 p-2 bg-gray-50 dark:bg-gray-800 rounded text-xs text-gray-600 dark:text-gray-400">
226+
<strong>Empirical:</strong> Measured • <strong>Calculated:</strong> Theoretical<br />
227+
<strong>Van der Waals:</strong> Non-bonded • <strong>Covalent:</strong> Bonded atoms
228+
</div>
229+
</div>
230+
)}
198231
</div>
199232
</div>
200233
)

src/pages/FissionQuery.tsx

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useState, useEffect } from 'react'
22
import { Download, Info, Loader, Eye, EyeOff } from 'lucide-react'
33
import { useSearchParams } from 'react-router-dom'
4-
import type { FissionReaction, QueryFilter, Element, Nuclide } from '../types'
4+
import type { FissionReaction, QueryFilter, Element, Nuclide, AtomicRadiiData } from '../types'
55
import { useDatabase } from '../contexts/DatabaseContext'
6-
import { queryFission, getAllElements, getElementBySymbol, getNuclideBySymbol } from '../services/queryService'
6+
import { queryFission, getAllElements, getElementBySymbol, getNuclideBySymbol, getAtomicRadii } from '../services/queryService'
77
import PeriodicTableSelector from '../components/PeriodicTableSelector'
88
import ElementDetailsCard from '../components/ElementDetailsCard'
99
import NuclideDetailsCard from '../components/NuclideDetailsCard'
@@ -91,6 +91,7 @@ export default function FissionQuery() {
9191
const [pinnedElement, setPinnedElement] = useState(false)
9292
const [selectedElementDetails, setSelectedElementDetails] = useState<Element | null>(null)
9393
const [selectedNuclideDetails, setSelectedNuclideDetails] = useState<Nuclide | null>(null)
94+
const [selectedElementRadii, setSelectedElementRadii] = useState<AtomicRadiiData | null>(null)
9495

9596
// Load elements when database is ready
9697
useEffect(() => {
@@ -111,30 +112,34 @@ export default function FissionQuery() {
111112
if (!db) {
112113
setSelectedElementDetails(null)
113114
setSelectedNuclideDetails(null)
115+
setSelectedElementRadii(null)
114116
return
115117
}
116118

117-
// Check if element is pinned
119+
// Fetch element details if pinned
118120
if (pinnedElement && highlightedElement) {
119121
const elementDetails = getElementBySymbol(db, highlightedElement)
120122
setSelectedElementDetails(elementDetails)
121-
setSelectedNuclideDetails(null)
122-
return
123+
if (elementDetails) {
124+
const radiiData = getAtomicRadii(db, elementDetails.Z)
125+
setSelectedElementRadii(radiiData)
126+
} else {
127+
setSelectedElementRadii(null)
128+
}
129+
} else {
130+
setSelectedElementDetails(null)
131+
setSelectedElementRadii(null)
123132
}
124133

125-
// Check if nuclide is pinned - fetch nuclide details from NuclidesPlus
134+
// Fetch nuclide details if pinned
126135
if (pinnedNuclide && highlightedNuclide) {
127136
const [elementSymbol, massStr] = highlightedNuclide.split('-')
128137
const massNumber = parseInt(massStr)
129138
const nuclideDetails = getNuclideBySymbol(db, elementSymbol, massNumber)
130139
setSelectedNuclideDetails(nuclideDetails)
131-
setSelectedElementDetails(null)
132-
return
140+
} else {
141+
setSelectedNuclideDetails(null)
133142
}
134-
135-
// Nothing pinned
136-
setSelectedElementDetails(null)
137-
setSelectedNuclideDetails(null)
138143
}, [db, pinnedElement, highlightedElement, pinnedNuclide, highlightedNuclide])
139144

140145
// Update URL when filters change
@@ -592,9 +597,6 @@ export default function FissionQuery() {
592597
setPinnedNuclide(false)
593598
setHighlightedNuclide(null)
594599
} else {
595-
// Unpin element when selecting nuclide
596-
setPinnedElement(false)
597-
setHighlightedElement(null)
598600
setPinnedNuclide(true)
599601
setHighlightedNuclide(nuclideId)
600602
}
@@ -636,9 +638,6 @@ export default function FissionQuery() {
636638
setPinnedElement(false)
637639
setHighlightedElement(null)
638640
} else {
639-
// Unpin nuclide when selecting element
640-
setPinnedNuclide(false)
641-
setHighlightedNuclide(null)
642641
setPinnedElement(true)
643642
setHighlightedElement(elementId)
644643
}
@@ -654,32 +653,38 @@ export default function FissionQuery() {
654653
</div>
655654

656655
{/* Details Section */}
657-
<div className="card p-6">
658-
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
659-
Details
660-
</h3>
661-
{selectedNuclideDetails ? (
662-
<NuclideDetailsCard
663-
nuclide={selectedNuclideDetails}
664-
onClose={() => {
665-
setPinnedNuclide(false)
666-
setHighlightedNuclide(null)
667-
}}
668-
/>
669-
) : selectedElementDetails ? (
670-
<ElementDetailsCard
671-
element={selectedElementDetails}
672-
onClose={() => {
673-
setPinnedElement(false)
674-
setHighlightedElement(null)
675-
}}
676-
/>
677-
) : (
656+
{(selectedElementDetails || selectedNuclideDetails) ? (
657+
<div className="space-y-6">
658+
{selectedElementDetails && (
659+
<ElementDetailsCard
660+
element={selectedElementDetails}
661+
atomicRadii={selectedElementRadii}
662+
onClose={() => {
663+
setPinnedElement(false)
664+
setHighlightedElement(null)
665+
}}
666+
/>
667+
)}
668+
{selectedNuclideDetails && (
669+
<NuclideDetailsCard
670+
nuclide={selectedNuclideDetails}
671+
onClose={() => {
672+
setPinnedNuclide(false)
673+
setHighlightedNuclide(null)
674+
}}
675+
/>
676+
)}
677+
</div>
678+
) : (
679+
<div className="card p-6">
680+
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
681+
Details
682+
</h3>
678683
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
679684
<p className="text-sm">Click on a nuclide or element above to see detailed properties</p>
680685
</div>
681-
)}
682-
</div>
686+
</div>
687+
)}
683688
</div>
684689
)}
685690
</div>

0 commit comments

Comments
 (0)