Skip to content

Commit

Permalink
feat(components): add confidence intervals to line and bar chart #111
Browse files Browse the repository at this point in the history
- adapt colors to colorblind friendly colors
  • Loading branch information
JonasKellerer committed Apr 16, 2024
1 parent 6e66e2d commit 7243c6d
Show file tree
Hide file tree
Showing 24 changed files with 470 additions and 69 deletions.
9 changes: 9 additions & 0 deletions components/package-lock.json

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

1 change: 1 addition & 0 deletions components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@lit/context": "^1.1.0",
"@lit/task": "^1.0.0",
"chart.js": "^4.4.2",
"chartjs-chart-error-bars": "^4.4.0",
"chartjs-chart-venn": "^4.3.0",
"dayjs": "^1.11.10",
"flatpickr": "^4.6.13",
Expand Down
45 changes: 45 additions & 0 deletions components/src/preact/components/confidence-interval-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type FunctionComponent } from 'preact';

import { Select } from './select';
import type { ConfidenceIntervalMethod } from '../shared/charts/confideceInterval';

type ConfidenceIntervalSelectorProps = {
confidenceIntervalMethod: ConfidenceIntervalMethod;
setConfidenceIntervalMethod: (method: ConfidenceIntervalMethod) => void;
confidenceIntervalMethods: ConfidenceIntervalMethod[];
};

export const ConfidenceIntervalSelector: FunctionComponent<ConfidenceIntervalSelectorProps> = ({
confidenceIntervalMethod,
setConfidenceIntervalMethod,
confidenceIntervalMethods,
}) => {
if (confidenceIntervalMethods.length === 0) {
return null;
}

const items = [
{ label: 'Confidence interval method', value: 'none', disabled: true },
...confidenceIntervalMethods.concat('none').map((method) => {
switch (method) {
case 'wilson':
return { label: 'Wilson, 95% CI', value: 'wilson' };
case 'none':
return { label: 'None', value: 'none' };
}
}),
];

return (
<Select
items={items}
selected={confidenceIntervalMethod === undefined ? 'none' : confidenceIntervalMethod}
onChange={(event: Event) => {
const select = event.target as HTMLSelectElement;
const value = select.value as ConfidenceIntervalMethod;
setConfidenceIntervalMethod(value);
}}
selectStyle={'select-xs select-bordered'}
/>
);
};
13 changes: 8 additions & 5 deletions components/src/preact/components/scaling-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ export const ScalingSelector: FunctionComponent<ScalingSelectorProps> = ({
setYAxisScaleType,
className,
}) => {
const items = [
{ label: 'y axis scaling type', value: 'none', disabled: true },
{ label: 'Linear', value: 'linear' },
{ label: 'Logarithmic', value: 'logarithmic' },
{ label: 'Logit', value: 'logit' },
];

return (
<Select
items={[
{ label: 'Linear', value: 'linear' },
{ label: 'Logarithmic', value: 'logarithmic' },
{ label: 'Logit', value: 'logit' },
]}
items={items}
selected={yAxisScaleType}
onChange={(event: Event) => {
const select = event.target as HTMLSelectElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Chart, type ChartConfiguration, registerables, type TooltipItem } from 'chart.js';
import { BarWithErrorBar, BarWithErrorBarsController } from 'chartjs-chart-error-bars';

import { type PrevalenceOverTimeData, type PrevalenceOverTimeVariantData } from '../../query/queryPrevalenceOverTime';
import GsChart from '../components/chart';
import { LogitScale } from '../shared/charts/LogitScale';
import { singleGraphColorRGBA } from '../shared/charts/colors';
import { type ConfidenceIntervalMethod, wilson95PercentConfidenceInterval } from '../shared/charts/confideceInterval';
import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';

interface PrevalenceOverTimeBarChartProps {
data: PrevalenceOverTimeData;
yAxisScaleType: ScaleType;
confidenceIntervalMethod: ConfidenceIntervalMethod;
}

Chart.register(...registerables, LogitScale, BarWithErrorBarsController, BarWithErrorBar);

const PrevalenceOverTimeBarChart = ({
data,
yAxisScaleType,
confidenceIntervalMethod,
}: PrevalenceOverTimeBarChartProps) => {
const config: ChartConfiguration = {
type: BarWithErrorBarsController.id,
data: {
labels: data[0]?.content.map((dateRange) => dateRange.dateRange?.toString() ?? 'Unknown') || [],
datasets: data.map((graphData, index) => datasets(graphData, index, confidenceIntervalMethod)),
},
options: {
animation: false,
scales: {
// @ts-expect-error-next-line -- chart.js typings are not complete with custom scales
y: getYAxisScale(yAxisScaleType),
},
plugins: {
legend: {
display: false,
},
tooltip: tooltip(confidenceIntervalMethod),
},
},
};

return <GsChart configuration={config} />;
};

const datasets = (
prevalenceOverTimeVariant: PrevalenceOverTimeVariantData,
index: number,
confidenceIntervalMethod: ConfidenceIntervalMethod,
) => {
const generalConfig = {
borderWidth: 1,
pointRadius: 0,
label: prevalenceOverTimeVariant.displayName,
backgroundColor: singleGraphColorRGBA(index, 0.3),
borderColor: singleGraphColorRGBA(index),
};

switch (confidenceIntervalMethod) {
case 'wilson':
return {
...generalConfig,
data: prevalenceOverTimeVariant.content.map((dataPoint) => ({
y: dataPoint.prevalence,
yMin: wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total).lowerLimit,
yMax: wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total).upperLimit,
})),
};
default:
return {
...generalConfig,
data: prevalenceOverTimeVariant.content.map((dataPoint) => dataPoint.prevalence),
};
}
};

const tooltip = (confidenceIntervalMethod: ConfidenceIntervalMethod) => {
const generalConfig = {
mode: 'index' as const,
intersect: false,
};

switch (confidenceIntervalMethod) {
case 'wilson':
return {
...generalConfig,
callbacks: {
label: (context: TooltipItem<'barWithErrorBars'>) => {
const value = context.dataset.data[context.dataIndex] as {
y: number;
yMin: number;
yMax: number;
};

return `${context.dataset.label}: ${value.y.toFixed(3)} (${value.yMin.toFixed(3)} - ${value.yMax.toFixed(3)})`;
},
},
};
default:
return generalConfig;
}
};

export default PrevalenceOverTimeBarChart;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { addUnit, minusTemporal } from '../../utils/temporal';
import { getMinMaxNumber } from '../../utils/utils';
import GsChart from '../components/chart';
import { LogitScale } from '../shared/charts/LogitScale';
import { singleGraphColorRGBA } from '../shared/charts/colors';
import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';

interface PrevalenceOverTimeBubbleChartProps {
Expand All @@ -25,7 +26,7 @@ const PrevalenceOverTimeBubbleChart = ({ data, yAxisScaleType }: PrevalenceOverT
const config: ChartConfiguration = {
type: 'bubble',
data: {
datasets: data.map((graphData) => ({
datasets: data.map((graphData, index) => ({
label: graphData.displayName,
data: graphData.content
.filter((dataPoint) => dataPoint.dateRange !== null)
Expand All @@ -36,6 +37,8 @@ const PrevalenceOverTimeBubbleChart = ({ data, yAxisScaleType }: PrevalenceOverT
})),
borderWidth: 1,
pointRadius: 0,
backgroundColor: singleGraphColorRGBA(index, 0.3),
borderColor: singleGraphColorRGBA(index),
})),
},
options: {
Expand Down

This file was deleted.

0 comments on commit 7243c6d

Please sign in to comment.