Skip to content

Commit 11a7dbb

Browse files
authored
Merge pull request #145 from ELIXIR-NO/update/map-trendChart
Update/map trend chart
2 parents b3fd544 + 3e068d0 commit 11a7dbb

File tree

4 files changed

+201
-35
lines changed

4 files changed

+201
-35
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"framer-motion": "^11.3.29",
4949
"install": "^0.13.0",
5050
"lucide-react": "^0.379.0",
51+
"ml-regression-simple-linear": "^3.0.1",
5152
"newick": "^2.0.0",
5253
"next": "15.1.7",
5354
"next-themes": "^0.3.0",

pnpm-lock.yaml

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/dashboards/norm-atlas/atlas.tsx

Lines changed: 154 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { NormDataRecord } from "@/lib/data/csvUtils";
44
import React, {
55
forwardRef,
6+
useEffect,
67
useImperativeHandle,
78
useMemo,
89
useRef,
@@ -46,6 +47,7 @@ import { geoPath, geoMercator, GeoProjection } from "d3-geo";
4647
import { GeoJson } from "@/lib/data/geojsonLoader";
4748
import * as turf from "@turf/turf";
4849
import { Checkbox } from "@/components/ui/checkbox";
50+
import { SimpleLinearRegression } from "ml-regression-simple-linear";
4951

5052
export default function Atlas({
5153
data,
@@ -574,9 +576,10 @@ function ResistanceChart({
574576
domain={[0, maxResistance]}
575577
unit="%"
576578
label={{
577-
value: "Resistens (%)",
579+
value: "Prosent resistente isolater",
578580
angle: -90,
579-
position: "insideLeft",
581+
position: "center",
582+
dx: -20,
580583
}}
581584
/>
582585
{hoveredRegion?.length !== undefined ? (
@@ -632,6 +635,24 @@ function ResistanceTrendChart({
632635
selectedDataSet,
633636
}: ResistanceTrendChartProps) {
634637
const [showLineChart, setShowLineChart] = useState(true);
638+
const [showRegression, setShowRegression] = useState(false);
639+
640+
const generateRegressionData = (data: YearDataEntry[]) => {
641+
const years = data.map((entry) => entry.year);
642+
643+
return selectedRegions.map((region, index) => {
644+
const regionDataX = years;
645+
const regionDataY = data.map((entry) => entry.y);
646+
647+
const regression = new SimpleLinearRegression(regionDataX, regionDataY);
648+
const regressionLine = regionDataX.map((xValue) => ({
649+
year: xValue,
650+
y: regression.predict(xValue),
651+
}));
652+
653+
return { region, regressionLine };
654+
});
655+
};
635656

636657
const chartData = useMemo<YearDataEntry[]>(() => {
637658
if (
@@ -665,12 +686,43 @@ function ResistanceTrendChart({
665686
const resistant = record.antall_R || 0;
666687

667688
if (total > 0) {
668-
yearEntry[region] = (resistant / total) * 100;
689+
yearEntry[region] = Number(((resistant / total) * 100).toFixed(1));
669690
yearEntry[`${region}-total`] = total;
670691
}
671692
});
672693

673-
return Array.from(yearData.values()).sort((a, b) => a.year - b.year);
694+
const yearArray = Array.from(yearData.values()).sort(
695+
(a, b) => a.year - b.year
696+
);
697+
698+
selectedRegions.forEach((region) => {
699+
const regionData = yearArray
700+
.map((entry) => ({
701+
year: entry.year,
702+
y: entry[region] ?? 0,
703+
}))
704+
.filter((point) => point.y !== 0);
705+
706+
const regressionLine = generateRegressionData(regionData).find(
707+
(line) => line.region === region
708+
)?.regressionLine;
709+
710+
if (regressionLine && regressionLine.length > 1) {
711+
const firstPoint = regressionLine.at(0);
712+
const lastPoint = regressionLine.at(-1);
713+
714+
[firstPoint, lastPoint].forEach((point) => {
715+
if (point) {
716+
const yearEntry = yearData.get(point.year);
717+
if (yearEntry) {
718+
yearEntry[`${region}-regression`] = point.y;
719+
}
720+
}
721+
});
722+
}
723+
});
724+
725+
return yearArray;
674726
}, [
675727
data,
676728
selectedMicrobe,
@@ -721,22 +773,62 @@ function ResistanceTrendChart({
721773
return null;
722774
}
723775

776+
const handleLineChartChange = (checked: boolean) => {
777+
if (checked) {
778+
setShowLineChart(true);
779+
setShowRegression(false);
780+
} else {
781+
setShowLineChart(false);
782+
}
783+
};
784+
785+
const handleRegressionChange = (checked: boolean) => {
786+
if (checked) {
787+
setShowRegression(true);
788+
setShowLineChart(false);
789+
} else {
790+
setShowRegression(false);
791+
}
792+
};
793+
724794
return (
725795
<div className="rounded-lg border bg-card p-4">
726-
<div className="mb-4 flex items-center space-x-2">
727-
<Checkbox
728-
id="show-line-chart"
729-
className="peer rounded-none border-2 border-gray-400"
730-
checked={showLineChart}
731-
onCheckedChange={(checked) => setShowLineChart(checked === true)}
732-
/>
733-
<label
734-
htmlFor="show-line-chart"
735-
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
736-
>
737-
Show line graph(Total Samples)
738-
</label>
796+
<div className="mb-4 flex items-center space-x-4">
797+
<div className="flex items-center space-x-2">
798+
<Checkbox
799+
id="show-line-chart"
800+
className="peer rounded-none border-2 border-gray-400"
801+
checked={showLineChart}
802+
onCheckedChange={(checked) =>
803+
handleLineChartChange(checked === true)
804+
}
805+
/>
806+
<label
807+
htmlFor="show-line-chart"
808+
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
809+
>
810+
Antall prøveisolater
811+
</label>
812+
</div>
813+
814+
<div className="flex items-center space-x-2">
815+
<Checkbox
816+
id="show-regression-line"
817+
className="peer rounded-none border-2 border-gray-400"
818+
checked={showRegression}
819+
onCheckedChange={(checked) =>
820+
handleRegressionChange(checked === true)
821+
}
822+
/>
823+
<label
824+
htmlFor="show-regression-line"
825+
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
826+
>
827+
Lineær regresjon
828+
</label>
829+
</div>
739830
</div>
831+
740832
<ChartContainer config={chartConfig} className="aspect-video w-full">
741833
<ComposedChart
742834
data={chartData}
@@ -750,26 +842,31 @@ function ResistanceTrendChart({
750842
domain={[0, maxResistance]}
751843
unit="%"
752844
label={{
753-
value: "Resistens (%)",
845+
value: "Prosent resistente isolater",
754846
angle: -90,
755-
position: "insideLeft",
847+
position: "center",
848+
dx: -20,
756849
}}
757850
yAxisId="left"
758851
orientation="left"
759852
/>
760-
<YAxis
761-
tickLine={false}
762-
fontSize={12}
763-
domain={[0, maxTotal]}
764-
label={{
765-
value: "Resistens",
766-
angle: -90,
767-
position: "insideRight",
768-
}}
769-
yAxisId="right"
770-
orientation="right"
771-
/>
772-
<ChartTooltip content={<ChartTooltipContent />} />
853+
{showLineChart && (
854+
<YAxis
855+
tickLine={false}
856+
fontSize={12}
857+
domain={[0, maxTotal]}
858+
label={{
859+
value: "Antall prøveisolater",
860+
angle: -90,
861+
position: "center",
862+
dx: 20,
863+
}}
864+
yAxisId="right"
865+
orientation="right"
866+
/>
867+
)}
868+
<ChartTooltip content={<ChartTooltipContent trendChart={true} />} />
869+
773870
<Legend></Legend>
774871
{selectedRegions.map((region) => (
775872
<Bar
@@ -782,6 +879,7 @@ function ResistanceTrendChart({
782879
opacity={0.7}
783880
/>
784881
))}
882+
785883
{showLineChart &&
786884
selectedRegions.map((region) => (
787885
<Line
@@ -790,6 +888,23 @@ function ResistanceTrendChart({
790888
type="monotone"
791889
dataKey={`${region}-total`}
792890
stroke={regionColors[region]}
891+
legendType="none"
892+
tooltipType="none"
893+
strokeWidth={2}
894+
/>
895+
))}
896+
897+
{showRegression &&
898+
selectedRegions.map((region) => (
899+
<Line
900+
yAxisId="left"
901+
key={`${region}-regression`}
902+
type="monotone"
903+
dataKey={`${region}-regression`}
904+
stroke={regionColors[region]}
905+
strokeWidth={2}
906+
legendType="none"
907+
connectNulls={true}
793908
/>
794909
))}
795910
</ComposedChart>
@@ -864,6 +979,12 @@ export const MyChart = forwardRef<SVGSVGElement, MyChartProps>(
864979

865980
useImperativeHandle(ref, () => chartRef.current!);
866981

982+
const [forceRender, setForceRender] = useState(false);
983+
984+
useEffect(() => {
985+
setForceRender(true);
986+
}, []);
987+
867988
useMemo(() => {
868989
if (!chartRef.current || !geoData) return;
869990

@@ -1154,6 +1275,7 @@ export const MyChart = forwardRef<SVGSVGElement, MyChartProps>(
11541275
selectedAntibiotic,
11551276
selectedDataSet,
11561277
selectedYear,
1278+
forceRender,
11571279
]);
11581280

11591281
return (

0 commit comments

Comments
 (0)