Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions frontend/src/lib/components/Story.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@

const isExternal = !!story.externalUrl;
const finalHref = isExternal ? story.externalUrl : `${base}/${story.slug}`;

// Glob-import all thumbnails through enhanced-img for automatic WebP/AVIF conversion
const thumbnails = import.meta.glob<{ default: string }>(
'/src/lib/assets/thumbnails/*.{jpg,png,webp}',
{ eager: true, query: { enhanced: true } }
);

// Find this story's thumbnail (try jpg then png)
const thumbnailKey = Object.keys(thumbnails).find(
k => k.endsWith(`/${story.slug}.jpg`) || k.endsWith(`/${story.slug}.png`)
);
const thumbnail = thumbnailKey ? thumbnails[thumbnailKey].default : null;
</script>

<div class:external={isExternal}>
Expand All @@ -22,11 +34,19 @@
<ExternalLink size={18} />
</div>
{/if}
<img
src="{base}/common/thumbnails/screenshots/{story.slug}.jpg"
loading="lazy"
alt="Thumbnail for {story.title}"
/>
{#if thumbnail}
<enhanced:img
src={thumbnail}
loading="lazy"
alt="Thumbnail for {story.title}"
/>
{:else}
<img
src="{base}/common/thumbnails/screenshots/{story.slug}.jpg"
loading="lazy"
alt="Thumbnail for {story.title}"
/>
{/if}
</div>
<div class="text">
<div class="header-row">
Expand Down Expand Up @@ -83,7 +103,8 @@
transform: translateY(-2px);
}

.screenshot img {
.screenshot img,
.screenshot :global(picture img) {
position: absolute;
inset: 0;
width: 100%;
Expand All @@ -92,7 +113,8 @@
transition: transform 0.25s ease;
}

.story:hover .screenshot img {
.story:hover .screenshot img,
.story:hover .screenshot :global(picture img) {
transform: scale(1.05);
}

Expand Down
117 changes: 102 additions & 15 deletions frontend/src/lib/stories/allotax-scrolly/components/BarChartRank.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,107 @@
<script>
import { Plot, BarY, RuleY } from 'svelteplot';
import * as d3 from 'd3';

let { data, fill } = $props();

const H = 300;
const MIN_BAR_WIDTH = 20; // px — minimum readable bar width
const margin = { top: 25, right: 25, bottom: 80, left: 50 };

let width = $state(1100); // SSR default, updated by bind:clientWidth

let sorted = $derived([...data].sort((a, b) => b.counts - a.counts));

// How many bars fit at the current width
let maxBars = $derived(
Math.max(5, Math.floor((width - margin.left - margin.right) / MIN_BAR_WIDTH))
);
let displayData = $derived(sorted.slice(0, maxBars));

let xScale = $derived(
d3.scaleBand()
.domain(displayData.map(d => d.types))
.range([margin.left, width - margin.right])
.padding(0.1)
);

let yScale = $derived(
d3.scaleLinear()
.domain([0, d3.max(sorted, d => d.counts) ?? 1])
.nice()
.range([H - margin.bottom, margin.top])
);

let yTicks = $derived(yScale.ticks(5));
</script>

<Plot
y={{ grid: true }}
x={{ tickRotate: 25, label: '' }}
marginTop={25}
marginRight={25}
height={300}>
<BarY
{data}
y="counts" x="types"
{fill}
stroke="grey"
sort={{ channel: '-y' }} />
<RuleY data={[0]} />
</Plot>
<div class="chart-container" bind:clientWidth={width}>
<svg {width} height={H}>
<!-- Y gridlines -->
{#each yTicks as tick (tick)}
<line
x1={margin.left} x2={width - margin.right}
y1={yScale(tick)} y2={yScale(tick)}
stroke="var(--color-border, #e5e7eb)"
stroke-width="1"
/>
{/each}

<!-- Bars -->
{#each displayData as d (d.types)}
<rect
x={xScale(d.types) ?? 0}
y={yScale(d.counts)}
width={xScale.bandwidth()}
height={yScale(0) - yScale(d.counts)}
{fill}
stroke="grey"
stroke-width="0.5"
/>
{/each}

<!-- Baseline at y=0 -->
<line
x1={margin.left} x2={width - margin.right}
y1={yScale(0)} y2={yScale(0)}
stroke="currentColor" stroke-width="1"
/>

<!-- X axis labels (rotated -45°) -->
{#each displayData as d (d.types)}
{@const cx = (xScale(d.types) ?? 0) + xScale.bandwidth() / 2}
{@const cy = yScale(0) + 6}
<text
x={cx} y={cy}
text-anchor="end"
transform="rotate(-45, {cx}, {cy})"
font-size="10"
fill="currentColor"
>
{d.types}
</text>
{/each}

<!-- Y axis labels -->
{#each yTicks as tick (tick)}
<text
x={margin.left - 6}
y={yScale(tick)}
text-anchor="end"
dominant-baseline="middle"
font-size="11"
fill="currentColor"
>
{d3.format('~s')(tick)}
</text>
{/each}
</svg>
</div>

<style>
.chart-container {
width: 100%;
min-height: 300px;
position: relative;
overflow: hidden;
}
</style>
14 changes: 2 additions & 12 deletions frontend/src/lib/stories/allotax-scrolly/components/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
import Scrolly from '$lib/components/helpers/Scrolly.svelte';
import ScrollyMd from './ScrollyMarkdown.svelte'; // New import
import Md from '$lib/components/helpers/MarkdownRenderer.svelte';
import Spinner from '$lib/components/helpers/Spinner.svelte';

let { story, data } = $props();

let scrollyIndex = $state();
Expand Down Expand Up @@ -165,21 +163,13 @@
<p>Here is the ranking of the {@render G(isGirls ? 'girl' : 'boy')} baby names in <span class="year-1980">1980</span>:</p>

<div class="initial-chart">
{#if browser}
<BarChartRank data={sys1.slice(0, 30)} fill={"#a6a6a6"} />
{:else}
<Spinner text="" />
{/if}
<BarChartRank data={sys1.slice(0, 30)} fill={"#a6a6a6"} />
</div>

<p>Here is the ranking for {@render G(isGirls ? 'girl' : 'boy')} baby names in Quebec for <span class="year-2023">2023</span>:</p>

<div class="initial-chart">
{#if browser}
<BarChartRank data={sys2.slice(0, 30)} fill={"#c3e6f3e6"} />
{:else}
<Spinner text="" />
{/if}
<BarChartRank data={sys2.slice(0, 30)} fill={"#c3e6f3e6"} />
</div>

<p>I really like this analysis, but there are some limitations in comparing ranks using raw counts, especially when it comes to systems that are known to be "heavy-tailed". That is, when a few names, or types, occur many more times in your dataset than less frequent ones, aka the tail. For instance, in the analysis the author compares baby names between "then and now". By just looking at raw counts, we are stuck with such comparisons where top-ranking baby names in <span class="year-1980">1980</span> might now be in the tail, which is a bit underwhelming. How can we know about the most surprising comparisons, given the heavy-tailed distribution?</p>
Expand Down