Skip to content

Commit

Permalink
Implement layout and improve the first visualization
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianCassayre committed Aug 5, 2023
1 parent 25cf69c commit 4c03bff
Show file tree
Hide file tree
Showing 8 changed files with 788 additions and 56 deletions.
687 changes: 647 additions & 40 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"update:compile": "ts-node-esm src/scripts/compile.ts"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/inter": "^5.0.7",
"@mui/icons-material": "^5.14.3",
"@mui/joy": "^5.0.0-beta.0",
"@mui/material": "^5.14.3",
"d3-scale-chromatic": "^3.0.0",
"radash": "^11.0.0",
"react": "^18.2.0",
Expand Down
6 changes: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { QueryClient, QueryClientProvider } from 'react-query';
import { Homepage } from './Homepage.tsx';
import '@fontsource/inter';
import { Layout } from './Layout.tsx';

export const App = () => {
const queryClient = new QueryClient({
Expand All @@ -14,7 +16,9 @@ export const App = () => {

return (
<QueryClientProvider client={queryClient}>
<Homepage />
<Layout>
<Homepage />
</Layout>
</QueryClientProvider>
);
};
14 changes: 11 additions & 3 deletions src/Homepage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { useDataQuery } from './hooks/useDataQuery.ts';
import { DiskVisualization } from './viz/DiskVisualization.tsx';
import { Box, Grid, Typography } from '@mui/joy';

export const Homepage = () => {
const { data } = useDataQuery('geographyDisk');
return data ? (
<>
<DiskVisualization data={data.tree} />
</>
<Box>
<Grid container justifyContent="center">
<Grid xs={12} sm={10} md={8} lg={7} xl={6}>
<Typography level="h3" color="neutral" textAlign="center" sx={{ mb: 3 }}>
Place of birth
</Typography>
<DiskVisualization data={data.tree} />
</Grid>
</Grid>
</Box>
) : null;
};
51 changes: 51 additions & 0 deletions src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { Box, Container, CssBaseline, Divider, IconButton, Stack, Tooltip, Typography } from '@mui/joy';
import { AutoStories, GitHub } from '@mui/icons-material';

interface LayoutProps {
children: React.ReactNode;
}

export const Layout: React.FC<LayoutProps> = ({ children }) => {
const renderDateBuilt = () => {
const timestamp = import.meta.env.BUILD_TIMESTAMP as number | undefined;
const date = new Date(timestamp ?? new Date().getTime());
return date.toLocaleDateString('en-us', { year: 'numeric', month: 'short', day: 'numeric' });
};
return (
<>
<CssBaseline />
<Container sx={{ my: 2 }}>
<Stack direction="row" justifyContent="space-between">
<Stack direction="column" sx={{ mb: 2 }}>
<Stack direction="row" spacing={1} alignItems="center">
<AutoStories />
<Typography level="h3" color="neutral">
Florian Cassayre
</Typography>
</Stack>
<Typography color="neutral">Genealogy and family history</Typography>
</Stack>
<Box>
<Tooltip title="View on GitHub">
<IconButton
variant="outlined"
component="a"
href="https://github.com/FlorianCassayre/genealogy-visualization"
target="_blank"
rel="noopener"
>
<GitHub />
</IconButton>
</Tooltip>
</Box>
</Stack>
{children}
<Divider sx={{ my: 2 }} />
<footer>
<Typography textAlign="center">Date updated: {renderDateBuilt()}</Typography>
</footer>
</Container>
</>
);
};
64 changes: 54 additions & 10 deletions src/viz/DiskVisualization.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { SimpleIndividualTree } from '../scripts/types';
import * as _ from 'radash';
import { interpolateSinebow } from 'd3-scale-chromatic';
import * as _ from 'radash';
import { Tooltip } from '@mui/joy';

interface DiskVisualizationProps {
data: SimpleIndividualTree;
}

export const DiskVisualization: React.FC<DiskVisualizationProps> = ({ data }) => {
const [hoveredKey, setHoveredKey] = useState<string | null>(null);

const radius = 50;
const initialAngle = Math.PI / 2;
const strokeWidth = 1;
Expand Down Expand Up @@ -41,18 +44,59 @@ export const DiskVisualization: React.FC<DiskVisualizationProps> = ({ data }) =>
const colors = _.objectify(
uniquePlaces.map((v, i) => [v, i] as const),
([key]) => key,
([key, i]) => key ? interpolate(uniquePlaces.length > 1 ? i / (uniquePlaces.length - 1) : 0) : 'white',
([key, i]) => (key ? interpolate(uniquePlaces.length > 1 ? i / (uniquePlaces.length - 1) : 0) : 'white'),
);
const maxDistance = _.max(layout.map((p) => p.distance))! + radius;
const size = (maxDistance + strokeWidth) * 2;
const handleEnter = (key: string) => setHoveredKey(key || null);
const handleLeave = (key: string) => {
if (hoveredKey === key) {
setHoveredKey(null);
}
};
return (
<Tooltip title={hoveredKey} followCursor>
<svg viewBox={[0, 0, size, size].join(' ')}>
<g transform={`translate(${size / 2}, ${size / 2})`}>
{Object.entries(_.group(layout, (p) => p.place)).map(([place, allProps], i) => (
<DiskTreeLayoutGroup
key={i}
onEnter={() => handleEnter(place)}
onLeave={() => handleLeave(place)}
focused={hoveredKey === null ? undefined : place === hoveredKey}
>
{allProps!.map(({ place, ...props }, i) => (
<DiskTreeLayout key={i} {...props} color={colors[place]} />
))}
</DiskTreeLayoutGroup>
))}
</g>
</svg>
</Tooltip>
);
};

interface DiskTreeLayoutGroupProps {
onEnter: () => void;
onLeave: () => void;
focused?: boolean;
children: React.ReactNode;
}

const DiskTreeLayoutGroup: React.FC<DiskTreeLayoutGroupProps> = ({ onEnter, onLeave, focused, children }) => {
return (
<svg width={size} height={size}>
<g transform={`translate(${size / 2}, ${size / 2})`}>
{layout.map(({ place, ...props }, i) => (
<DiskTreeLayout key={i} {...props} color={colors[place]} />
))}
</g>
</svg>
<g
opacity={focused !== false ? undefined : 0.25}
onMouseEnter={() => onEnter()}
onMouseLeave={() => onLeave()}
style={{
transition: 'opacity 0.5s, filter 0.5s',
filter: focused !== false ? undefined : 'grayscale(100%)',
cursor: 'help',
}}
>
{children}
</g>
);
};

Expand Down
7 changes: 5 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["node"]
"types": ["node"],
"paths": {
"@mui/material": ["./node_modules/@mui/joy"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"references": [{ "path": "./tsconfig.node.json" }],
}
9 changes: 9 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,13 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
// https://mui.com/joy-ui/guides/using-icon-libraries/
'@mui/material': '@mui/joy',
},
},
define: {
BUILD_TIMESTAMP: new Date().getTime(),
}
})

0 comments on commit 4c03bff

Please sign in to comment.