33import { NormDataRecord } from "@/lib/data/csvUtils" ;
44import React , {
55 forwardRef ,
6+ useEffect ,
67 useImperativeHandle ,
78 useMemo ,
89 useRef ,
@@ -46,6 +47,7 @@ import { geoPath, geoMercator, GeoProjection } from "d3-geo";
4647import { GeoJson } from "@/lib/data/geojsonLoader" ;
4748import * as turf from "@turf/turf" ;
4849import { Checkbox } from "@/components/ui/checkbox" ;
50+ import { SimpleLinearRegression } from "ml-regression-simple-linear" ;
4951
5052export 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