back-testing-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +2 -0
  2. package/dist/cjs/index.js +132 -0
  3. package/dist/cjs/index.js.map +1 -0
  4. package/dist/esm/index.js +132 -0
  5. package/dist/esm/index.js.map +1 -0
  6. package/dist/types.d.ts +2 -0
  7. package/package.json +49 -0
  8. package/rollup.config.js +42 -0
  9. package/src/components/back-testing-cat-legend/back-testing-cat-legend.css +36 -0
  10. package/src/components/back-testing-cat-legend/back-testing-cat-legend.stories.tsx +12 -0
  11. package/src/components/back-testing-cat-legend/back-testing-cat-legend.tsx +23 -0
  12. package/src/components/back-testing-cat-legend/back-testing-cat-legend.types.ts +3 -0
  13. package/src/components/back-testing-cat-legend/index.ts +1 -0
  14. package/src/components/back-testing-map/back-testing-map.css +39 -0
  15. package/src/components/back-testing-map/back-testing-map.service.ts +63 -0
  16. package/src/components/back-testing-map/back-testing-map.stories.tsx +427 -0
  17. package/src/components/back-testing-map/back-testing-map.tsx +503 -0
  18. package/src/components/back-testing-map/back-testing-map.types.ts +176 -0
  19. package/src/components/back-testing-map/index.ts +1 -0
  20. package/src/components/back-testing-storm-legend/back-testing-storm-legend.css +3 -0
  21. package/src/components/back-testing-storm-legend/back-testing-storm-legend.stories.tsx +98 -0
  22. package/src/components/back-testing-storm-legend/back-testing-storm-legend.tsx +28 -0
  23. package/src/components/back-testing-storm-legend/back-testing-storm-legend.types.ts +6 -0
  24. package/src/components/back-testing-storm-legend/index.ts +1 -0
  25. package/src/components/index.ts +3 -0
  26. package/src/index.ts +1 -0
  27. package/src/stories/Button.stories.ts +53 -0
  28. package/src/stories/Button.tsx +37 -0
  29. package/src/stories/Configure.mdx +364 -0
  30. package/src/stories/Header.stories.ts +33 -0
  31. package/src/stories/Header.tsx +56 -0
  32. package/src/stories/Page.stories.ts +32 -0
  33. package/src/stories/Page.tsx +73 -0
  34. package/src/stories/assets/accessibility.png +0 -0
  35. package/src/stories/assets/accessibility.svg +1 -0
  36. package/src/stories/assets/addon-library.png +0 -0
  37. package/src/stories/assets/assets.png +0 -0
  38. package/src/stories/assets/avif-test-image.avif +0 -0
  39. package/src/stories/assets/context.png +0 -0
  40. package/src/stories/assets/discord.svg +1 -0
  41. package/src/stories/assets/docs.png +0 -0
  42. package/src/stories/assets/figma-plugin.png +0 -0
  43. package/src/stories/assets/github.svg +1 -0
  44. package/src/stories/assets/share.png +0 -0
  45. package/src/stories/assets/styling.png +0 -0
  46. package/src/stories/assets/testing.png +0 -0
  47. package/src/stories/assets/theming.png +0 -0
  48. package/src/stories/assets/tutorials.svg +1 -0
  49. package/src/stories/assets/youtube.svg +1 -0
  50. package/src/stories/button.css +30 -0
  51. package/src/stories/header.css +32 -0
  52. package/src/stories/page.css +69 -0
  53. package/tsconfig.json +20 -0
