@@ -350,6 +350,10 @@ export function LineChart({
350350 const hardCodedSubregionReps = [ 'Finland' , 'Pakistan' , 'Japan' , 'Bulgaria' , 'Kazakhstan' ] ;
351351 const clusterRepsDataPath = '/sandbox/data/cluster_representatives.csv' ;
352352 const subregionRepsDataPath = '/sandbox/data/subregion_representatives.csv' ;
353+ // ---------------------------- Metadata (hardcoded countries) ---------------------------- //
354+ const metadataCountries = useMemo ( ( ) => (
355+ [ 'Sweden' , 'Latvia' , 'Denmark' , 'Finland' , 'Guernsey' ]
356+ ) , [ ] ) ;
353357
354358 useEffect ( ( ) => {
355359 if ( guardrail !== 'cluster' ) return ;
@@ -488,6 +492,18 @@ export function LineChart({
488492 return [ ...selY , ...clusterY ] ;
489493 }
490494
495+ if ( guardrail === 'metadata' ) {
496+ const selY = data
497+ . filter ( ( val ) => selection ?. includes ( val [ parameters . cat_var ] ) )
498+ . map ( ( d ) => + d [ parameters . y_var ] )
499+ . filter ( ( val ) => ! Number . isNaN ( val ) ) ;
500+ const metaY = data
501+ . filter ( ( val ) => metadataCountries . includes ( val [ parameters . cat_var ] ) )
502+ . map ( ( d ) => + d [ parameters . y_var ] )
503+ . filter ( ( val ) => ! Number . isNaN ( val ) ) ;
504+ return [ ...selY , ...metaY ] ;
505+ }
506+
491507 return data
492508 . filter ( ( val ) => selection ?. includes ( val [ parameters . cat_var ] ) )
493509 . map ( ( d ) => + d [ parameters . y_var ] )
@@ -517,7 +533,7 @@ export function LineChart({
517533 yMin : computedYMin - buffer ,
518534 yMax : computedYMax + buffer ,
519535 } ;
520- } , [ data , selection , randomCountries , medianIQRData , avgData , medianCountryData , parameters , guardrail , medianClosestData , medianIQRClosestData , percentileClosestData , filteredClusterReps ] ) ;
536+ } , [ data , selection , randomCountries , medianIQRData , avgData , medianCountryData , parameters , guardrail , medianClosestData , medianIQRClosestData , percentileClosestData , filteredClusterReps , metadataCountries ] ) ;
521537 const xScale = useMemo ( ( ) => {
522538 if ( range ) {
523539 return d3 . scaleTime ( [ margin . left , width + margin . left ] ) . domain ( range ) ;
@@ -673,6 +689,37 @@ export function LineChart({
673689 } ) . filter ( Boolean ) ;
674690 } , [ filteredClusterReps , data , xScale , yScale , parameters , guardrail , dataname ] ) ;
675691
692+ // ---------------------------- Metadata (hardcoded) ----------------------------
693+ const metadataLines = useMemo ( ( ) => {
694+ if ( guardrail !== 'metadata' ) return null ;
695+ const symbols = metadataCountries ;
696+
697+ return symbols . map ( ( symbol ) => {
698+ const values = data . filter ( ( d ) => d [ parameters . cat_var ] === symbol ) ;
699+ const lineGenerator = d3 . line < [ number , number ] > ( )
700+ . x ( ( d ) => xScale ( d [ 0 ] ) )
701+ . y ( ( d ) => yScale ( d [ 1 ] ) )
702+ . curve ( d3 . curveBasis ) ;
703+
704+ const parsedData : [ number , number ] [ ] = values
705+ . map ( ( d : any ) => {
706+ const dateObj = d3 . timeParse ( '%Y-%m-%d' ) ( d [ parameters . x_var ] ) ;
707+ const val = + d [ parameters . y_var ] ;
708+ if ( ! dateObj || Number . isNaN ( val ) ) return null ;
709+ return [ dateObj . getTime ( ) , val ] ;
710+ } )
711+ . filter ( ( d ) : d is [ number , number ] => d !== null ) ;
712+
713+ if ( parsedData . length === 0 ) return null ;
714+
715+ return {
716+ name : symbol ,
717+ path : lineGenerator ( parsedData ) ?? '' ,
718+ lastPoint : parsedData [ parsedData . length - 1 ] ,
719+ } ;
720+ } ) . filter ( Boolean ) ;
721+ } , [ data , xScale , yScale , parameters , guardrail , metadataCountries ] ) ;
722+
676723 // ---------------------------- All ----------------------------
677724 const allBackgroundLines = useMemo ( ( ) => {
678725 if ( guardrail !== 'all' ) return null ;
@@ -999,6 +1046,24 @@ export function LineChart({
9991046 ) ;
10001047 }
10011048
1049+ if ( metadataLines && guardrail === 'metadata' ) {
1050+ labels = labels . concat (
1051+ metadataLines
1052+ . filter ( ( line ) => line !== null )
1053+ . map ( ( line ) => {
1054+ if ( ! line ) return null ;
1055+ const item = items . find ( ( it ) => it . name === line . name ) ;
1056+ const subregion = item ?. subregion ? ` (${ item . subregion } )` : '' ;
1057+ return {
1058+ label : `${ line . name } ${ subregion } ` ,
1059+ y : yScale ( line . lastPoint [ 1 ] ) ,
1060+ color : darkGrayColor ,
1061+ } ;
1062+ } )
1063+ . filter ( ( label ) : label is { label : string ; y : number ; color : string } => label !== null ) ,
1064+ ) ;
1065+ }
1066+
10021067 labels = labels . filter ( ( l ) => typeof l . y === 'number' && ! Number . isNaN ( l . y ) ) . sort ( ( a , b ) => b . y - a . y ) ;
10031068
10041069 let prevY : number | undefined ;
@@ -1015,7 +1080,7 @@ export function LineChart({
10151080 return labels ;
10161081 } , [
10171082 selection , data , parameters , dataname , yScale , colorScale ,
1018- medianLineClosest , medianIQRClosestPaths , percentileClosestPaths , clusterLines , guardrail ,
1083+ medianLineClosest , medianIQRClosestPaths , percentileClosestPaths , clusterLines , guardrail , metadataLines ,
10191084 ] ) ;
10201085
10211086 // ---------------------------- store -----------------------------
@@ -1373,6 +1438,17 @@ export function LineChart({
13731438 </foreignObject> */ }
13741439 </ g >
13751440 ) : null ) ) }
1441+ { metadataLines ?. map ( ( line ) => ( line ? (
1442+ < g key = { `metadata-${ line . name } ` } >
1443+ < path
1444+ d = { line . path }
1445+ fill = "none"
1446+ stroke = { darkGrayColor }
1447+ strokeWidth = { 1.0 }
1448+ strokeDasharray = "2,2"
1449+ />
1450+ </ g >
1451+ ) : null ) ) }
13761452 { allBackgroundLines && (
13771453 < >
13781454 { allBackgroundLines . map ( ( x ) => ( x . path ? (
0 commit comments