mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-22 11:41:48 +00:00
First version of integrating donatso/family-chart #185
This commit is contained in:
37
src/app.tsx
37
src/app.tsx
@@ -47,6 +47,7 @@ import {
|
||||
WikiTreeDataSource,
|
||||
WikiTreeSourceSpec,
|
||||
} from './datasource/wikitree';
|
||||
import {DonatsoChart} from './donatso-chart';
|
||||
|
||||
/**
|
||||
* Load GEDCOM URL from REACT_APP_STATIC_URL environment variable.
|
||||
@@ -142,6 +143,7 @@ function getArguments(location: H.Location<any>): Arguments {
|
||||
const chartTypes = new Map<string | undefined, ChartType>([
|
||||
['relatives', ChartType.Relatives],
|
||||
['fancy', ChartType.Fancy],
|
||||
['donatso', ChartType.Donatso],
|
||||
]);
|
||||
|
||||
const hash = getParam('file');
|
||||
@@ -481,6 +483,30 @@ export function App() {
|
||||
setShowErrorPopup(false);
|
||||
}
|
||||
|
||||
function renderChart(selection: IndiInfo) {
|
||||
if (chartType === ChartType.Donatso) {
|
||||
return (
|
||||
<DonatsoChart
|
||||
data={data!.chartData}
|
||||
selection={selection}
|
||||
onSelection={onSelection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Chart
|
||||
data={data!.chartData}
|
||||
selection={selection}
|
||||
chartType={chartType}
|
||||
onSelection={onSelection}
|
||||
freezeAnimation={freezeAnimation}
|
||||
colors={config.color}
|
||||
hideIds={config.id}
|
||||
hideSex={config.sex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderMainArea() {
|
||||
switch (state) {
|
||||
case AppState.SHOWING_CHART:
|
||||
@@ -523,16 +549,7 @@ export function App() {
|
||||
{state === AppState.LOADING_MORE ? (
|
||||
<Loader active size="small" className="loading-more" />
|
||||
) : null}
|
||||
<Chart
|
||||
data={data!.chartData}
|
||||
selection={updatedSelection}
|
||||
chartType={chartType}
|
||||
onSelection={onSelection}
|
||||
freezeAnimation={freezeAnimation}
|
||||
colors={config.color}
|
||||
hideIds={config.id}
|
||||
hideSex={config.sex}
|
||||
/>
|
||||
{renderChart(updatedSelection)}
|
||||
{showSidePanel ? (
|
||||
<Media greaterThanOrEqual="large" className="sidePanel">
|
||||
<Tab panes={sidePanelTabs} />
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
CircleRenderer,
|
||||
ChartColors as TopolaChartColors,
|
||||
} from 'topola';
|
||||
import {usePrevious} from './util/previous-hook';
|
||||
|
||||
/** How much to zoom when using the +/- buttons. */
|
||||
const ZOOM_FACTOR = 1.3;
|
||||
@@ -217,6 +218,7 @@ export async function downloadPdf() {
|
||||
export enum ChartType {
|
||||
Hourglass,
|
||||
Relatives,
|
||||
Donatso,
|
||||
Fancy,
|
||||
}
|
||||
|
||||
@@ -402,14 +404,6 @@ class ChartWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export function Chart(props: ChartProps) {
|
||||
const chartWrapper = useRef(new ChartWrapper());
|
||||
const prevProps = usePrevious(props);
|
||||
|
||||
105
src/donatso-chart.tsx
Normal file
105
src/donatso-chart.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {IntlShape, useIntl} from 'react-intl';
|
||||
import {IndiInfo, JsonFam, JsonGedcomData} from 'topola';
|
||||
|
||||
import {formatDateOrRange} from './util/date_util';
|
||||
import {usePrevious} from './util/previous-hook';
|
||||
|
||||
const f3: any = require('family-chart');
|
||||
|
||||
export interface DonatsoChartProps {
|
||||
data: JsonGedcomData;
|
||||
selection: IndiInfo;
|
||||
onSelection: (indiInfo: IndiInfo) => void;
|
||||
}
|
||||
|
||||
function getOtherSpouse(fam: JsonFam, indi: string) {
|
||||
return fam.husb === indi ? fam.wife : fam.husb;
|
||||
}
|
||||
|
||||
function convertData(data: JsonGedcomData, intl: IntlShape) {
|
||||
const famMap = new Map<string, JsonFam>();
|
||||
data.fams.forEach((fam) => famMap.set(fam.id, fam));
|
||||
return data.indis.map((indi) => {
|
||||
const famc = famMap.get(indi.famc!);
|
||||
const fams = (indi.fams || [])
|
||||
.map((fam) => famMap.get(fam))
|
||||
.filter((fam): fam is JsonFam => fam !== undefined);
|
||||
const father = famc?.husb;
|
||||
const mother = famc?.wife;
|
||||
const spouses = fams
|
||||
.map((fam) => getOtherSpouse(fam, indi.id))
|
||||
.filter((indi): indi is string => indi !== undefined);
|
||||
const children = fams.flatMap((fam) => fam.children || []);
|
||||
|
||||
return {
|
||||
id: indi.id,
|
||||
data: {
|
||||
'first name': indi.firstName,
|
||||
'last name': indi.lastName,
|
||||
birthday: formatDateOrRange(indi.birth, intl),
|
||||
avatar: indi.images?.[0]?.url,
|
||||
gender: indi.sex,
|
||||
},
|
||||
rels: {
|
||||
father,
|
||||
mother,
|
||||
spouses,
|
||||
children,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class ChartWrapper {
|
||||
private store?: any;
|
||||
|
||||
initializeChart(props: DonatsoChartProps, intl: IntlShape) {
|
||||
const data = convertData(props.data, intl);
|
||||
this.store = f3.createStore({
|
||||
data,
|
||||
main_id: props.selection.id,
|
||||
});
|
||||
const svg = f3.createSvg(document.querySelector('#dotatsoSvgContainer'));
|
||||
const card = f3.elements.Card({
|
||||
store: this.store,
|
||||
svg,
|
||||
card_display: [
|
||||
(i: any) =>
|
||||
`${i.data['first name'] || ''} ${i.data['last name'] || ''}`,
|
||||
(i: any) => `${i.data.birthday || ''}`,
|
||||
],
|
||||
mini_tree: true,
|
||||
link_break: false,
|
||||
onCardClick: (e: any, d: any) =>
|
||||
props.onSelection({id: d.data.id, generation: 0}),
|
||||
});
|
||||
this.store.setOnUpdate((props: any) => {
|
||||
f3.view(this.store.getTree(), svg, card, props || {});
|
||||
});
|
||||
this.store.updateTree({initial: true});
|
||||
}
|
||||
|
||||
updateChart(props: DonatsoChartProps, intl: IntlShape) {
|
||||
const data = convertData(props.data, intl);
|
||||
this.store.updateData(data);
|
||||
this.store.updateMainId(props.selection.id);
|
||||
this.store.updateTree();
|
||||
}
|
||||
}
|
||||
|
||||
export function DonatsoChart(props: DonatsoChartProps) {
|
||||
const chartWrapper = useRef(new ChartWrapper());
|
||||
const prevProps = usePrevious(props);
|
||||
const intl = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
if (!prevProps) {
|
||||
chartWrapper.current.initializeChart(props, intl);
|
||||
} else {
|
||||
chartWrapper.current.updateChart(props, intl);
|
||||
}
|
||||
});
|
||||
|
||||
return <div id="dotatsoSvgContainer"></div>;
|
||||
}
|
||||
@@ -232,3 +232,49 @@ div.zoom {
|
||||
padding: 14px 0px 0px;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer svg.main_svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer rect.card-female, #dotatsoSvgContainer .card-female .card-body-rect, #dotatsoSvgContainer .card-female .text-overflow-mask {
|
||||
fill: rgb(196, 138, 146);
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer rect.card-male, #dotatsoSvgContainer .card-male .card-body-rect, #dotatsoSvgContainer .card-male .text-overflow-mask {
|
||||
fill: rgb(120, 159, 172);
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer .card-genderless .card-body-rect, #dotatsoSvgContainer .card-genderless .text-overflow-mask {
|
||||
fill: lightgray;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer g.card_add text {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer .card-main-outline {
|
||||
stroke: black;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer .card_family_tree rect {
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer .card_family_tree:hover rect {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer .card_family_tree line {
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
#dotatsoSvgContainer .link {
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
9
src/util/previous-hook.ts
Normal file
9
src/util/previous-hook.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {useEffect, useRef} from 'react';
|
||||
|
||||
export function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
Reference in New Issue
Block a user