1- import { StateModal } from "./StateModal.js" ;
1+ import * as d3 from "d3" ;
2+ import { StateModal } from "./StateModal" ;
3+
4+ interface MapConfig {
5+ center : [ number , number ] ;
6+ scale : number ;
7+ width : number ;
8+ height : number ;
9+ initialTransform : d3 . ZoomTransform | null ;
10+ }
11+
12+ interface CapitalData {
13+ name : string ;
14+ coordinates : [ number , number ] ;
15+ }
16+
17+ interface StateData {
18+ capital : CapitalData ;
19+ [ key : string ] : any ; // for other state properties
20+ }
221
322export class BharatMap {
4- // Main class handling the map visualization and interaction
5- constructor ( config ) {
23+ private config : MapConfig ;
24+ private stateData : Record < string , StateData > ;
25+ private projection : d3 . GeoProjection ;
26+ private path : d3 . GeoPath ;
27+ private zoom : d3 . ZoomBehavior < SVGSVGElement , unknown > ;
28+ private svg ! : d3 . Selection < SVGSVGElement , unknown , HTMLElement , any > ;
29+ private mapGroup ! : d3 . Selection < SVGGElement , unknown , HTMLElement , any > ;
30+ private initialTransform : d3 . ZoomTransform | null ;
31+
32+ constructor ( config : MapConfig ) {
633 this . config = config ;
734 this . stateData = { } ;
835 this . projection = this . createProjection ( ) ;
936 this . path = d3 . geoPath ( ) . projection ( this . projection ) ;
1037 this . zoom = d3
11- . zoom ( )
38+ . zoom < SVGSVGElement , unknown > ( )
1239 . scaleExtent ( [ 1 , 8 ] )
1340 . on ( "zoom" , ( event ) => this . handleZoom ( event ) ) ;
1441 this . initializeSVG ( ) ;
@@ -21,7 +48,7 @@ export class BharatMap {
2148 window . addEventListener ( "beforeunload" , this . cleanup ) ;
2249 }
2350
24- cleanup ( ) {
51+ private cleanup ( ) : void {
2552 window . removeEventListener ( "resize" , this . handleResize ) ;
2653 window . removeEventListener ( "beforeunload" , this . cleanup ) ;
2754 // Clear any D3 selections
@@ -31,9 +58,9 @@ export class BharatMap {
3158 }
3259
3360 // Initialize SVG container and set up zoom behavior
34- initializeSVG ( ) {
61+ private initializeSVG ( ) : void {
3562 this . svg = d3
36- . select ( ".map-container" )
63+ . select < SVGSVGElement , unknown > ( ".map-container" )
3764 . append ( "svg" )
3865 . attr ( "viewBox" , `0 0 ${ this . config . width } ${ this . config . height } ` )
3966 . attr ( "preserveAspectRatio" , "xMidYMid meet" ) ;
@@ -46,7 +73,7 @@ export class BharatMap {
4673 }
4774
4875 // Create Mercator projection for Bharat map
49- createProjection ( ) {
76+ private createProjection ( ) : d3 . GeoProjection {
5077 return d3
5178 . geoMercator ( )
5279 . center ( this . config . center )
@@ -55,9 +82,15 @@ export class BharatMap {
5582 }
5683
5784 // Load GeoJSON and state data from external files
58- async loadData ( ) {
85+ public async loadData ( ) : Promise < void > {
5986 try {
60- const [ geoData , states ] = await Promise . all ( [ d3 . json ( "data/bharat.geojson" ) , d3 . json ( "data/state_data.json" ) ] ) ;
87+ const [ geoData , states ] = await Promise . all ( [
88+ d3 . json < GeoJSON . FeatureCollection > ( "data/bharat.geojson" ) ,
89+ d3 . json < Record < string , StateData > > ( "data/state_data.json" )
90+ ] ) ;
91+
92+ if ( ! geoData || ! states ) throw new Error ( "Failed to load data" ) ;
93+
6194 this . stateData = states ;
6295 this . renderMap ( geoData ) ;
6396 this . renderCapitals ( ) ;
@@ -67,22 +100,24 @@ export class BharatMap {
67100 }
68101 }
69102
70- handleDataLoadError ( ) {
103+ private handleDataLoadError ( ) : void {
71104 // Error UI feedback
72105 const container = document . querySelector ( ".map-container" ) ;
73- container . innerHTML = `
74- <div class="error-message">
75- Failed to load map data. Please try refreshing the page.
76- </div>
77- ` ;
106+ if ( container ) {
107+ container . innerHTML = `
108+ <div class="error-message">
109+ Failed to load map data. Please try refreshing the page.
110+ </div>
111+ ` ;
112+ }
78113 }
79114
80115 // Handle zoom and pan events
81- handleZoom ( event ) {
82- this . mapGroup . attr ( "transform" , event . transform ) ;
116+ private handleZoom ( event : d3 . D3ZoomEvent < SVGSVGElement , unknown > ) : void {
117+ this . mapGroup . attr ( "transform" , event . transform . toString ( ) ) ;
83118 }
84119
85- renderMap ( geoData ) {
120+ private renderMap ( geoData : GeoJSON . FeatureCollection ) : void {
86121 if ( ! this . mapGroup ) return ;
87122
88123 this . mapGroup
@@ -92,19 +127,22 @@ export class BharatMap {
92127 . append ( "path" )
93128 . attr ( "d" , this . path )
94129 . attr ( "class" , "state" )
95- . on ( "click" , ( event , d ) => {
96- this . handleStateClick ( d . properties . st_nm ) ;
130+ . on ( "click" , ( event : MouseEvent , d : GeoJSON . Feature ) => {
131+ const properties = d . properties as { st_nm : string } ;
132+ this . handleStateClick ( properties . st_nm ) ;
97133 } ) ;
98134 }
99135
100- renderCapitals ( ) {
136+ private renderCapitals ( ) : void {
101137 const capitals = this . mapGroup . append ( "g" ) . attr ( "class" , "capitals" ) ;
102138
103139 Object . entries ( this . stateData ) . forEach ( ( [ stateName , data ] ) => {
104140 const coords = data . capital . coordinates ;
105141 if ( ! coords ) return ;
106142
107- const [ x , y ] = this . projection ( coords ) ;
143+ const projected = this . projection ( coords ) ;
144+ if ( ! projected ) return ;
145+ const [ x , y ] = projected ;
108146 if ( isNaN ( x ) || isNaN ( y ) ) return ;
109147
110148 const g = capitals . append ( "g" ) . attr ( "class" , "capital-group" ) . attr ( "data-state" , stateName ) ;
@@ -117,27 +155,27 @@ export class BharatMap {
117155 } ) ;
118156 }
119157
120- handleStateClick ( stateName ) {
158+ private handleStateClick ( stateName : string ) : void {
121159 this . highlightCapital ( stateName ) ;
122160 this . showStateModal ( stateName ) ;
123161 }
124162
125- highlightCapital ( stateName ) {
163+ private highlightCapital ( stateName : string ) : void {
126164 d3 . selectAll ( ".capital-marker, .capital-label" ) . classed ( "active" , false ) ;
127165
128166 d3 . selectAll ( `[data-state="${ stateName } "]` ) . classed ( "active" , true ) ;
129167 }
130168
131- showStateModal ( stateName ) {
169+ private showStateModal ( stateName : string ) : void {
132170 const modal = new StateModal ( this . stateData [ stateName ] , stateName ) ;
133171 modal . show ( ) ;
134172 }
135173
136- resetZoom ( ) {
174+ public resetZoom ( ) : void {
137175 this . svg . transition ( ) . duration ( 750 ) . call ( this . zoom . transform , d3 . zoomIdentity . scale ( 1 ) ) ;
138176 }
139177
140- handleResize ( ) {
178+ private handleResize ( ) : void {
141179 this . config . width = window . innerWidth - 60 ;
142180 this . config . height = window . innerHeight - 180 ;
143181
@@ -148,7 +186,7 @@ export class BharatMap {
148186 . scale ( this . config . scale )
149187 . translate ( [ this . config . width / 2 , this . config . height / 2 ] ) ;
150188
151- this . mapGroup . selectAll ( "path" ) . attr ( "d" , this . path ) ;
189+ this . mapGroup . selectAll ( "path" ) . attr ( "d" , ( d ) => this . path ( d as GeoJSON . Feature | GeoJSON . Geometry ) ) ;
152190
153191 // Update capital positions
154192 this . mapGroup . selectAll ( ".capital-group" ) . remove ( ) ;
0 commit comments