Skip to content

Commit

Permalink
show tiles of reduced opacity representing all lanth-/actinides in gr…
Browse files Browse the repository at this point in the history
…id col=3, row=6/7

set ElementTile container-type: inline-size and adjust container query font-sizes so that large standalone ElementTiles outside PeriodicTable still use correct font-sizes
only show tip to navigate element detail pages with arrow keys on hovering PrevNextElement
ppm i d3-color to handle non-rgb color strings in ElementTile text color thresholding
fix tests
move elem_symbols and categories to src/lib/labels.ts
  • Loading branch information
janosh committed Feb 27, 2023
1 parent c3a5a91 commit be4f88a
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 64 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@iconify/svelte": "^3.1.0",
"@sveltejs/kit": "1.3.10",
"d3-array": "^3.2.2",
"d3-color": "^3.1.0",
"d3-interpolate-path": "^2.3.0",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.0.0",
Expand Down
41 changes: 22 additions & 19 deletions src/lib/ElementTile.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<script lang="ts">
import { rgb } from 'd3-color'
import { createEventDispatcher } from 'svelte'
import type { ChemicalElement, PeriodicTableEvents } from '.'
import { pretty_num } from './labels'
import { last_element } from './stores'
export let element: ChemicalElement
export let bg_color: string | null = null
export let show_symbol = true
export let show_number = true
export let show_name = true
export let show_symbol: boolean = true
export let show_number: boolean = true
export let show_name: boolean = true
export let value: number | false | undefined = undefined
export let style = ``
export let active = false
export let style: string = ``
export let symbol_style: string = ``
export let active: boolean = false
export let href: string | null = null
// at what background color lightness text color switches from black to white
export let text_color_threshold = 0.7
Expand All @@ -25,13 +27,13 @@
dispatch(dom_event.type, { element, event: dom_event, active })
}
function luminance(rgb: string) {
function luminance(clr: string) {
// calculate human-perceived lightness from RGB
const [r, g, b] = rgb
.replace(`rgb(`, ``)
.split(`,`)
.map((v) => parseInt(v) / 255)
return 0.2126 * r + 0.7152 * g + 0.0722 * b // https://stackoverflow.com/a/596243
const { r, g, b } = rgb(clr)
// if (![r, g, b].every((c) => c >= 0 && c <= 255)) {
// console.error(`invalid RGB color: ${clr}, parsed to rgb=${r},${g},${b}`)
// }
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 // https://stackoverflow.com/a/596243
}
function get_bg_color(
Expand Down Expand Up @@ -76,7 +78,7 @@
</span>
{/if}
{#if show_symbol}
<span class="symbol">
<span class="symbol" style={symbol_style}>
{element.symbol}
</span>
{/if}
Expand Down Expand Up @@ -105,6 +107,7 @@
color: var(--elem-tile-text-color, white);
/* add persistent invisible border so content doesn't move on hover */
border: 1px solid transparent;
container-type: inline-size;
}
.element-tile span {
line-height: 1em;
Expand All @@ -117,24 +120,24 @@
border: 1px dotted;
}
.number {
font-size: 1.1cqw;
font-size: 22cqw;
position: absolute;
top: 0.3cqw;
top: 6cqw;
font-weight: lighter;
left: 0.3cqw;
left: 6cqw;
}
.symbol {
font-size: 2cqw;
font-size: 40cqw;
}
span.name,
span.value {
position: absolute;
bottom: 0.4cqw;
bottom: 8cqw;
}
span.value {
font-size: 0.9cqw;
font-size: 18cqw;
}
span.name {
font-size: 0.6cqw;
font-size: 12cqw;
}
</style>
33 changes: 31 additions & 2 deletions src/lib/PeriodicTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import { goto } from '$app/navigation'
import * as d3sc from 'd3-scale-chromatic'
import type { Category, ChemicalElement, PeriodicTableEvents } from '.'
import { ElementPhoto, ElementTile, elem_symbols, type ElementSymbol } from '.'
import { ElementPhoto, ElementTile, type ElementSymbol } from '.'
import element_data from './element-data'
import { elem_symbols } from './labels'
export let tile_props: {
show_name?: boolean
Expand All @@ -26,6 +27,24 @@
export let active_category: Category | null = null
export let gap = `0.3cqw` // gap between element tiles, default is 0.3% of container width
export let inner_transition_metal_offset = 0.5
const default_lanth_act_tiles = [
{
name: `Lanthanides`,
symbol: `La-Lu`,
number: `57-71`,
category: `lanthanide`,
},
{
name: `Actinides`,
symbol: `Ac-Lr`,
number: `89-103`,
category: `actinide`,
},
]
// show lanthanides and actinides as tiles
export let lanth_act_tiles =
tile_props?.show_symbol == false ? [] : default_lanth_act_tiles
export let lanth_act_style: string = ``
type $$Events = PeriodicTableEvents // for type-safe event listening on this component
Expand Down Expand Up @@ -120,6 +139,16 @@
on:mouseleave
/>
{/each}
<!-- show tile for lanthanides and actinides with text La-Lu and Ac-Lr respectively -->
{#each lanth_act_tiles || [] as element, idx}
<ElementTile
{element}
style="opacity: 0.8; grid-column: 3; grid-row: {6 + idx}; {lanth_act_style};"
on:mouseenter={() => (active_category = element.category)}
on:mouseleave={() => (active_category = null)}
symbol_style="font-size: 30cqw;"
/>
{/each}
{#if inner_transition_metal_offset}
<!-- provide vertical offset for lanthanides + actinides -->
<div class="spacer" style:aspect-ratio={1 / inner_transition_metal_offset} />
Expand All @@ -135,7 +164,7 @@

<style>
.periodic-table-container {
/* needed for gap: 0.3cqw; below to work */
/* needed for gap: 0.3cqw; to work */
container-type: inline-size;
}
div.periodic-table {
Expand Down
18 changes: 2 additions & 16 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { categories, elem_symbols } from './labels'

export { default as BohrAtom } from './BohrAtom.svelte'
export { default as ColorBar } from './ColorBar.svelte'
export { default as ColorCustomizer } from './ColorCustomizer.svelte'
Expand All @@ -17,24 +19,8 @@ export { default as ScatterPlot } from './ScatterPlot.svelte'
export { default as ScatterPoint } from './ScatterPoint.svelte'
export { default as TableInset } from './TableInset.svelte'

export const categories = [
`actinide`,
`alkali metal`,
`alkaline earth metal`,
`diatomic nonmetal`,
`lanthanide`,
`metalloid`,
`noble gas`,
`polyatomic nonmetal`,
`post-transition metal`,
`transition metal`,
] as const

export type Category = (typeof categories)[number]

// prettier-ignore
export const elem_symbols = [`H`,`He`,`Li`,`Be`,`B`,`C`,`N`,`O`,`F`,`Ne`,`Na`,`Mg`,`Al`,`Si`,`P`,`S`,`Cl`,`Ar`,`K`,`Ca`,`Sc`,`Ti`,`V`,`Cr`,`Mn`,`Fe`,`Co`,`Ni`,`Cu`,`Zn`,`Ga`,`Ge`,`As`,`Se`,`Br`,`Kr`,`Rb`,`Sr`,`Y`,`Zr`,`Nb`,`Mo`,`Tc`,`Ru`,`Rh`,`Pd`,`Ag`,`Cd`,`In`,`Sn`,`Sb`,`Te`,`I`,`Xe`,`Cs`,`Ba`,`La`,`Ce`,`Pr`,`Nd`,`Pm`,`Sm`,`Eu`,`Gd`,`Tb`,`Dy`,`Ho`,`Er`,`Tm`,`Yb`,`Lu`,`Hf`,`Ta`,`W`,`Re`,`Os`,`Ir`,`Pt`,`Au`,`Hg`,`Tl`,`Pb`,`Bi`,`Po`,`At`,`Rn`,`Fr`,`Ra`,`Ac`,`Th`,`Pa`,`U`,`Np`,`Pu`,`Am`,`Cm`,`Bk`,`Cf`,`Es`,`Fm`,`Md`,`No`,`Lr`,`Rf`,`Db`,`Sg`,`Bh`,`Hs`,`Mt`,`Ds`,`Rg`,`Cn`,`Nh`,`Fl`,`Mc`,`Lv`,`Ts`,`Og`] as const

export type ElementSymbol = (typeof elem_symbols)[number]

export type ChemicalElement = {
Expand Down
16 changes: 16 additions & 0 deletions src/lib/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,19 @@ export const category_counts: Record<Category, number> = {
'post-transition metal': 12,
'transition metal': 38,
}

export const categories = [
`actinide`,
`alkali metal`,
`alkaline earth metal`,
`diatomic nonmetal`,
`lanthanide`,
`metalloid`,
`noble gas`,
`polyatomic nonmetal`,
`post-transition metal`,
`transition metal`,
] as const

// prettier-ignore
export const elem_symbols = [`H`,`He`,`Li`,`Be`,`B`,`C`,`N`,`O`,`F`,`Ne`,`Na`,`Mg`,`Al`,`Si`,`P`,`S`,`Cl`,`Ar`,`K`,`Ca`,`Sc`,`Ti`,`V`,`Cr`,`Mn`,`Fe`,`Co`,`Ni`,`Cu`,`Zn`,`Ga`,`Ge`,`As`,`Se`,`Br`,`Kr`,`Rb`,`Sr`,`Y`,`Zr`,`Nb`,`Mo`,`Tc`,`Ru`,`Rh`,`Pd`,`Ag`,`Cd`,`In`,`Sn`,`Sb`,`Te`,`I`,`Xe`,`Cs`,`Ba`,`La`,`Ce`,`Pr`,`Nd`,`Pm`,`Sm`,`Eu`,`Gd`,`Tb`,`Dy`,`Ho`,`Er`,`Tm`,`Yb`,`Lu`,`Hf`,`Ta`,`W`,`Re`,`Os`,`Ir`,`Pt`,`Au`,`Hg`,`Tl`,`Pb`,`Bi`,`Po`,`At`,`Rn`,`Fr`,`Ra`,`Ac`,`Th`,`Pa`,`U`,`Np`,`Pu`,`Am`,`Cm`,`Bk`,`Cf`,`Es`,`Fm`,`Md`,`No`,`Lr`,`Rf`,`Db`,`Sg`,`Bh`,`Hs`,`Mt`,`Ds`,`Rg`,`Cn`,`Nh`,`Fl`,`Mc`,`Lv`,`Ts`,`Og`] as const
6 changes: 2 additions & 4 deletions src/routes/(demos)/element-tile/+page.svx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
<script>
import { ElementTile, element_data } from '$lib'

const colors = [
`red`, `cornflowerblue`, `rebeccapurple`, `gold`, `green`, `blue`, `yellow`, `orange`, `purple`, `beige`, `pink`, `AliceBlue`, `BlanchedAlmond`, `BurlyWood`, `CadetBlue`, `Chartreuse`, `DarkGoldenRod`, `DeepSkyBlue`, `FireBrick`, `Gainsboro`, `Honeydew`, `LavenderBlush`, `LemonChiffon`, `LightGoldenRodYellow`, `MediumAquamarine`, `MintCream`, `NavajoWhite`, `OldLace`, `OliveDrab`, `PapayaWhip`, `PeachPuff`, `Peru`, `SaddleBrown`, `Thistle`,
]
const rand_color = () => `hsl(${Math.random() * 360}, ${Math.random() * 50 + 50}%, ${Math.random() * 50 + 50}%)`
</script>

<ol>
{#each colors as bg_color, idx}
{#each Array(27).fill(0).map(rand_color) as bg_color, idx}
<ElementTile {bg_color} element={element_data[idx]} style="width: 4em; margin: 0;" />
{/each}
</ol>
Expand Down
17 changes: 5 additions & 12 deletions src/site/Footer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,11 @@
<p class="toggle-icons">
<Toggle bind:checked={$show_icons} style="transform: scale(0.8);" />Icons
</p>
<p>
<small>
Use arrow keys &thinsp;&larr; &rarr;&thinsp; to navigate between elements.
</small>
</p>
<p>
<small>
Built with Svelte by
<a href="https://github.com/janosh">Janosh</a>
&copy; 2022
</small>
</p>
<small>
Built with Svelte by
<a href="https://github.com/janosh">Janosh</a>
&copy; 2022
</small>
</footer>

<style>
Expand Down
17 changes: 17 additions & 0 deletions src/site/PrevNextElement.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
<ElementTile element={next} style={tile_style} show_name={false} />
</a>
</li>
<small>
You can use arrow keys &thinsp;&larr; &rarr;&thinsp; to navigate between elements.
</small>
</ul>

<style>
Expand All @@ -55,6 +58,7 @@
gap: 2em;
max-width: 1200px;
margin: 5em auto 0;
position: relative;
}
ul li {
display: flex;
Expand All @@ -65,4 +69,17 @@
ul li a {
display: grid;
}
ul:has(li) small {
position: absolute;
visibility: hidden;
opacity: 0;
transition: 0.3s;
left: 50%;
top: 2em;
transform: translateX(-50%);
}
ul:has(li:hover) small {
visibility: visible;
opacity: 1;
}
</style>
11 changes: 8 additions & 3 deletions tests/periodic-table.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from '@playwright/test'
import element_data from '../src/lib/element-data.ts'
import {
categories,
category_counts,
heatmap_keys,
heatmap_labels,
Expand All @@ -12,11 +13,15 @@ test.describe(`Periodic Table`, () => {
await page.goto(`/`, { waitUntil: `networkidle` })

const element_tiles = await page.$$(`.element-tile`)
expect(element_tiles).toHaveLength(element_data.length)
expect(element_tiles).toHaveLength(element_data.length + 2)

for (const [category, count] of Object.entries(category_counts)) {
for (const category of categories) {
let count = category_counts[category] as number
const css_cls = `.${category.replaceAll(` `, `-`)}`
expect(await page.$$(css_cls)).toHaveLength(count as number)
// add 1 to expected count since lanthanides and actinides have placeholder
// tiles showing where in the periodic table their rows insert
if ([`lanthanide`, `actinide`].includes(category)) count += 1
expect(await page.$$(css_cls), category).toHaveLength(count as number)
}
})

Expand Down
5 changes: 3 additions & 2 deletions tests/unit/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as lib from '$lib'
import IndexPeriodicTable, { ElementScatter, PeriodicTable } from '$lib'
import FileElementScatter from '$lib/ElementScatter.svelte'
import * as labels from '$lib/labels'
import FilePeriodicTable from '$lib/PeriodicTable.svelte'
import { expect, test } from 'vitest'

Expand All @@ -21,6 +22,6 @@ test(`src/lib/icons/index.ts re-exports all icons`, () => {
})

test(`categories and element_symbols are exported`, () => {
expect(lib.categories).toHaveLength(10)
expect(lib.elem_symbols).toHaveLength(lib.element_data.length)
expect(labels.categories).toHaveLength(10)
expect(labels.elem_symbols).toHaveLength(lib.element_data.length)
})
21 changes: 15 additions & 6 deletions tests/unit/periodic-table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { describe, expect, test, vi } from 'vitest'
import { doc_query, sleep } from '.'

describe(`PeriodicTable`, () => {
test(`renders element tiles`, async () => {
new PeriodicTable({ target: document.body })

const element_tiles = document.querySelectorAll(`.element-tile`)
expect(element_tiles.length).toBe(118)
})
test.each([
[true, 120],
[false, 118],
[null, 118],
[[], 118],
])(
`renders element tiles with show_lanth_act_tiles=%s`,
async (lanth_act_tiles, expected_tiles) => {
const props = lanth_act_tiles == true ? {} : { lanth_act_tiles }
new PeriodicTable({ target: document.body, props })

const element_tiles = document.querySelectorAll(`.element-tile`)
expect(element_tiles.length).toBe(expected_tiles)
}
)

test(`has no text content when symbols, names and numbers are disabled`, async () => {
const tile_props = {
Expand Down

0 comments on commit be4f88a

Please sign in to comment.