Added menu option to display the relatives chart

This commit is contained in:
Przemek Wiech 2019-05-20 22:13:18 +02:00
parent d091d3e3ce
commit 9bd1720122
6 changed files with 99 additions and 19 deletions

9
package-lock.json generated
View File

@ -8060,8 +8060,7 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
@ -17998,9 +17997,9 @@
}
},
"topola": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/topola/-/topola-2.2.9.tgz",
"integrity": "sha512-bMS3RxmB4P9JmwaiycnbgguIRaXUUAnASyUqGc1GNmloRUBM9yZgGWQMZ4xkFHE7Otr4IiyMp8wOwrXahYLaYQ==",
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/topola/-/topola-2.3.4.tgz",
"integrity": "sha512-eJpy19T6xFkrLgf1NGAMkgmLoswB00hY206LQZWiQRURr0+p0fdGAmNQKkWLba/e/MT3H/Qe/SSnAvRmHhFobA==",
"requires": {
"d3": "^5.4.0",
"d3-flextree": "^2.1.1",

View File

@ -23,7 +23,7 @@
"react-router-dom": "^4.3.1",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^0.84.0",
"topola": "^2.2.9"
"topola": "^2.3.4"
},
"devDependencies": {
"@types/array.prototype.flatmap": "^1.2.0",

View File

@ -1,7 +1,7 @@
import * as queryString from 'query-string';
import * as React from 'react';
import {analyticsEvent} from './analytics';
import {Chart} from './chart';
import {Chart, ChartType} from './chart';
import {Details} from './details';
import {getSelection, loadFromUrl, loadGedcom} from './load_data';
import {IndiInfo} from 'topola';
@ -53,20 +53,27 @@ interface State {
hash?: string;
/** Error to display. */
error?: string;
/** True if currently loading. */
/** True if data is currently being loaded. */
loading: boolean;
/** URL of the data that is loaded or is being loaded. */
url?: string;
/** Whether the side panel is shoen. */
/** Whether the side panel is shown. */
showSidePanel?: boolean;
/** Whether the app is in embedded mode, i.e. embedded in an iframe. */
embedded: boolean;
/** Whether the app is in standalone mode, i.e. showing 'open file' menus */
/** Whether the app is in standalone mode, i.e. showing 'open file' menus. */
standalone: boolean;
/** Type of displayed chart. */
chartType: ChartType;
}
export class App extends React.Component<RouteComponentProps, {}> {
state: State = {loading: false, embedded: false, standalone: true};
state: State = {
loading: false,
embedded: false,
standalone: true,
chartType: ChartType.Hourglass,
};
chartRef: Chart | null = null;
private isNewData(
@ -81,16 +88,18 @@ export class App extends React.Component<RouteComponentProps, {}> {
);
}
/** Sets the state with a new individual selection. */
private updateSelection(selection: IndiInfo) {
/** Sets the state with a new individual selection and chart type. */
private updateDisplay(selection: IndiInfo, chartType?: ChartType) {
if (
!this.state.selection ||
this.state.selection.id !== selection.id ||
this.state.selection!.generation !== selection.generation
this.state.selection!.generation !== selection.generation ||
(chartType !== undefined && chartType !== this.state.chartType)
) {
this.setState(
Object.assign({}, this.state, {
selection,
chartType: chartType !== undefined ? chartType : this.state.chartType,
}),
);
}
@ -179,6 +188,10 @@ export class App extends React.Component<RouteComponentProps, {}> {
const hash = getParam('file');
const handleCors = getParam('handleCors') !== 'false'; // True by default.
const standalone = getParam('standalone') !== 'false'; // True by default.
const view = getParam('view');
// Hourglass is the default view.
const chartType =
view === 'relatives' ? ChartType.Relatives : ChartType.Hourglass;
const gedcom = this.props.location.state && this.props.location.state.data;
const images =
@ -198,6 +211,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
loading: true,
url,
standalone,
chartType,
}),
);
const data = hash
@ -221,6 +235,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
url,
showSidePanel,
standalone,
chartType,
}),
);
} catch (error) {
@ -234,7 +249,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
indi,
generation,
);
this.updateSelection(selection);
this.updateDisplay(selection, chartType);
}
}
@ -246,7 +261,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
analyticsEvent('selection_changed');
if (this.state.embedded) {
// In embedded mode the URL doesn't change.
this.updateSelection(selection);
this.updateDisplay(selection);
return;
}
const location = this.props.location;
@ -263,8 +278,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
<div id="content">
<Chart
data={this.state.data.chartData}
onSelection={this.onSelection}
selection={this.state.selection}
chartType={this.state.chartType}
onSelection={this.onSelection}
ref={(ref) => (this.chartRef = ref)}
/>
{this.state.showSidePanel ? (

View File

@ -10,6 +10,7 @@ import {
createChart,
DetailedRenderer,
HourglassChart,
RelativesChart,
} from 'topola';
/** Called when the view is dragged with the mouse. */
@ -101,9 +102,16 @@ function canvasToBlob(canvas: HTMLCanvasElement, type: string) {
});
}
/** Supported chart types. */
export enum ChartType {
Hourglass,
Relatives,
}
export interface ChartProps {
data: JsonGedcomData;
selection: IndiInfo;
chartType: ChartType;
onSelection: (indiInfo: IndiInfo) => void;
}
@ -111,6 +119,18 @@ export interface ChartProps {
export class Chart extends React.PureComponent<ChartProps, {}> {
private chart?: ChartHandle;
private getChartType() {
switch (this.props.chartType) {
case ChartType.Hourglass:
return HourglassChart;
case ChartType.Relatives:
return RelativesChart;
default:
// Fall back to hourglass chart.
return HourglassChart;
}
}
/**
* Renders the chart or performs a transition animation to a new state.
* If indiInfo is not given, it means that it is the initial render and no
@ -121,7 +141,7 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
(d3.select('#chart').node() as HTMLElement).innerHTML = '';
this.chart = createChart({
json: this.props.data,
chartType: HourglassChart,
chartType: this.getChartType(),
renderer: DetailedRenderer,
svgSelector: '#chart',
indiCallback: (info) => this.props.onSelection(info),
@ -192,7 +212,10 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
}
componentDidUpdate(prevProps: ChartProps) {
this.renderChart({initialRender: this.props.data !== prevProps.data});
const initialRender =
this.props.data !== prevProps.data ||
this.props.chartType !== prevProps.chartType;
this.renderChart({initialRender});
}
/** Make intl appear in this.context. */

View File

@ -184,6 +184,16 @@ export class TopBar extends React.Component<
}
}
changeView(view: string) {
const location = this.props.location;
const search = queryString.parse(location.search);
if (search.view !== view) {
search.view = view;
location.search = queryString.stringify(search);
this.props.history.push(location);
}
}
componentDidMount() {
this.initializeSearchIndex();
}
@ -259,6 +269,7 @@ export class TopBar extends React.Component<
<Icon name="print" />
<FormattedMessage id="menu.print" defaultMessage="Print" />
</Menu.Item>
<Dropdown
trigger={
<div>
@ -280,6 +291,34 @@ export class TopBar extends React.Component<
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Dropdown
trigger={
<div>
<Icon name="eye" />
<FormattedMessage id="menu.view" defaultMessage="View" />
</div>
}
className="item"
>
<Dropdown.Menu>
<Dropdown.Item onClick={() => this.changeView('hourglass')}>
<Icon name="hourglass" />
<FormattedMessage
id="menu.hourglass"
defaultMessage="Hourglass chart"
/>
</Dropdown.Item>
<Dropdown.Item onClick={() => this.changeView('relatives')}>
<Icon name="users" />
<FormattedMessage
id="menu.relatives"
defaultMessage="All relatives"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Search
onSearchChange={debounce(
(_: React.MouseEvent<HTMLElement>, data: SearchProps) =>

View File

@ -6,6 +6,9 @@
"menu.pdf_file": "Plik PDF",
"menu.png_file": "Plik PNG",
"menu.svg_file": "Plik SVG",
"menu.view": "Widok",
"menu.hourglass": "Wykres klepsydrowy",
"menu.relatives": "Wszyscy krewni",
"menu.github": "Źródła na GitHub",
"menu.powered_by": "Topola Genealogy",
"menu.search.placeholder": "Szukaj osoby",