@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
22import { flushSync } from 'svelte' ;
33
44import { scaleBand } from 'd3-scale' ;
5+ import { geoAlbersUsa } from 'd3-geo' ;
56import { timeDay } from 'd3-time' ;
67
78import { ChartState } from './chart.svelte.js' ;
@@ -11,6 +12,7 @@ import { isScaleBand, isScaleTime } from '$lib/utils/scales.svelte.js';
1112type TestData = { date : string ; value : number } ;
1213type MultiSeriesData = { date : string ; apples : number ; bananas : number } ;
1314type WideData = { year : string ; apples : number ; bananas : number ; cherries : number ; grapes : number } ;
15+ type GeoData = { name : string ; longitude : number ; latitude : number } ;
1416
1517function createChartState < T = TestData > ( props : Partial < ChartPropsWithoutHTML < T > > ) {
1618 let cleanup : ( ) => void ;
@@ -603,6 +605,130 @@ describe('ChartState mark registration', () => {
603605 } ) ;
604606} ) ;
605607
608+ describe ( 'ChartState geo projection skips markInfo' , ( ) => {
609+ const geoData : GeoData [ ] = [
610+ { name : 'New York' , longitude : - 74.006 , latitude : 40.7128 } ,
611+ { name : 'Los Angeles' , longitude : - 118.2437 , latitude : 34.0522 } ,
612+ { name : 'Chicago' , longitude : - 87.6298 , latitude : 41.8781 } ,
613+ ] ;
614+
615+ it ( 'should not create implicit series from marks when geo projection is active' , ( ) => {
616+ const { state, cleanup } = createChartState < GeoData > ( {
617+ data : geoData ,
618+ x : 'longitude' ,
619+ y : 'latitude' ,
620+ geo : { projection : geoAlbersUsa } ,
621+ } ) ;
622+
623+ try {
624+ // Register a mark with its own data (like a tooltip highlight Circle)
625+ state . registerMark ( { data : [ geoData [ 0 ] ] , x : 'longitude' , y : 'latitude' } ) ;
626+ flushSync ( ) ;
627+
628+ // Should remain default series — mark should not create implicit "latitude" series
629+ expect ( state . seriesState . isDefaultSeries ) . toBe ( true ) ;
630+ } finally {
631+ cleanup ( ) ;
632+ }
633+ } ) ;
634+
635+ it ( 'should not add mark data to flatData when geo projection is active' , ( ) => {
636+ const { state, cleanup } = createChartState < GeoData > ( {
637+ data : geoData ,
638+ x : 'longitude' ,
639+ y : 'latitude' ,
640+ geo : { projection : geoAlbersUsa } ,
641+ } ) ;
642+
643+ try {
644+ state . registerMark ( { data : [ geoData [ 0 ] ] , x : 'longitude' , y : 'latitude' } ) ;
645+ flushSync ( ) ;
646+
647+ // flatData should only contain chart data, not the mark's extra data
648+ expect ( state . flatData ) . toHaveLength ( 3 ) ;
649+ expect ( state . flatData ) . toBe ( geoData ) ;
650+ } finally {
651+ cleanup ( ) ;
652+ }
653+ } ) ;
654+
655+ it ( 'should not derive x/y accessors from marks when geo projection is active' , ( ) => {
656+ // Chart with geo but no explicit x/y — marks should not fill in the accessors
657+ const { state : stateWithGeo , cleanup : cleanupGeo } = createChartState < GeoData > ( {
658+ data : geoData ,
659+ geo : { projection : geoAlbersUsa } ,
660+ } ) ;
661+
662+ const { state : stateWithoutGeo , cleanup : cleanupNoGeo } = createChartState < GeoData > ( {
663+ data : geoData ,
664+ } ) ;
665+
666+ try {
667+ // Both start with null x accessor (no x prop set)
668+ expect ( stateWithGeo . x ) . toBeNull ( ) ;
669+ expect ( stateWithoutGeo . x ) . toBeNull ( ) ;
670+
671+ stateWithGeo . registerMark ( { x : 'longitude' , y : 'latitude' } ) ;
672+ stateWithoutGeo . registerMark ( { x : 'longitude' , y : 'latitude' } ) ;
673+ flushSync ( ) ;
674+
675+ // Without geo: mark should derive x accessor
676+ expect ( stateWithoutGeo . x ) . not . toBeNull ( ) ;
677+ expect ( stateWithoutGeo . x ! ( geoData [ 0 ] ) ) . toBe ( geoData [ 0 ] . longitude ) ;
678+
679+ // With geo: mark should NOT derive x accessor
680+ expect ( stateWithGeo . x ) . toBeNull ( ) ;
681+ } finally {
682+ cleanupGeo ( ) ;
683+ cleanupNoGeo ( ) ;
684+ }
685+ } ) ;
686+
687+ it ( 'should preserve seriesKey/color/label from marks in geo mode for legends' , ( ) => {
688+ const { state, cleanup } = createChartState < GeoData > ( {
689+ data : geoData ,
690+ x : 'longitude' ,
691+ y : 'latitude' ,
692+ geo : { projection : geoAlbersUsa } ,
693+ } ) ;
694+
695+ try {
696+ state . registerMark ( { seriesKey : 'earthquakes' , color : 'red' , label : 'Earthquakes' } ) ;
697+ state . registerMark ( { seriesKey : 'volcanos' , color : 'orange' , label : 'Volcanos' } ) ;
698+ flushSync ( ) ;
699+
700+ // seriesKey/color/label should still create implicit series for legends
701+ expect ( state . seriesState . isDefaultSeries ) . toBe ( false ) ;
702+ expect ( state . seriesState . series ) . toHaveLength ( 2 ) ;
703+ expect ( state . seriesState . series [ 0 ] ) . toMatchObject ( { key : 'earthquakes' , color : 'red' , label : 'Earthquakes' } ) ;
704+ expect ( state . seriesState . series [ 1 ] ) . toMatchObject ( { key : 'volcanos' , color : 'orange' , label : 'Volcanos' } ) ;
705+
706+ // But flatData should not include extra mark data
707+ expect ( state . flatData ) . toHaveLength ( 3 ) ;
708+ } finally {
709+ cleanup ( ) ;
710+ }
711+ } ) ;
712+
713+ it ( 'should still process marks normally without geo projection' , ( ) => {
714+ const { state, cleanup } = createChartState < GeoData > ( {
715+ data : geoData ,
716+ x : 'name' ,
717+ } ) ;
718+
719+ try {
720+ state . registerMark ( { y : 'latitude' , color : 'blue' } ) ;
721+ flushSync ( ) ;
722+
723+ // Without geo, marks should create implicit series as normal
724+ expect ( state . seriesState . isDefaultSeries ) . toBe ( false ) ;
725+ expect ( state . seriesState . series [ 0 ] . key ) . toBe ( 'latitude' ) ;
726+ } finally {
727+ cleanup ( ) ;
728+ }
729+ } ) ;
730+ } ) ;
731+
606732describe ( 'ChartState implicit series domain update on visibility toggle' , ( ) => {
607733 it ( 'should update y domain when hiding an implicit series' , ( ) => {
608734 const data : MultiSeriesData [ ] = [
0 commit comments