Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for choropleth map & clean up chart types #112

Merged
merged 7 commits into from
Sep 28, 2023
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
55 changes: 49 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bento_public",
"version": "0.14.1",
"version": "0.15.0",
"description": "A publicly accessible portal for clinical datasets, where users are able to see high-level statistics of the data available through predefined variables of interest and search the data using limited variables at a time. This portal allows users to gain a generic understanding of the data available (secure and firewalled) without the need to access it directly. Initially, this portal facilitates the search in English language only, but the French language will be added at a later time.",
"main": "index.js",
"scripts": {
Expand All @@ -19,17 +19,19 @@
"@reduxjs/toolkit": "^1.9.3",
"antd": "^5.6.2",
"axios": "^1.4.0",
"bento-charts": "^2.3.0",
"bento-charts": "^2.4.1",
"css-loader": "^6.8.1",
"dotenv": "^16.3.1",
"i18next": "^23.2.2",
"i18next-browser-languagedetector": "^7.0.2",
"i18next-http-backend": "^2.2.1",
"leaflet": "^1.9.4",
"less": "^4.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^13.0.0",
"react-icons": "^4.9.0",
"react-leaflet": "^4.2.1",
"react-redux": "^8.1.1",
"react-router-dom": "^6.13.0",
"recharts": "^2.7.1",
Expand Down
104 changes: 66 additions & 38 deletions src/js/components/Overview/Chart.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,84 @@
import React from 'react';
import React, { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { chartTypes } from '@/constants/overviewConstants';
import { useNavigate } from 'react-router-dom';
import { BarChart, PieChart } from 'bento-charts';
import { ChoroplethMap } from 'bento-charts/dist/maps';

import { CHART_HEIGHT } from '@/constants/overviewConstants';
import { ChartData } from '@/types/data';
import { useNavigate } from 'react-router-dom';
import { CHART_TYPE_BAR, CHART_TYPE_CHOROPLETH, CHART_TYPE_PIE, ChartConfig } from '@/types/chartConfig';

const Chart = ({ chartType, data, units, id }: ChartProps) => {
const Chart = memo(({ chartConfig, data, units, id }: ChartProps) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const translateMap = ({ x, y }: { x: string; y: number }) => ({ x: t(x), y });
const removeMissing = ({ x }: { x: string }) => x !== 'missing';

const renderChartSwitch = () => {
switch (chartType) {
case chartTypes.BAR:
// bar charts can be rendered slightly larger as they do not clip
return (
<BarChart
data={data}
height={CHART_HEIGHT + 30}
units={units}
preFilter={removeMissing}
dataMap={translateMap}
onClick={(d) => {
navigate(`/${i18n.language}/search?${id}=${d.payload.x}`);
}}
/>
);
case chartTypes.PIE:
return (
<PieChart
data={data}
height={CHART_HEIGHT}
preFilter={removeMissing}
dataMap={translateMap}
onClick={(d) => {
navigate(`/${i18n.language}/search?${id}=${d.name}`);
}}
/>
);
default:
return <p>chart type doesnt exists</p>;
const { chart_type: type } = chartConfig;

switch (type) {
case CHART_TYPE_BAR:
// bar charts can be rendered slightly larger as they do not clip
return (
<BarChart
data={data}
height={CHART_HEIGHT + 50}
units={units}
preFilter={removeMissing}
dataMap={translateMap}
onClick={(d) => {
navigate(`/${i18n.language}/search?${id}=${d.payload.x}`);
}}
/>
);
case CHART_TYPE_PIE:
return (
<PieChart
data={data}
height={CHART_HEIGHT}
preFilter={removeMissing}
dataMap={translateMap}
onClick={(d) => {
navigate(`/${i18n.language}/search?${id}=${d.name}`);
}}
/>
);
case CHART_TYPE_CHOROPLETH: {
// map charts can be rendered at full height as they do not clip
const { category_prop: categoryProp, features, center, zoom, color_mode: colorMode } = chartConfig;
return (
<ChoroplethMap
data={data}
height={CHART_HEIGHT + 50}
preFilter={removeMissing}
dataMap={translateMap}
categoryProp={categoryProp}
features={features}
center={center}
zoom={zoom}
colorMode={colorMode}
onClick={(d) => {
const val = d.properties?.[categoryProp];
if (val === undefined) return;
navigate(`/${i18n.language}/search?${id}=${val}`);
}}
renderPopupBody={(_f, d) => (
<>
Count: {d} {units}
</>
)}
/>
);
}
};
default:
return <p>chart type does not exist</p>;
}
});

return <>{renderChartSwitch()}</>;
};
Chart.displayName = 'Chart';

export interface ChartProps {
chartType: string;
chartConfig: ChartConfig;
data: ChartData[];
units: string;
id: string;
Expand Down
42 changes: 28 additions & 14 deletions src/js/components/Overview/Drawer/ChartTree.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { Tree, TreeProps } from 'antd';
import { useTranslation } from 'react-i18next';
Expand All @@ -7,27 +7,41 @@ import { rearrange, setDisplayedCharts } from '@/features/data/data.store';
import { NON_DEFAULT_TRANSLATION } from '@/constants/configConstants';
import { ChartDataField } from '@/types/data';

interface MappedChartItem {
title: string;
key: string;
}

const ChartTree = ({ charts, section }: ChartTreeProps) => {
const dispatch = useDispatch();
const { t } = useTranslation(NON_DEFAULT_TRANSLATION);

const allCharts = charts.map(({ title, id }) => ({ title: t(title), key: id }));
const allCharts: MappedChartItem[] = useMemo(
() => charts.map(({ field: { title }, id }) => ({ title: t(title), key: id })),
[charts]
);

const onChartDrop: TreeProps['onDrop'] = (info) => {
const originalLocation = parseInt(info.dragNode.pos.substring(2));
const newLocation = info.dropPosition - 1;
const onChartDrop: TreeProps['onDrop'] = useMemo(() => {
const fn: TreeProps['onDrop'] = (event) => {
const originalLocation = parseInt(event.dragNode.pos.substring(2));
const newLocation = event.dropPosition - 1;

const data = [...allCharts];
const element = data.splice(originalLocation, 1)[0];
data.splice(newLocation, 0, element);
dispatch(rearrange({ section, arrangement: data.map((e) => e.key) }));
};
const data = [...(allCharts ?? [])];
const element = data.splice(originalLocation, 1)[0];
data.splice(newLocation, 0, element);
dispatch(rearrange({ section, arrangement: data.map((e) => e.key) }));
};
return fn;
}, [dispatch, allCharts, section]);

const checkedKeys = charts.filter((e) => e.isDisplayed).map((e) => e.id);
const checkedKeys = useMemo(() => charts.filter((e) => e.isDisplayed).map((e) => e.id), [charts]);

const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => {
dispatch(setDisplayedCharts({ section, charts: checkedKeysValue }));
};
const onCheck = useMemo(() => {
const fn: TreeProps['onCheck'] = (checkedKeysValue) => {
dispatch(setDisplayedCharts({ section, charts: checkedKeysValue }));
};
return fn;
}, [dispatch, section]);

return (
<Tree
Expand Down
16 changes: 11 additions & 5 deletions src/js/components/Overview/MakeChartCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { memo } from 'react';
import Chart from './Chart';
import { Card, Button, Tooltip, Space, Typography, Row } from 'antd';
import { CloseOutlined, TeamOutlined, QuestionOutlined } from '@ant-design/icons';
Expand All @@ -9,11 +9,15 @@ import { ChartDataField } from '@/types/data';

const CARD_STYLE = { width: '430px', height: '415px', margin: '5px 0', borderRadius: '11px' };

const MakeChartCard = ({ section, chart, onRemoveChart }: MakeChartCardProps) => {
const MakeChartCard = memo(({ section, chart, onRemoveChart }: MakeChartCardProps) => {
const { t } = useTranslation(NON_DEFAULT_TRANSLATION);
const { t: td } = useTranslation(DEFAULT_TRANSLATION);

const { title, data, chartType, config, id, description } = chart;
const {
data,
field: { id, description, title, config },
chartConfig,
} = chart;

const extraOptionsData = [
{
Expand Down Expand Up @@ -54,7 +58,7 @@ const MakeChartCard = ({ section, chart, onRemoveChart }: MakeChartCardProps) =>
<div key={id} style={{ height: '100%', width: '430px' }}>
<Card title={t(title)} style={CARD_STYLE} size="small" extra={<Space size="small">{ed}</Space>}>
{data.filter((e) => !(e.x === 'missing')).length !== 0 ? (
<Chart chartType={chartType} data={data} units={config?.units || ''} id={id} />
<Chart chartConfig={chartConfig} data={data} units={config?.units || ''} id={id} />
) : (
<Row style={{ height: '350px ' }} justify="center" align="middle">
<CustomEmpty text="No Data" />
Expand All @@ -63,7 +67,9 @@ const MakeChartCard = ({ section, chart, onRemoveChart }: MakeChartCardProps) =>
</Card>
</div>
);
};
});

MakeChartCard.displayName = 'MakeChartCard';

export interface MakeChartCardProps {
section: string;
Expand Down
Loading