@@ -0,0 +1,503 @@
1
+ import { BackTestingActions, BackTestingMapProps, Payout, CIAC_LAYER, CIAC_SOURCE, Constants, DEFAULT_MAP_COORDINATE, DEFAULT_MAP_ZOOM, generateCATColor, MapView, TROPICAL_STORM_LAYER, TROPICAL_STORM_SOURCE, TROPICAL_STORM_TRACK_LAYER, TROPICAL_STORM_TRACK_SOURCE, generateCIACRequest, generateProxyRequest, generateAnemometerRequest, PayoutRequest, randomColor } from "./back-testing-map.types";
2
+ import { GeoJsonProperties, Feature, Polygon, Geometry, LineString, Point, FeatureCollection } from "geojson";
3
+ import "mapbox-gl/dist/mapbox-gl.css";
4
+ import "./back-testing-map.css"
5
+ import { useEffect, useRef, useState } from "react";
6
+ import mapboxgl, { GeoJSONSource } from 'mapbox-gl';
7
+ import { LngLatLike } from "@mapbox/search-js-core";
8
+ import * as turf from "@turf/turf";
9
+
10
+ import React, { forwardRef, useImperativeHandle } from "react";
11
+ import { fetchPayouts, StormPayout } from "./back-testing-map.service";
12
+ import BackTestingCatLegend from "../back-testing-cat-legend";
13
+ import BackTestingStormLegend from "../back-testing-storm-legend";
14
+ import { ToggleButton, ToggleButtonGroup } from "@mui/material";
15
+
16
+
17
+ const BackTestingMap = forwardRef<BackTestingActions, BackTestingMapProps>((props,ref) => {
18
+
19
+ const mapInstanceRef = useRef<mapboxgl.Map | undefined>(undefined);
20
+ const mapContainerRef = useRef<any>(undefined);
21
+ const [mapLoaded, setMapLoaded] = useState(false);
22
+
23
+ const [locationMarker, setLocationMarker] = useState<mapboxgl.Marker>(new mapboxgl.Marker());
24
+ const [catCircle, setCatCircle] = useState<Feature<Polygon, GeoJsonProperties> | undefined>(undefined);
25
+ const [mapView, setMapView] = useState<MapView>(MapView.CAT_INTENSITY);
26
+ const [payouts, setPayouts] = useState<StormPayout[]>([]);
27
+
28
+ const toggleView = (
29
+ event: React.MouseEvent<HTMLElement>,
30
+ newView: MapView,
31
+ ) => {
32
+ if(newView !== null){
33
+ setMapView(newView);
34
+ }
35
+ toggleStormTracks(newView);
36
+ if(props.onViewChange != undefined){
37
+ props.onViewChange(newView);
38
+ }
39
+ };
40
+
41
+ useImperativeHandle(ref, () => ({
42
+ calculatePayouts(proxyPayouts: Payout[], anemometerCode: string, anemometerPayouts: Payout[], ciacPayouts: Payout[], includeNoNamedStorms: boolean, includeZeroPayouts: boolean, limit: number){
43
+ calculatePayouts(proxyPayouts, anemometerCode, anemometerPayouts, ciacPayouts, includeNoNamedStorms, includeZeroPayouts, limit);
44
+ }
45
+ }));
46
+
47
+ function calculatePayouts(proxyPayouts: Payout[], anemometerCode: string, anemometerPayouts: Payout[], ciacPayouts: Payout[], includeNoNamedStorms: boolean, includeZeroPayouts: boolean, limit: number){
48
+ if(props.ciacRadius != undefined && props.ciacRadius != null && props.location != undefined && props.location != null){
49
+ let latLng : LngLatLike = [props.location.longitude,props.location.latitude];
50
+ const proxy = generateProxyRequest(latLng as [number,number], 1000, proxyPayouts);
51
+ const anemometer = generateAnemometerRequest(latLng as [number,number], anemometerCode, anemometerPayouts);
52
+ const ciac = generateCIACRequest(latLng as [number,number], props.ciacRadius, includeNoNamedStorms, ciacPayouts);
53
+ let request : PayoutRequest = {
54
+ includeZeroPayouts: includeZeroPayouts,
55
+ limit: limit,
56
+ latitude: props.location.latitude,
57
+ longitude: props.location.longitude,
58
+ proxy: proxy,
59
+ anemometer: anemometer,
60
+ ciac: ciac
61
+ }
62
+ fetchPayouts(request, props.apiAccessToken).then((data) => {
63
+ let storms = data.payouts.map((storm,index) => { storm.color = randomColor(index); return storm})
64
+ setPayouts(storms);
65
+ if(props.onCalculatePayouts != undefined){
66
+ props.onCalculatePayouts(storms);
67
+ }
68
+ }, (err) => {
69
+ console.error(err);
70
+ throw Error(err);
71
+ })
72
+ }
73
+ }
74
+
75
+ function maskDateString(value:string){
76
+ let date = new Date(Date.parse(value));
77
+ return date.toLocaleDateString('en-us', { day:"numeric", year:"numeric", month:"short", hour:"numeric", minute:"numeric", timeZone: "UTC"})
78
+ }
79
+
80
+ function maskStormCategory(cat:number): string {
81
+ switch(cat){
82
+ case 5:
83
+ case 4:
84
+ case 3:
85
+ case 2:
86
+ case 1:
87
+ return `Category ${cat}`
88
+ case 0:
89
+ return "Tropical Storm"
90
+ case -1:
91
+ return "Tropical Depression"
92
+ case -2:
93
+ return "Tropical Expression"
94
+ case -3:
95
+ return "Tropical Expression"
96
+ case -4:
97
+ return "Tropical Expression"
98
+ case -5:
99
+ return "Tropical Expression"
100
+ case -10:
101
+ return "N/A"
102
+ default:
103
+ return ""
104
+ }
105
+ }
106
+
107
+ const toggleStormTracks = (view: MapView) => {
108
+ if(mapInstanceRef && mapInstanceRef.current){
109
+ const circleSource = (mapInstanceRef.current as mapboxgl.Map).getSource(TROPICAL_STORM_SOURCE)
110
+ if (circleSource){
111
+ let features = (((circleSource as GeoJSONSource)._data as unknown) as FeatureCollection).features;
112
+ let newData = features.map((feature) => {
113
+ return {
114
+ type: feature.type,
115
+ geometry: feature.geometry,
116
+ properties:{
117
+ view: view,
118
+ muted: feature.properties?.muted,
119
+ name : feature.properties?.name,
120
+ year: feature.properties?.year,
121
+ windspeed: feature.properties?.windspeed,
122
+ category: feature.properties?.category,
123
+ time: feature.properties?.time,
124
+ latitude: feature.properties?.latitude,
125
+ longitude: feature.properties?.longitude,
126
+ location: feature.properties?.location,
127
+ checked: feature.properties?.checked,
128
+ color: feature.properties?.color }}
129
+ }) as Feature<Geometry, GeoJsonProperties>[]
130
+ (circleSource as GeoJSONSource).setData({type: 'FeatureCollection', features: newData})
131
+ }
132
+ const lineSource = (mapInstanceRef.current as mapboxgl.Map).getSource(TROPICAL_STORM_TRACK_SOURCE)
133
+ if (lineSource){
134
+ let features = (((lineSource as GeoJSONSource)._data as unknown) as FeatureCollection).features;
135
+ let newData = features.map((feature) => {
136
+ return {
137
+ type: feature.type,
138
+ geometry: feature.geometry,
139
+ properties:{
140
+ view: view,
141
+ muted: feature.properties?.muted,
142
+ name : feature.properties?.name,
143
+ year: feature.properties?.year,
144
+ category: feature.properties?.category,
145
+ checked: feature.properties?.checked,
146
+ color: feature.properties?.color }}
147
+ }) as Feature<Geometry, GeoJsonProperties>[]
148
+ (lineSource as GeoJSONSource).setData({type: 'FeatureCollection', features: newData})
149
+ }
150
+ }
151
+ }
152
+
153
+ function updateStormTracks(storms: StormPayout[], view: MapView){
154
+ if(mapInstanceRef && mapInstanceRef.current){
155
+ let index = 0;
156
+ let tracks = storms.map((cyclone) => {
157
+ let stormColor = randomColor(index)
158
+ index = index + 1
159
+ return cyclone.stormTrack.map((storm) => {
160
+ return {
161
+ type: 'Feature',
162
+ geometry: storm.LOCATION,
163
+ properties:{
164
+ view: view,
165
+ muted: false,
166
+ checked: true,
167
+ name : storm.NAME,
168
+ year: storm.SEASON,
169
+ windspeed: storm.USA_WIND,
170
+ category: storm.USA_SSHS,
171
+ time: storm.ISO_TIME,
172
+ latitude: storm.LOCATION.coordinates.at(1),
173
+ longitude: storm.LOCATION.coordinates.at(0),
174
+ location: storm.LOCATION,
175
+ color: stormColor }
176
+ } as Feature
177
+ })
178
+ })
179
+ let lines = tracks.map((track) => {return pointsToLines(track)})
180
+
181
+ const circleSource = (mapInstanceRef.current as mapboxgl.Map).getSource(TROPICAL_STORM_SOURCE)
182
+ if (circleSource){
183
+ (circleSource as GeoJSONSource).setData({type: 'FeatureCollection', features: tracks.flat()})
184
+ }
185
+ const lineSource = (mapInstanceRef.current as mapboxgl.Map).getSource(TROPICAL_STORM_TRACK_SOURCE)
186
+ if (lineSource){
187
+ (lineSource as GeoJSONSource).setData({type: 'FeatureCollection', features: lines.flat()})
188
+ }
189
+ }
190
+ }
191
+
192
+ function pointsToLines(points:Feature<Geometry, GeoJsonProperties>[]): Feature<LineString, GeoJsonProperties>[]{
193
+ let retVal : Feature<LineString, GeoJsonProperties>[] = []
194
+ for(var i = 1; i < points.length; i++){
195
+ let strength = ((points[i].properties?.category as number) + (points[i-1].properties?.category as number))/2
196
+ let point1 = (points[i-1].geometry as Point).coordinates;
197
+ let point2 = (points[i].geometry as Point).coordinates;
198
+ if(point1[0] < 0 || point2[0] < 0){
199
+ let one = trueModulo(point1[0],360)
200
+ let two = trueModulo(point2[0],360)
201
+ point1[0] = one
202
+ point2[0] = two
203
+ }
204
+ retVal.push({
205
+ type: 'Feature',
206
+ geometry:{type:'LineString',coordinates:[point1,point2]},
207
+ properties:{
208
+ 'name': points[i].properties?.name,
209
+ 'year': points[i].properties?.year,
210
+ 'category': strength,
211
+ 'muted': points[i].properties?.muted,
212
+ 'checked': true,
213
+ 'view': points[i].properties?.view,
214
+ 'color': points[i].properties?.color}})
215
+ }
216
+ return retVal
217
+ }
218
+
219
+ function trueModulo(num: number, divisor: number): number {
220
+ return ((num % divisor) + divisor) % divisor;
221
+ }
222
+
223
+ useEffect(() => {
224
+
225
+ mapboxgl.accessToken = props.mapAccessToken;
226
+
227
+ mapInstanceRef.current = new mapboxgl.Map({
228
+ container: mapContainerRef.current,
229
+ style: 'mapbox://styles/adubu001/cm23vizjd00gk01qk59q062mj',
230
+ pitchWithRotate: false,
231
+ dragRotate: false,
232
+ touchZoomRotate: false,
233
+ center: props.location ? [props.location.longitude,props.location.latitude] : DEFAULT_MAP_COORDINATE,
234
+ zoom: DEFAULT_MAP_ZOOM
235
+ });
236
+
237
+ mapInstanceRef.current.on("load", () => {
238
+ setMapLoaded(true);
239
+
240
+ mapInstanceRef.current?.addSource( CIAC_SOURCE, {
241
+ type: 'geojson',
242
+ data: {
243
+ type: 'FeatureCollection',
244
+ features: []
245
+ }
246
+ });
247
+
248
+ mapInstanceRef.current?.addSource( TROPICAL_STORM_SOURCE, {
249
+ type: 'geojson',
250
+ data: {
251
+ type: 'FeatureCollection',
252
+ features: []
253
+ }
254
+ });
255
+
256
+ mapInstanceRef.current?.addSource( TROPICAL_STORM_TRACK_SOURCE, {
257
+ type: 'geojson',
258
+ data: {
259
+ type: 'FeatureCollection',
260
+ features: []
261
+ }
262
+ });
263
+
264
+ mapInstanceRef.current?.addLayer({
265
+ id: CIAC_LAYER,
266
+ type: 'fill',
267
+ source: CIAC_SOURCE,
268
+ paint: {
269
+ 'fill-color': '#105397',
270
+ 'fill-opacity': 0.35,
271
+ },
272
+ });
273
+
274
+ mapInstanceRef.current?.addLayer({
275
+ id: 'outline',
276
+ type: 'line',
277
+ source: CIAC_SOURCE,
278
+ paint: {
279
+ 'line-color': '#105397',
280
+ 'line-width': 2
281
+ }
282
+ });
283
+
284
+ mapInstanceRef.current?.addLayer({
285
+ id: TROPICAL_STORM_TRACK_LAYER,
286
+ type: 'line',
287
+ source: TROPICAL_STORM_TRACK_SOURCE,
288
+ paint: {
289
+ 'line-color': [
290
+ 'match',
291
+ ["concat",
292
+ ["get","view"],
293
+ ["get", "muted"],
294
+ ],
295
+ MapView.CAT_INTENSITY + "false",
296
+ ['interpolate',
297
+ ['linear'],
298
+ ['get', 'category'],
299
+ -5,
300
+ Constants.COLOR_TROPICAL_EXPRESSION,
301
+ -4,
302
+ Constants.COLOR_TROPICAL_EXPRESSION,
303
+ -3,
304
+ Constants.COLOR_TROPICAL_EXPRESSION,
305
+ -2,
306
+ Constants.COLOR_TROPICAL_EXPRESSION,
307
+ -1,
308
+ Constants.COLOR_TROPICAL_DEPRESSION,
309
+ 0,
310
+ Constants.COLOR_TROPICAL_STORM,
311
+ 1,
312
+ Constants.COLOR_CAT_1,
313
+ 2,
314
+ Constants.COLOR_CAT_2,
315
+ 3,
316
+ Constants.COLOR_CAT_3,
317
+ 4,
318
+ Constants.COLOR_CAT_4,
319
+ 5,
320
+ Constants.COLOR_CAT_5
321
+ ],
322
+ MapView.STORMS + "false",
323
+ ["get","color"],
324
+ "rgba(255,255,255,0.2)"
325
+ ],
326
+ 'line-width': 4,
327
+ },
328
+ });
329
+
330
+ mapInstanceRef.current?.addLayer({
331
+ id: TROPICAL_STORM_LAYER,
332
+ type: 'circle',
333
+ source: TROPICAL_STORM_SOURCE,
334
+ paint: {
335
+ 'circle-stroke-color': [
336
+ 'match',
337
+ ["concat",
338
+ "",
339
+ ["get", "muted"],
340
+ ],
341
+ 'false',
342
+ '#000',
343
+ 'rgba(0,0,0,0.2)'
344
+ ],
345
+ 'circle-stroke-width': 1,
346
+ 'circle-color': [
347
+ 'match',
348
+ ["concat",
349
+ ["get", "view"],
350
+ ["get", "category"],
351
+ ["get", "muted"],
352
+ ],
353
+ MapView.CAT_INTENSITY + "-5false",Constants.COLOR_TROPICAL_EXPRESSION,
354
+ MapView.CAT_INTENSITY + "-4false",Constants.COLOR_TROPICAL_EXPRESSION,
355
+ MapView.CAT_INTENSITY + "-3false",Constants.COLOR_TROPICAL_EXPRESSION,
356
+ MapView.CAT_INTENSITY + "-2false",Constants.COLOR_TROPICAL_EXPRESSION,
357
+ MapView.CAT_INTENSITY + "-1false",Constants.COLOR_TROPICAL_DEPRESSION,
358
+ MapView.CAT_INTENSITY + "0false",Constants.COLOR_TROPICAL_STORM,
359
+ MapView.CAT_INTENSITY + "1false",Constants.COLOR_CAT_1,
360
+ MapView.CAT_INTENSITY + "2false",Constants.COLOR_CAT_2,
361
+ MapView.CAT_INTENSITY + "3false",Constants.COLOR_CAT_3,
362
+ MapView.CAT_INTENSITY + "4false",Constants.COLOR_CAT_4,
363
+ MapView.CAT_INTENSITY + "5false",Constants.COLOR_CAT_5,
364
+ MapView.STORMS + "-5false",["get","color"],
365
+ MapView.STORMS + "-4false",["get","color"],
366
+ MapView.STORMS + "-3false",["get","color"],
367
+ MapView.STORMS + "-2false",["get","color"],
368
+ MapView.STORMS + "-1false",["get","color"],
369
+ MapView.STORMS + "0false",["get","color"],
370
+ MapView.STORMS + "1false",["get","color"],
371
+ MapView.STORMS + "2false",["get","color"],
372
+ MapView.STORMS + "3false",["get","color"],
373
+ MapView.STORMS + "4false",["get","color"],
374
+ MapView.STORMS + "5false",["get","color"],
375
+ Constants.COLOR_MUTED
376
+ ],
377
+ 'circle-radius': 5,
378
+ },
379
+ });
380
+
381
+ const popup = new mapboxgl.Popup({
382
+ closeButton: false,
383
+ closeOnClick: true,
384
+ closeOnMove:true,
385
+ });
386
+
387
+ mapInstanceRef.current?.on('mouseenter', TROPICAL_STORM_LAYER, (e: any) => {
388
+ if(mapInstanceRef.current){
389
+ mapInstanceRef.current.getCanvas().style.cursor = 'pointer';
390
+
391
+ const coordinates = e.features[0].geometry.coordinates.slice();
392
+ const properties = e.features[0].properties;
393
+ let description = '';
394
+ description = description + `<strong>${properties.name} ${properties.year}</strong>`
395
+ description = description + `<span style="border-radius:3px;margin-left:5px;padding:3px 10px;color:white;background-color:${generateCATColor(properties.category)}">${maskStormCategory(properties.category)}</span>`
396
+ description = description + `<div style="width:150px">${maskDateString(properties.time)}</div><p>`
397
+ description = description + `<div><strong>Windspeed: </strong><span">${properties.windspeed} MPH</span></div>`
398
+ description = description + `<div><strong>LatLng: </strong><span">(${properties.latitude}°, ${properties.longitude}°)</span></div>`
399
+ description = description + `</p>`
400
+
401
+ while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
402
+ coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
403
+ }
404
+
405
+ popup.setLngLat(coordinates).setHTML(description).addTo(mapInstanceRef.current);
406
+ popup.on('mouseleave', () => {
407
+ if(mapInstanceRef.current){
408
+ mapInstanceRef.current.getCanvas().style.cursor = '';
409
+ }
410
+ popup.remove();
411
+ });
412
+ }
413
+ });
414
+
415
+ mapInstanceRef.current?.on('mouseleave', TROPICAL_STORM_LAYER, () => {
416
+ if(mapInstanceRef.current){
417
+ mapInstanceRef.current.getCanvas().style.cursor = '';
418
+ }
419
+ popup.remove();
420
+ });
421
+
422
+ });
423
+ return () => mapInstanceRef.current?.remove();
424
+
425
+ }, []);
426
+
427
+ useEffect(() => {
428
+ if(mapLoaded){
429
+ if(props.location != undefined && props.location != null){
430
+ if(mapInstanceRef?.current){
431
+ let latLng : LngLatLike = [props.location.longitude,props.location.latitude];
432
+ setLocationMarker(locationMarker.remove().setLngLat(latLng).addTo(mapInstanceRef.current));
433
+ mapInstanceRef.current.flyTo({center:latLng,essential:true})
434
+ }
435
+ } else {
436
+ setLocationMarker(locationMarker.remove());
437
+ }
438
+ }
439
+ },[props.location, mapInstanceRef, mapLoaded])
440
+
441
+ useEffect(() => {
442
+ if(mapLoaded){
443
+ if(props.ciacRadius != undefined && props.ciacRadius != null && props.location != undefined && props.location != null){
444
+ if(mapInstanceRef?.current){
445
+ let latLng : LngLatLike = [props.location.longitude,props.location.latitude];
446
+ var circle = turf.circle(latLng as [number,number], props.ciacRadius, { steps:200, units: "miles"});
447
+ setCatCircle(catCircle)
448
+
449
+ const source = mapInstanceRef.current.getSource(CIAC_SOURCE)
450
+ if (source){
451
+ console.log("draw circle");
452
+ (source as GeoJSONSource).setData({type: 'FeatureCollection', features: [circle]})
453
+ }
454
+ }
455
+ } else {
456
+ console.log("skip circle")
457
+ if(mapInstanceRef?.current){
458
+ setCatCircle(undefined)
459
+ const source = mapInstanceRef.current.getSource(CIAC_SOURCE)
460
+ if (source){
461
+ (source as GeoJSONSource).setData({type: 'FeatureCollection', features: []})
462
+ }
463
+ }
464
+ }
465
+ }
466
+ },[props.ciacRadius, props.location, mapInstanceRef, mapLoaded])
467
+
468
+ useEffect(() => {
469
+ if(mapLoaded){
470
+ updateStormTracks(payouts,mapView)
471
+ }
472
+ },[mapLoaded,payouts,mapView])
473
+
474
+ useEffect(() => {
475
+ if(mapLoaded){
476
+ if(props.location && props.limit && props.proxyPayouts && props.anemometerCode && props.anemometerPayouts && props.ciacRadius && props.ciacRadius && mapInstanceRef){
477
+ calculatePayouts(props.proxyPayouts, props.anemometerCode, props.anemometerPayouts, props.ciacPayouts, false, false, props.limit)
478
+ }
479
+ }
480
+ },[props.location, props.limit, props.proxyPayouts, props.anemometerCode, props.anemometerPayouts, props.ciacRadius, props.ciacRadius, mapInstanceRef, mapLoaded])
481
+
482
+ return(
483
+ <div className="map__wrapper">
484
+ {mapView == MapView.CAT_INTENSITY &&<BackTestingCatLegend className="cat__legend"/>}
485
+ <ToggleButtonGroup
486
+ className='map__views'
487
+ color="primary"
488
+ value={mapView}
489
+ exclusive
490
+ onChange={toggleView}
491
+ aria-label="Platform"
492
+ sx={{backgroundColor:'white', zIndex: 10}}
493
+ >
494
+ <ToggleButton value={MapView.CAT_INTENSITY}>CAT Strength View</ToggleButton>
495
+ <ToggleButton value={MapView.STORMS}>Storms View</ToggleButton>
496
+ </ToggleButtonGroup>
497
+ {mapView == MapView.STORMS && <BackTestingStormLegend stormTracks={payouts}/>}
498
+ <div ref={mapContainerRef} className="map__container" />
499
+ </div>
500
+ );
501
+ })
502
+
503
+ export default BackTestingMap
@@ -0,0 +1,176 @@
1
+ import mapboxgl from 'mapbox-gl';
2
+ import { LngLatLike } from "@mapbox/search-js-core"
3
+ import { StormPayout } from './back-testing-map.service';
4
+
5
+ export interface Payout {
6
+ category: number,
7
+ payout: number
8
+ }
9
+
10
+ export interface Shape {
11
+ type: "__circle";
12
+ latitude: number | undefined;
13
+ longitude: number | undefined;
14
+ radius: number | undefined;
15
+ }
16
+
17
+ export interface ProxyRequest {
18
+ radius: number;
19
+ latitude: number | undefined;
20
+ longitude: number | undefined;
21
+ payouts: Payout[] | undefined;
22
+ }
23
+
24
+ export interface AnemometerRequest {
25
+ code: string
26
+ latitude: number | undefined;
27
+ longitude: number | undefined;
28
+ payouts: Payout[] | undefined;
29
+ }
30
+
31
+ export interface CIACRequest {
32
+ includeNotNamedStorms: boolean;
33
+ shapes: Shape[];
34
+ payouts: Payout[] | undefined;
35
+ }
36
+
37
+ export interface PayoutRequest {
38
+ limit: number;
39
+ latitude: number | undefined;
40
+ longitude: number | undefined;
41
+ includeZeroPayouts: boolean;
42
+ proxy: ProxyRequest;
43
+ anemometer: AnemometerRequest;
44
+ ciac: CIACRequest;
45
+ }
46
+
47
+ export interface BackTestingMapProps {
48
+ mapAccessToken: string,
49
+ apiAccessToken: string,
50
+ location: {longitude: number, latitude: number } | undefined | null,
51
+ limit: number | undefined | null,
52
+ ciacRadius: number | undefined | null,
53
+ anemometerCode: string,
54
+ proxyPayouts: Payout[],
55
+ anemometerPayouts: Payout[],
56
+ ciacPayouts: Payout[],
57
+ includeNoNameStorms?: boolean,
58
+ includeZeroPayouts?: boolean,
59
+ onViewChange?: (view: MapView) => void
60
+ onCalculatePayouts?: (payouts: StormPayout[]) => void
61
+ }
62
+
63
+ export interface BackTestingActions {
64
+ calculatePayouts: (proxyPayouts: Payout[], anemometerCode: string, anemometerPayouts: Payout[], ciacPayouts: Payout[], includeNoNameStorms: boolean, includeZeroPayouts: boolean, limit: number) => void,
65
+ }
66
+
67
+ export enum MapView {
68
+ CAT_INTENSITY = '__cat_view',
69
+ STORMS = '__storms_view'
70
+ }
71
+
72
+ export enum Constants {
73
+ COLOR_CAT_5 = "rgba(252, 92, 13, 1)", // red-orange
74
+ COLOR_CAT_5_MUTED = "rgba(255, 255, 255, 0.5)", // red-orange
75
+
76
+ COLOR_CAT_4 = "rgba(250, 141, 14, 1)", // orange
77
+ COLOR_CAT_4_MUTED = "rgba(255, 255, 255, 0.5)", // orange
78
+
79
+ COLOR_CAT_3 = "rgba(247, 190, 15, 1)", // orange-yellow
80
+ COLOR_CAT_3_MUTED = "rgba(255, 255, 255, 0.5)", // orange-yellow
81
+
82
+ COLOR_CAT_2 = "rgba(246, 215, 15, 1)", // yellow
83
+ COLOR_CAT_2_MUTED = "rgba(255, 255, 255, 0.5)", // yellow
84
+
85
+ COLOR_CAT_1 = "rgba(192, 232, 38, 1)", // yellow-green
86
+ COLOR_CAT_1_MUTED = "rgba(255, 255, 255, 0.5)", // yellow-green
87
+
88
+ COLOR_TROPICAL_STORM = "rgba(156, 244, 53, 1)", // lime-green
89
+ COLOR_TROPICAL_STORM_MUTED = "rgba(255, 255, 255, 0.5)", // lime-green
90
+
91
+ COLOR_TROPICAL_DEPRESSION = "rgba(120, 255, 68,1)", // bright-green
92
+ COLOR_TROPICAL_DEPRESSION_MUTED = "rgba(255, 255, 255, 0.5)", // bright-green
93
+
94
+ COLOR_TROPICAL_EXPRESSION = "rgba(120, 255, 68,1)", // bright-green
95
+ COLOR_TROPICAL_EXPRESSION_MUTED = "rgba(255, 255, 255, 0.5)", // bright-green
96
+
97
+ COLOR_MUTED = "rgba(255, 255, 255, 0.5)", // bright-green
98
+ }
99
+
100
+ export const DEFAULT_MAP_COORDINATE : LngLatLike = [-70.9,42.35]
101
+ export const DEFAULT_MAP_ZOOM : number = 8
102
+
103
+ // map data sources
104
+ export const CIAC_SOURCE = '__ciac_data'
105
+ export const TROPICAL_STORM_SOURCE = '__tropical_storms_data'
106
+ export const TROPICAL_STORM_TRACK_SOURCE = '__tropical_storm_tracks_data'
107
+
108
+ //map visual layers
109
+ export const CIAC_LAYER = '__ciac_layer'
110
+ export const TROPICAL_STORM_LAYER = '__tropical_storms_layer'
111
+ export const TROPICAL_STORM_TRACK_LAYER = '__tropical_storm_tracks_layer'
112
+
113
+ export function generateCATColor(cat:number): string {
114
+ switch(cat){
115
+ case 5:
116
+ return Constants.COLOR_CAT_5
117
+ case 4:
118
+ return Constants.COLOR_CAT_4
119
+ case 3:
120
+ return Constants.COLOR_CAT_3
121
+ case 2:
122
+ return Constants.COLOR_CAT_2
123
+ case 1:
124
+ return Constants.COLOR_CAT_1
125
+ case 0:
126
+ return Constants.COLOR_TROPICAL_STORM
127
+ case -1:
128
+ return Constants.COLOR_TROPICAL_DEPRESSION
129
+ case -2:
130
+ return Constants.COLOR_TROPICAL_EXPRESSION
131
+ default:
132
+ return Constants.COLOR_TROPICAL_EXPRESSION
133
+ }
134
+ }
135
+
136
+ export function randomColor(index:number): string {
137
+ var hue = (Math.floor(index) * 120) + (Math.round((index * 120)/360) * 30);
138
+ return `hsla(${hue}, 90%, 60%, 1)`;
139
+ }
140
+
141
+ export function generateProxyRequest(latLng: [number,number], radius: number, payouts: Payout[] | undefined ){
142
+ let coord = (latLng);
143
+ let proxy : ProxyRequest = {
144
+ radius: radius,
145
+ latitude: coord[1],
146
+ longitude: coord[0],
147
+ payouts: payouts
148
+ }
149
+ return proxy
150
+ }
151
+
152
+ export function generateAnemometerRequest(latLng: [number,number], anemometerCode:string, payouts: Payout[] | undefined){
153
+ let coord = (latLng);
154
+ let anemometer : AnemometerRequest = {
155
+ code:anemometerCode,
156
+ latitude: coord[1],
157
+ longitude: coord[0],
158
+ payouts: payouts
159
+ }
160
+ return anemometer
161
+ }
162
+
163
+ export function generateCIACRequest(latLng: [number,number], radius: number | undefined, includeNoNameStorms: boolean, payouts: Payout[] | undefined){
164
+ let coord = (latLng);
165
+ let ciac : CIACRequest = {
166
+ shapes:[{
167
+ type: "__circle",
168
+ latitude: coord[1],
169
+ longitude: coord[0],
170
+ radius: radius
171
+ }],
172
+ includeNotNamedStorms: includeNoNameStorms,
173
+ payouts: payouts
174
+ }
175
+ return ciac
176
+ }
@@ -0,0 +1 @@
1
+ export {default} from './back-testing-map';
@@ -0,0 +1,3 @@
1
+ .storm-legend{
2
+ top: 80px;
3
+ }