From b4932ca772bf7d13a0896903f2c63546af2eb638 Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Tue, 8 Aug 2023 12:34:39 -0500 Subject: [PATCH 1/9] Create new map chart using leaflet --- angular.json | 2 +- package-lock.json | 42 + package.json | 2 + .../src/lib/common/charts/chart.component.ts | 6 +- src/app/app.component.html | 11 + src/app/app.component.ts | 20 + src/app/app.module.ts | 4 +- src/app/chartTypes.ts | 10 + .../map-chart/map-chart.component.html | 1 + .../map-chart/map-chart.component.scss | 5 + .../map-chart/map-chart.component.ts | 187 ++++ src/app/custom-charts/map-chart/us.svg | 438 ++++++++ src/app/custom-charts/map-chart/world.svg | 977 ++++++++++++++++++ 13 files changed, 1701 insertions(+), 4 deletions(-) create mode 100644 src/app/custom-charts/map-chart/map-chart.component.html create mode 100644 src/app/custom-charts/map-chart/map-chart.component.scss create mode 100644 src/app/custom-charts/map-chart/map-chart.component.ts create mode 100644 src/app/custom-charts/map-chart/us.svg create mode 100644 src/app/custom-charts/map-chart/world.svg diff --git a/angular.json b/angular.json index 43dfc4fe9..77b9ed1e9 100644 --- a/angular.json +++ b/angular.json @@ -26,7 +26,7 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], + "styles": ["node_modules/leaflet/dist/leaflet.css", "src/styles.scss"], "scripts": [], "allowedCommonJsDependencies": [ "emoji-flags", diff --git a/package-lock.json b/package-lock.json index 9e6134791..199adfc15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "d3-time-format": "^3.0.0", "d3-transition": "^3.0.1", "emoji-flags": "^1.2.0", + "leaflet": "^1.9.4", "moment-timezone": "^0.5.40", "ng-in-viewport": "^6.1.5", "ngx-moment": "^5.0.0", @@ -65,6 +66,7 @@ "@types/jasmine": "^3.6.0", "@types/jasminewd2": "~2.0.3", "@types/json-schema": "^7.0.4", + "@types/leaflet": "^1.9.3", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.11.0", "@typescript-eslint/parser": "5.11.0", @@ -3925,6 +3927,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "node_modules/@types/http-proxy": { "version": "1.17.7", "dev": true, @@ -3951,6 +3959,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/leaflet": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.3.tgz", + "integrity": "sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "12.19.9", "dev": true, @@ -10668,6 +10685,11 @@ "node": ">= 8" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, "node_modules/less": { "version": "4.1.2", "dev": true, @@ -19909,6 +19931,12 @@ "version": "0.0.50", "dev": true }, + "@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "@types/http-proxy": { "version": "1.17.7", "dev": true, @@ -19931,6 +19959,15 @@ "version": "7.0.6", "dev": true }, + "@types/leaflet": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.3.tgz", + "integrity": "sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/node": { "version": "12.19.9", "dev": true @@ -24306,6 +24343,11 @@ "version": "2.0.5", "dev": true }, + "leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, "less": { "version": "4.1.2", "dev": true, diff --git a/package.json b/package.json index 140b8ff56..11032e543 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "d3-time-format": "^3.0.0", "d3-transition": "^3.0.1", "emoji-flags": "^1.2.0", + "leaflet": "^1.9.4", "moment-timezone": "^0.5.40", "ng-in-viewport": "^6.1.5", "ngx-moment": "^5.0.0", @@ -87,6 +88,7 @@ "@types/jasmine": "^3.6.0", "@types/jasminewd2": "~2.0.3", "@types/json-schema": "^7.0.4", + "@types/leaflet": "^1.9.3", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.11.0", "@typescript-eslint/parser": "5.11.0", diff --git a/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts b/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts index 3f31ae76e..e810e6ef5 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts @@ -16,9 +16,10 @@ import { ScaleType } from '../types/scale-type.enum'; selector: 'ngx-charts-chart', template: `
- - + + + (); @Output() legendLabelActivate = new EventEmitter<{ name: string }>(); diff --git a/src/app/app.component.html b/src/app/app.component.html index 6dd4b53cc..992fd8af8 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -597,6 +597,17 @@ (select)="onSelect($event)" > + + +
+
+ + `, + styleUrls: ['./map-chart.component.scss', '../../../../projects/swimlane/ngx-charts/src/lib/common/base-chart.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class MapChartComponent extends BaseChartComponent implements OnInit { + @Input() legend = false; + @Input() legendTitle: string = 'Legend'; + @Input() legendPosition: string = 'right'; + + @Output() activate: EventEmitter = new EventEmitter(); + @Output() deactivate: EventEmitter = new EventEmitter(); + + dims: ViewDimensions; + transform: string; + colors: ColorHelper; + margin: any[] = [10, 20, 10, 20]; + legendOptions: any; + legendWidth + mapInitialize: boolean = false; + map: any; + markersLayer: any; + domain: any[]; + filteredDomain: any[]; + + trackBy(index, item): string { + return `${item.name}`; + } + + update(): void { + super.update(); + this.dims = calculateViewDimensions({ + width: this.width, + height: this.height, + margins: this.margin, + showXAxis: false, + showYAxis: false, + showXLabel: false, + showYLabel: false, + showLegend: false, + legendType: this.schemeType, + legendPosition: this.legendPosition as any + }); + console.log(this.legend) + + this.domain = this.getDomain(); + if (this.filteredDomain) { + this.domain = this.filteredDomain; + } + + this.colors = new ColorHelper(this.scheme, this.schemeType, this.domain, this.customColors); + console.log() + + this.addMarkers(); + + let legendColumns = 0; + if (this.legend) { + + if (this.legendPosition === LegendPosition.Right) { + legendColumns = 2; + } + } + + const chartColumns = 12 - legendColumns; + + const chartWidth = Math.floor((this.view[0] * chartColumns) / 12.0); + this.legendWidth = + !this.legendOptions || this.legendOptions.position === LegendPosition.Right + ? Math.floor((this.view[0] * legendColumns) / 12.0) + : chartWidth; + + this.transform = `translate(${this.dims.xOffset} , ${this.margin[0]})`; + } + + ngOnInit() { + this.mapInit(); + } + + + mapInit(): void { + this.map = L.map('map', { + center: [ 39.8282, -98.5795 ], + zoom: 3 + }); + + setTimeout(() => { + this.map.invalidateSize(true); + }, 0); + + const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 18, + minZoom: 3, + attribution: '© OpenStreetMap' + }); + + tiles.addTo(this.map); + + this.markersLayer = L.layerGroup(); + this.markersLayer.addTo(this.map); + + this.mapInitialize = true; + } + + getDomain(): any[] { + let values = []; + for (const d of this.results) { + values.push(d.name); + } + return values; + } + + addMarkers(): void { + if (!this.mapInitialize) return; + this.markersLayer.clearLayers(); + for (const d of this.results) { + if (this.domain.includes(d.name)) { + for (const coord of d.value) { + const markerHtmlStyles = ` + background-color: ${this.colors.getColor(d.name)}; + width: 2rem; + height: 2rem; + display: block; + left: -1rem; + top: -1rem; + position: relative; + border-radius: 2rem 2rem 0; + transform: rotate(45deg); + border: 1px solid #FFFFFF` + + const icon = L.divIcon({ + className: "my-custom-pin", + iconAnchor: [0, 24], + tooltipAnchor: [-6, 0], + popupAnchor: [0, -36], + html: `` + }) + + this.markersLayer.addLayer(L.marker(coord, {icon: icon})); + } + } + } + } + + onMapReady(map: L.Map) { + setTimeout(() => { + map.invalidateSize(); + }, 0); + } + + onClick(data) { + this.select.emit(data); + } +} diff --git a/src/app/custom-charts/map-chart/us.svg b/src/app/custom-charts/map-chart/us.svg new file mode 100644 index 000000000..708ea1148 --- /dev/null +++ b/src/app/custom-charts/map-chart/us.svg @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/custom-charts/map-chart/world.svg b/src/app/custom-charts/map-chart/world.svg new file mode 100644 index 000000000..84c8e242e --- /dev/null +++ b/src/app/custom-charts/map-chart/world.svg @@ -0,0 +1,977 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 37d68f052203c06d80ef764d5b788671a2314869 Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Tue, 8 Aug 2023 16:01:41 -0500 Subject: [PATCH 2/9] Add legend and option to toggle markers --- .../lib/bar-chart/bar-vertical.component.ts | 2 + .../src/lib/common/charts/chart.component.ts | 6 +- .../common/legend/legend-entry.component.ts | 3 +- .../src/lib/common/legend/legend.component.ts | 12 ++- src/app/app.component.html | 2 + src/app/app.component.ts | 2 + src/app/chartTypes.ts | 2 + .../map-chart/map-chart.component.html | 1 - .../map-chart/map-chart.component.ts | 86 +++++++++++-------- 9 files changed, 71 insertions(+), 45 deletions(-) delete mode 100644 src/app/custom-charts/map-chart/map-chart.component.html diff --git a/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts b/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts index cf2e9e353..d10820809 100644 --- a/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts @@ -179,6 +179,7 @@ export class BarVerticalComponent extends BaseChartComponent { this.legendOptions = this.getLegendOptions(); this.transform = `translate(${this.dims.xOffset} , ${this.margin[0] + this.dataLabelMaxHeight.negative})`; + console.log(this.scheme); } getXScale(): any { @@ -213,6 +214,7 @@ export class BarVerticalComponent extends BaseChartComponent { } onClick(data: DataItem | string) { + console.log(data); this.select.emit(data); } diff --git a/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts b/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts index e810e6ef5..3f31ae76e 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/charts/chart.component.ts @@ -16,10 +16,9 @@ import { ScaleType } from '../types/scale-type.enum'; selector: 'ngx-charts-chart', template: `
- - + + - (); @Output() legendLabelActivate = new EventEmitter<{ name: string }>(); diff --git a/projects/swimlane/ngx-charts/src/lib/common/legend/legend-entry.component.ts b/projects/swimlane/ngx-charts/src/lib/common/legend/legend-entry.component.ts index e30d095bc..e535a456d 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/legend/legend-entry.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/legend/legend-entry.component.ts @@ -4,7 +4,7 @@ import { Component, Input, Output, ChangeDetectionStrategy, HostListener, EventE selector: 'ngx-charts-legend-entry', template: ` - + {{ trimmedLabel }} @@ -17,6 +17,7 @@ export class LegendEntryComponent { @Input() label: string; @Input() formattedLabel: string; @Input() isActive: boolean = false; + @Input() isIncluded: boolean = true; @Output() select: EventEmitter = new EventEmitter(); @Output() activate: EventEmitter<{ name: string }> = new EventEmitter(); diff --git a/projects/swimlane/ngx-charts/src/lib/common/legend/legend.component.ts b/projects/swimlane/ngx-charts/src/lib/common/legend/legend.component.ts index b082067e4..5c470a3fa 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/legend/legend.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/legend/legend.component.ts @@ -33,6 +33,7 @@ export interface LegendEntry { [formattedLabel]="entry.formattedLabel" [color]="entry.color" [isActive]="isActive(entry)" + [isIncluded]="isIncluded(entry)" (select)="labelClick.emit($event)" (activate)="activate($event)" (deactivate)="deactivate($event)" @@ -53,7 +54,8 @@ export class LegendComponent implements OnChanges { @Input() colors: ColorHelper; @Input() height: number; @Input() width: number; - @Input() activeEntries; + @Input() activeEntries: any[]; + @Input() includedEntries: any[]; @Input() horizontal = false; @Output() labelClick: EventEmitter = new EventEmitter(); @@ -102,6 +104,14 @@ export class LegendComponent implements OnChanges { return item !== undefined; } + isIncluded(entry: LegendEntry): boolean { + if (!this.includedEntries) return true; + const item = this.includedEntries.find(d => { + return entry.label === d; + }) + return item !== undefined; + } + activate(item: { name: string }) { this.labelActivate.emit(item); } diff --git a/src/app/app.component.html b/src/app/app.component.html index 992fd8af8..a481acffb 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -604,6 +604,7 @@ [legend]="showLegend" [legendTitle]="legendTitle" [legendPosition]="legendPosition" + [scheme]="colorScheme" [results]="mapChartData" (select)="onSelect($event)" > @@ -1192,6 +1193,7 @@

Color Scheme

@@ -1569,6 +1573,19 @@



+ +
+
+
+
+
+
+
+
+
+
+
+

Documentation

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d5458edf0..9f420d260 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -257,6 +257,46 @@ export class AppComponent implements OnInit { ]; mapChartData = [ + { + name: 'A', + series: [ + { + name: 'New York', + value: [40.7128, -74.0060] + }, + { + name: 'Boston', + value: [42.3601, -71.0589] + }, + { + name: 'Philadelphia', + value: [39.9526, -75.1652] + }, + { + name: 'Raleigh', + value: [35.7796, -78.6382] + } + ] + }, + { + name: 'B', + series: [ + { + name: 'Austin', + value: [30.2672, -97.7431] + }, + { + name: 'Houston', + value: [29.7604, -95.3698] + }, + { + name: 'Dallas', + value: [32.7767, -96.7970] + } + ] + } + ]; + /*mapChartData = [ { name: 'A', value: [ @@ -274,7 +314,10 @@ export class AppComponent implements OnInit { [32.7767, -96.7970] ] } - ]; + ];*/ + mapZoom = 3; + initCoordX = 39.8282; + initCoordY = -98.5795; // data plotData: any; diff --git a/src/app/chartTypes.ts b/src/app/chartTypes.ts index 6007413f6..f9fc62407 100644 --- a/src/app/chartTypes.ts +++ b/src/app/chartTypes.ts @@ -713,7 +713,9 @@ const chartGroups = [ 'legendTitle', 'legendPosition', 'colorScheme', - 'schemeType' + 'mapZoom', + 'initCoordX', + 'initCoordY' ] }, { diff --git a/src/app/custom-charts/map-chart/map-chart.component.scss b/src/app/custom-charts/map-chart/map-chart.component.scss index dcdaf1d6a..a42084267 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.scss +++ b/src/app/custom-charts/map-chart/map-chart.component.scss @@ -1,5 +1,23 @@ .map-container { + display: flex; + gap: 30px; + #map { height: 100%; + + .map-tooltip { + background: black; + color: white; + border: none; + } + /* Add custom styles for the tooltip */ + + .leaflet-tooltip-top:before, + .leaflet-tooltip-bottom:before, + .leaflet-tooltip-left:before, + .leaflet-tooltip-right:before { + border-top-color: black; + } + } } \ No newline at end of file diff --git a/src/app/custom-charts/map-chart/map-chart.component.ts b/src/app/custom-charts/map-chart/map-chart.component.ts index 064ed76c9..08039e3d3 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.ts +++ b/src/app/custom-charts/map-chart/map-chart.component.ts @@ -19,8 +19,11 @@ import * as L from 'leaflet'; @Component({ selector: 'map-chart-component', template: ` -
-
+
+
= new EventEmitter(); @Output() deactivate: EventEmitter = new EventEmitter(); @@ -103,8 +109,8 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { mapInit(): void { this.map = L.map('map', { - center: [ 39.8282, -98.5795 ], - zoom: 3 + center: [this.initCoordX, this.initCoordY], + zoom: this.mapZoom }); setTimeout(() => { @@ -138,7 +144,7 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { this.markersLayer.clearLayers(); for (const d of this.results) { if (this.filteredDomain.includes(d.name)) { - for (const coord of d.value) { + for (const location of d.series) { const markerHtmlStyles = ` background-color: ${this.colors.getColor(d.name)}; width: 2rem; @@ -149,17 +155,24 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { position: relative; border-radius: 2rem 2rem 0; transform: rotate(45deg); - border: 1px solid #FFFFFF` - + border: 1px solid #FFFFFF; + `; const icon = L.divIcon({ - className: "my-custom-pin", iconAnchor: [0, 24], tooltipAnchor: [-6, 0], popupAnchor: [0, -36], html: `` - }) + }); + const marker = L.marker(location.value, {icon: icon}); + + marker.bindTooltip(location.name, { + direction: 'top', + offset: L.point(7, -40), + className: 'map-tooltip', + opacity: 0.75 + }).openTooltip(); - this.markersLayer.addLayer(L.marker(coord, {icon: icon})); + this.markersLayer.addLayer(marker); } } } diff --git a/src/app/custom-charts/map-chart/us.svg b/src/app/custom-charts/map-chart/us.svg deleted file mode 100644 index 708ea1148..000000000 --- a/src/app/custom-charts/map-chart/us.svg +++ /dev/null @@ -1,438 +0,0 @@ - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/custom-charts/map-chart/world.svg b/src/app/custom-charts/map-chart/world.svg deleted file mode 100644 index 84c8e242e..000000000 --- a/src/app/custom-charts/map-chart/world.svg +++ /dev/null @@ -1,977 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 804f2fb2adbef5136d97d0306fc6772072ff65a0 Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Tue, 15 Aug 2023 23:22:43 -0500 Subject: [PATCH 5/9] Add more customization to map chart New option to change map language and map location --- src/app/app.component.html | 19 ++ src/app/app.component.ts | 171 +++++++++++++++++- src/app/chartTypes.ts | 7 +- .../map-chart/map-chart.component.ts | 87 +++++++-- 4 files changed, 267 insertions(+), 17 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 2ffa132f3..341bd3039 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -609,6 +609,10 @@ [mapZoom]="mapZoom" [initCoordX]="initCoordX" [initCoordY]="initCoordY" + [centerMapAt]="centerMapAt" + [latitude]="latitude" + [longitude]="longitude" + [mapLanguage]="mapLanguage" (select)="onSelect($event)" > @@ -1586,6 +1590,21 @@



+
+
+
+
+
+
+
+
+
+ +

Documentation

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9f420d260..8d3cc5f73 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation, ViewChild } from '@angular/core'; import { Location, LocationStrategy, HashLocationStrategy } from '@angular/common'; import * as shape from 'd3-shape'; import * as d3Array from 'd3-array'; @@ -25,6 +25,7 @@ import pkg from '../../projects/swimlane/ngx-charts/package.json'; import { InputTypes } from '@swimlane/ngx-ui'; import { LegendPosition } from '@swimlane/ngx-charts/common/types/legend.model'; import { ScaleType } from '@swimlane/ngx-charts/common/types/scale-type.enum'; +import { MapChartComponent } from './custom-charts/map-chart/map-chart.component'; const monthName = new Intl.DateTimeFormat('en-us', { month: 'short' }); const weekdayName = new Intl.DateTimeFormat('en-us', { weekday: 'short' }); @@ -117,6 +118,10 @@ export class AppComponent implements OnInit { strokeColor: string = '#FFFFFF'; strokeWidth: number = 2; wrapTicks = false; + latitude: number = 39.8282; + longitude: number = -98.5795; + mapLanguage: string = 'native'; + centerMapAt: boolean = false; curves = { Basis: shape.curveBasis, @@ -258,7 +263,7 @@ export class AppComponent implements OnInit { mapChartData = [ { - name: 'A', + name: 'Successful Logins', series: [ { name: 'New York', @@ -275,11 +280,91 @@ export class AppComponent implements OnInit { { name: 'Raleigh', value: [35.7796, -78.6382] + }, + { + name: 'Austin', + value: [30, -97.7431] + }, + { + name: 'Beijing', + value: [39.9, 116.4] + }, + { + name: 'Chengdu', + value: [30.5728, 104.0668] + }, + { + name: "Xi'an", + value: [34.3416, 108.9398] + }, + { + name: 'Wuhan', + value: [30.5928, 114.3055] + }, + { + name: 'Suzhou', + value: [31.2989, 120.5853] + }, + { + name: 'Harbin', + value: [45.8038, 126.5349] + }, + { + name: 'Chongqing', + value: [29.4316, 106.9123] + }, + { + name: 'Nanjing', + value: [32.0603, 118.7969] + }, + { + name: 'Tianjin', + value: [39.0842, 117.2008] + }, + { + name: 'Tokyo', + value: [35.6895, 139.6917] + }, + { + name: 'Buenos Aires', + value: [-34.6037, -58.3816] + }, + { + name: 'Cairo', + value: [30.0444, 31.2357] + }, + { + name: 'Singapore', + value: [1.3521, 103.8198] + }, + { + name: 'Berlin', + value: [52.5200, 13.4050] + }, + { + name: 'Rome', + value: [41.9028, 12.4964] + }, + { + name: 'New Delhi', + value: [28.6139, 77.2090] + }, + { + name: 'Johannesburg', + value: [-26.2041, 28.0473] + }, + { + name: 'Seoul', + value: [37.5665, 126.9780] + }, + { + name: 'Mexico City', + value: [19.4326, -99.1332] } ] }, { - name: 'B', + name: 'Failed Logins', series: [ { name: 'Austin', @@ -292,6 +377,78 @@ export class AppComponent implements OnInit { { name: 'Dallas', value: [32.7767, -96.7970] + }, + { + name: 'Washinton DC', + value: [38.9072, -77.0369] + }, + { + name: 'Tokyo', + value: [35.6895, 139.6917] + }, + { + name: 'Sydney', + value: [-33.8688, 151.2093] + }, + { + name: 'Guangzhou', + value: [23.1291, 113.2644] + }, + { + name: 'Shenzhen', + value: [22.5431, 114.0579] + }, + { + name: 'Lanzhou', + value: [36.0614, 103.8343] + }, + { + name: 'Changsha', + value: [28.2282, 112.9388] + }, + { + name: 'Urumqi', + value: [43.8256, 87.6168] + }, + { + name: 'Paris', + value: [48.8566, 2.3522] + }, + { + name: 'Rio de Janeiro', + value: [-22.9068, -43.1729] + }, + { + name: 'Cape Town', + value: [-33.9249, 18.4241] + }, + { + name: 'Dubai', + value: [25.276987, 55.296249] + }, + { + name: 'Mumbai', + value: [19.0760, 72.8777] + }, + { + name: 'Sydney', + value: [-33.8688, 151.2093] + }, + { + name: 'Moscow', + value: [55.7558, 37.6176] + }, + { + name: 'Sao Paulo', + value: [-23.5505, -46.6333] + }, + { + name: 'Istanbul', + value: [41.0082, 28.9784] + }, + { + name: 'Toronto', + value: [43.6532, -79.3832] } ] } @@ -328,6 +485,8 @@ export class AppComponent implements OnInit { dimVisible: boolean = true; optsVisible: boolean = true; + @ViewChild(MapChartComponent) mapComponent: MapChartComponent; + constructor(public location: Location) { this.mathFunction = this.getFunction(); @@ -545,6 +704,10 @@ export class AppComponent implements OnInit { this.view = [this.width, this.height]; } + mapChangePosition() { + this.mapComponent.changePosition(); + } + toggleFitContainer() { if (this.fitContainer) { this.view = undefined; @@ -602,7 +765,6 @@ export class AppComponent implements OnInit { } setColorScheme(name) { - console.log("update"); this.selectedColorScheme = name; this.colorScheme = this.colorSets.find(s => s.name === name); } @@ -862,7 +1024,6 @@ export class AppComponent implements OnInit { */ onSelect(event) { - console.log("??"); console.log(event); } diff --git a/src/app/chartTypes.ts b/src/app/chartTypes.ts index f9fc62407..6431be59e 100644 --- a/src/app/chartTypes.ts +++ b/src/app/chartTypes.ts @@ -715,7 +715,12 @@ const chartGroups = [ 'colorScheme', 'mapZoom', 'initCoordX', - 'initCoordY' + 'initCoordY', + 'view', + 'centerMapAt', + 'latitude', + 'longitude', + 'mapLanguage' ] }, { diff --git a/src/app/custom-charts/map-chart/map-chart.component.ts b/src/app/custom-charts/map-chart/map-chart.component.ts index 08039e3d3..7f9a65b90 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.ts +++ b/src/app/custom-charts/map-chart/map-chart.component.ts @@ -15,6 +15,7 @@ import { } from 'projects/swimlane/ngx-charts/src/public-api'; import { LegendPosition } from '../../../../projects/swimlane/ngx-charts/src/lib/common/types/legend.model'; import * as L from 'leaflet'; +import {select} from 'd3-selection'; @Component({ selector: 'map-chart-component', @@ -49,6 +50,12 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { @Input() mapZoom: number; @Input() initCoordX: any; @Input() initCoordY: any; + @Input() view: [number, number]; + @Input() longitude: number; + @Input() latitude: number; + @Input() mapLanguage: string = "native"; + @Input() centerMapAt: any; + @Input() mapLog: boolean = false; @Output() activate: EventEmitter = new EventEmitter(); @Output() deactivate: EventEmitter = new EventEmitter(); @@ -65,9 +72,11 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { domain: any[]; filteredDomain: any[]; includedEntries: any[]; + currentTiles = null; readonly LegendPosition = LegendPosition; + trackBy(index, item): string { return `${item.name}`; } @@ -99,31 +108,41 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { this.updateLegend(); this.transform = `translate(${this.dims.xOffset} , ${this.margin[0]})`; - console.log(this.scheme); - } - - ngOnInit() { - this.mapInit(); - } + this.adjustSize(); + + if (this.map) { + //trigger the map to reload + this.map.invalidateSize(); + } else { + this.mapInit(); + } + + if (this.mapLanguage) { + this.changeLanguage(); + } + } mapInit(): void { this.map = L.map('map', { center: [this.initCoordX, this.initCoordY], - zoom: this.mapZoom + zoom: this.mapZoom, + inertia: false }); setTimeout(() => { this.map.invalidateSize(true); }, 0); - const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + this.map.setMaxBounds(this); + + this.currentTiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, - minZoom: 3, + minZoom: 2, attribution: '© OpenStreetMap' }); - tiles.addTo(this.map); + this.currentTiles.addTo(this.map); this.markersLayer = L.layerGroup(); this.markersLayer.addTo(this.map); @@ -178,10 +197,13 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { } } + changePosition(): void { + this.map.setView([this.latitude, this.longitude]); + } + updateLegend(): void { let legendColumns = 0; if (this.legend) { - if (this.legendPosition === LegendPosition.Right) { legendColumns = 2; } @@ -207,4 +229,47 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { } this.update(); } + + adjustSize() { + if (this.view) { + this.width = this.view[0]; + this.height = this.view[1]; + } + const container = select(this.chartElement.nativeElement).select('#map').node() as HTMLElement; + container.style.width = this.width + "px"; + container.style.height = this.height + "px"; + } + + changeLanguage() { + const oldCenter = this.map.getCenter(); + + // Remove the current tile layer if it exists + if (this.currentTiles) { + this.map.removeLayer(this.currentTiles); + } + + // Create and add the new tile layer based on mapLanguage + if (this.mapLanguage == "native") { + this.currentTiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 18, + minZoom: 1, + attribution: '© OpenStreetMap' + }); + } else if (this.mapLanguage == "german"){ + this.currentTiles = L.tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', { + maxZoom: 18, + minZoom: 1, + attribution: '© OpenStreetMap' + }); + } else if (this.mapLanguage == "english") { + this.currentTiles = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', { + minZoom: 1, + maxZoom: 18, + attribution: '© carto.com contributors' + }); + } + this.currentTiles.addTo(this.map); + + this.map.setView(oldCenter); + } } From 8cb0307315cf69011be0b13327c40944a37bcaa2 Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Tue, 15 Aug 2023 23:28:14 -0500 Subject: [PATCH 6/9] Cleanup --- .../lib/bar-chart/bar-vertical.component.ts | 2 -- src/app/app.component.ts | 19 ------------------- .../map-chart/map-chart.component.scss | 3 +-- .../map-chart/map-chart.component.ts | 3 +-- 4 files changed, 2 insertions(+), 25 deletions(-) diff --git a/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts b/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts index d10820809..cf2e9e353 100644 --- a/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/bar-chart/bar-vertical.component.ts @@ -179,7 +179,6 @@ export class BarVerticalComponent extends BaseChartComponent { this.legendOptions = this.getLegendOptions(); this.transform = `translate(${this.dims.xOffset} , ${this.margin[0] + this.dataLabelMaxHeight.negative})`; - console.log(this.scheme); } getXScale(): any { @@ -214,7 +213,6 @@ export class BarVerticalComponent extends BaseChartComponent { } onClick(data: DataItem | string) { - console.log(data); this.select.emit(data); } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8d3cc5f73..eb4fbfc53 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -453,25 +453,6 @@ export class AppComponent implements OnInit { ] } ]; - /*mapChartData = [ - { - name: 'A', - value: [ - [40.7128, -74.0060], - [42.3601, -71.0589], - [39.9526, -75.1652], - [35.7796, -78.6382] - ] - }, - { - name: 'B', - value: [ - [30.2672, -97.7431], - [29.7604, -95.3698], - [32.7767, -96.7970] - ] - } - ];*/ mapZoom = 3; initCoordX = 39.8282; initCoordY = -98.5795; diff --git a/src/app/custom-charts/map-chart/map-chart.component.scss b/src/app/custom-charts/map-chart/map-chart.component.scss index a42084267..d9f1004d1 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.scss +++ b/src/app/custom-charts/map-chart/map-chart.component.scss @@ -10,8 +10,7 @@ color: white; border: none; } - /* Add custom styles for the tooltip */ - + .leaflet-tooltip-top:before, .leaflet-tooltip-bottom:before, .leaflet-tooltip-left:before, diff --git a/src/app/custom-charts/map-chart/map-chart.component.ts b/src/app/custom-charts/map-chart/map-chart.component.ts index 7f9a65b90..db01a44fe 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.ts +++ b/src/app/custom-charts/map-chart/map-chart.component.ts @@ -104,7 +104,6 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { this.colors = new ColorHelper(this.scheme, this.schemeType, this.domain, this.customColors); this.addMarkers(); - this.updateLegend(); this.transform = `translate(${this.dims.xOffset} , ${this.margin[0]})`; @@ -151,7 +150,7 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { } getDomain(): any[] { - let values = []; + const values = []; for (const d of this.results) { values.push(d.name); } From 1325b8bd326d5cb6334c6919999b732766a41d7c Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Tue, 15 Aug 2023 23:35:28 -0500 Subject: [PATCH 7/9] Fix bug where initial dimensions are incorrect --- src/app/custom-charts/map-chart/map-chart.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/custom-charts/map-chart/map-chart.component.ts b/src/app/custom-charts/map-chart/map-chart.component.ts index db01a44fe..f1bd2ff17 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.ts +++ b/src/app/custom-charts/map-chart/map-chart.component.ts @@ -231,8 +231,8 @@ export class MapChartComponent extends BaseChartComponent implements OnInit { adjustSize() { if (this.view) { - this.width = this.view[0]; - this.height = this.view[1]; + this.width = this.dims.width; + this.height = this.dims.height; } const container = select(this.chartElement.nativeElement).select('#map').node() as HTMLElement; container.style.width = this.width + "px"; From fe94d5452d6de1607aeb9eeebc8547627ef1075c Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Tue, 15 Aug 2023 23:39:56 -0500 Subject: [PATCH 8/9] Adjust map data --- src/app/app.component.ts | 164 +-------------------------------------- 1 file changed, 4 insertions(+), 160 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index eb4fbfc53..fda58d2cf 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -263,125 +263,25 @@ export class AppComponent implements OnInit { mapChartData = [ { - name: 'Successful Logins', + name: 'United States', series: [ { name: 'New York', value: [40.7128, -74.0060] }, - { - name: 'Boston', - value: [42.3601, -71.0589] - }, - { - name: 'Philadelphia', - value: [39.9526, -75.1652] - }, - { - name: 'Raleigh', - value: [35.7796, -78.6382] - }, { name: 'Austin', value: [30, -97.7431] }, { - name: 'Beijing', - value: [39.9, 116.4] - }, - { - name: 'Chengdu', - value: [30.5728, 104.0668] - }, - { - name: "Xi'an", - value: [34.3416, 108.9398] - }, - { - name: 'Wuhan', - value: [30.5928, 114.3055] - }, - { - name: 'Suzhou', - value: [31.2989, 120.5853] - }, - { - name: 'Harbin', - value: [45.8038, 126.5349] - }, - { - name: 'Chongqing', - value: [29.4316, 106.9123] - }, - { - name: 'Nanjing', - value: [32.0603, 118.7969] - }, - { - name: 'Tianjin', - value: [39.0842, 117.2008] - }, - { - name: 'Tokyo', - value: [35.6895, 139.6917] - }, - { - name: 'Buenos Aires', - value: [-34.6037, -58.3816] - }, - { - name: 'Cairo', - value: [30.0444, 31.2357] - }, - { - name: 'Singapore', - value: [1.3521, 103.8198] - }, - { - name: 'Berlin', - value: [52.5200, 13.4050] - }, - { - name: 'Rome', - value: [41.9028, 12.4964] - }, - { - name: 'New Delhi', - value: [28.6139, 77.2090] - }, - { - name: 'Johannesburg', - value: [-26.2041, 28.0473] - }, - { - name: 'Seoul', - value: [37.5665, 126.9780] - }, - { - name: 'Mexico City', - value: [19.4326, -99.1332] + name: 'Los Angeles', + value: [34.0522, -118.2437] } ] }, { - name: 'Failed Logins', + name: 'International', series: [ - { - name: 'Austin', - value: [30.2672, -97.7431] - }, - { - name: 'Houston', - value: [29.7604, -95.3698] - }, - { - name: 'Dallas', - value: [32.7767, -96.7970] - }, - { - name: 'Washinton DC', - value: [38.9072, -77.0369] - }, { name: 'Tokyo', value: [35.6895, 139.6917] @@ -393,62 +293,6 @@ export class AppComponent implements OnInit { { name: 'Guangzhou', value: [23.1291, 113.2644] - }, - { - name: 'Shenzhen', - value: [22.5431, 114.0579] - }, - { - name: 'Lanzhou', - value: [36.0614, 103.8343] - }, - { - name: 'Changsha', - value: [28.2282, 112.9388] - }, - { - name: 'Urumqi', - value: [43.8256, 87.6168] - }, - { - name: 'Paris', - value: [48.8566, 2.3522] - }, - { - name: 'Rio de Janeiro', - value: [-22.9068, -43.1729] - }, - { - name: 'Cape Town', - value: [-33.9249, 18.4241] - }, - { - name: 'Dubai', - value: [25.276987, 55.296249] - }, - { - name: 'Mumbai', - value: [19.0760, 72.8777] - }, - { - name: 'Sydney', - value: [-33.8688, 151.2093] - }, - { - name: 'Moscow', - value: [55.7558, 37.6176] - }, - { - name: 'Sao Paulo', - value: [-23.5505, -46.6333] - }, - { - name: 'Istanbul', - value: [41.0082, 28.9784] - }, - { - name: 'Toronto', - value: [43.6532, -79.3832] } ] } From 7b1fbacc225822f464369e77578a4b4c86e6d2ad Mon Sep 17 00:00:00 2001 From: Lyq322 Date: Wed, 16 Aug 2023 16:49:08 -0500 Subject: [PATCH 9/9] Cleanup --- src/app/custom-charts/map-chart/map-chart.component.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/custom-charts/map-chart/map-chart.component.scss b/src/app/custom-charts/map-chart/map-chart.component.scss index d9f1004d1..5014733f6 100644 --- a/src/app/custom-charts/map-chart/map-chart.component.scss +++ b/src/app/custom-charts/map-chart/map-chart.component.scss @@ -11,10 +11,10 @@ border: none; } - .leaflet-tooltip-top:before, - .leaflet-tooltip-bottom:before, - .leaflet-tooltip-left:before, - .leaflet-tooltip-right:before { + .leaflet-tooltip-top::before, + .leaflet-tooltip-bottom::before, + .leaflet-tooltip-left::before, + .leaflet-tooltip-right::before { border-top-color: black; }