WIP: add dynamic generation of marker if check-box in table is pressed
* fix: both use the same array -> delete if uncheck
@ -26,6 +26,7 @@ import {MatInputModule} from '@angular/material/input';
|
|||||||
import {MatTableModule} from '@angular/material/table';
|
import {MatTableModule} from '@angular/material/table';
|
||||||
import {AutoRefreshComponent} from './map/auto-refresh/auto-refresh.component';
|
import {AutoRefreshComponent} from './map/auto-refresh/auto-refresh.component';
|
||||||
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
||||||
|
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -57,7 +58,8 @@ import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatSlideToggleModule
|
MatSlideToggleModule,
|
||||||
|
MatCheckboxModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
@ -75,13 +75,22 @@
|
|||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="container-table" fxFlex fxLayout="row" fxLayoutAlign="center">
|
<div class="container-table" fxFlex fxLayout="row" fxLayoutAlign="center">
|
||||||
<div class="dashboard-table-to" fxFlex>
|
<div class="dashboard-table-to" fxFlex>
|
||||||
<table [dataSource]="stationToSource" class="mat-elevation-z8" fxFill mat-table>
|
<table [dataSource]="stationToSource" class="mat-elevation-z8" fxFill mat-table>
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
<mat-checkbox (click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? selectRowTo($event, row) : null"
|
||||||
|
[checked]="selectionTo.isSelected(row)"
|
||||||
|
[aria-label]="checkboxLabelTo(row)">
|
||||||
|
</mat-checkbox>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
<ng-container matColumnDef="endStationName">
|
<ng-container matColumnDef="endStationName">
|
||||||
<th *matHeaderCellDef mat-header-cell> station of lend destination</th>
|
<th *matHeaderCellDef mat-header-cell> station of lend destination</th>
|
||||||
<td *matCellDef="let element" mat-cell><a
|
<td *matCellDef="let element" mat-cell><a [style.color]="getColorTo(element)"
|
||||||
[routerLink]="['/dashboard/', element.stationId]">{{element.stationName}}</a></td>
|
[routerLink]="['/dashboard/', element.stationId]">{{element.stationName}}</a></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -101,9 +110,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dashboard-table-from" fxFlex>
|
<div class="dashboard-table-from" fxFlex>
|
||||||
<table [dataSource]="stationFromSource" class="mat-elevation-z9" fxFill mat-table>
|
<table [dataSource]="stationFromSource" class="mat-elevation-z9" fxFill mat-table>
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
<mat-checkbox (click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? selectRowFrom($event, row) : null"
|
||||||
|
[checked]="selectionFrom.isSelected(row)"
|
||||||
|
[aria-label]="checkboxLabelFrom(row)">
|
||||||
|
</mat-checkbox>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
<ng-container matColumnDef="startStationName">
|
<ng-container matColumnDef="startStationName">
|
||||||
<th *matHeaderCellDef mat-header-cell> station of lend origin</th>
|
<th *matHeaderCellDef mat-header-cell> station of lend origin</th>
|
||||||
<td *matCellDef="let element" mat-cell><a
|
<td *matCellDef="let element" mat-cell><a [style.color]="getColorFrom(element)"
|
||||||
[routerLink]="['/dashboard/', element.stationId]"> {{element.stationName}}</a></td>
|
[routerLink]="['/dashboard/', element.stationId]"> {{element.stationName}}</a></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -6,12 +6,9 @@ mat-sidenav-content {
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: #017bfe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-back:hover, .button-wiki:hover {
|
.button-back:hover, .button-wiki:hover {
|
||||||
background: #086ed2;
|
background: #086ed2;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-date {
|
.submit-date {
|
||||||
@ -31,7 +28,7 @@ a {
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
background-image: url('../../assets/bike-point.png');
|
background-image: url('../../assets/bike-point-blue.png');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import {
|
|||||||
ChartComponent
|
ChartComponent
|
||||||
} from 'ng-apexcharts';
|
} from 'ng-apexcharts';
|
||||||
import {IMapBikePoint} from '../service/domain/map-bike-point';
|
import {IMapBikePoint} from '../service/domain/map-bike-point';
|
||||||
|
import {SelectionModel} from '@angular/cdk/collections';
|
||||||
|
import {MatCheckboxChange} from '@angular/material/checkbox';
|
||||||
|
|
||||||
export type ChartOptions = {
|
export type ChartOptions = {
|
||||||
title: ApexTitleSubtitle
|
title: ApexTitleSubtitle
|
||||||
@ -54,10 +56,12 @@ export class DashboardComponent implements OnInit {
|
|||||||
public durationChartOptions: Partial<ChartOptions>;
|
public durationChartOptions: Partial<ChartOptions>;
|
||||||
public timeChartOptions: Partial<ChartOptions>;
|
public timeChartOptions: Partial<ChartOptions>;
|
||||||
public bikePointChartOptions: Partial<ChartOptions>;
|
public bikePointChartOptions: Partial<ChartOptions>;
|
||||||
displayedColumnsTo: string[] = ['endStationName', 'number', 'avgDuration'];
|
displayedColumnsTo: string[] = ['select', 'endStationName', 'number', 'avgDuration'];
|
||||||
displayedColumnsFrom: string[] = ['startStationName', 'number', 'avgDuration'];
|
displayedColumnsFrom: string[] = ['select', 'startStationName', 'number', 'avgDuration'];
|
||||||
stationToSource = new MatTableDataSource<any>();
|
stationToSource = new MatTableDataSource<IDashboardCommonBikePoint>();
|
||||||
|
selectionTo = new SelectionModel<any>(true, []);
|
||||||
stationFromSource = new MatTableDataSource<any>();
|
stationFromSource = new MatTableDataSource<any>();
|
||||||
|
selectionFrom = new SelectionModel<any>(true, []);
|
||||||
|
|
||||||
station: IDashboardCommonBikePoint;
|
station: IDashboardCommonBikePoint;
|
||||||
maxStartDate: Date;
|
maxStartDate: Date;
|
||||||
@ -67,6 +71,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
|
|
||||||
bikePoint: IMapBikePoint;
|
bikePoint: IMapBikePoint;
|
||||||
|
bikePointWithColor = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -205,15 +210,15 @@ export class DashboardComponent implements OnInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initDashboard(): void {
|
async initDashboard(): Promise<any> {
|
||||||
const initDate = this.maxEndDate.toISOString().substring(0, 10);
|
const initDate = this.maxEndDate.toISOString().substring(0, 10);
|
||||||
this.form.get('daterange').get('start').setValue(initDate);
|
this.form.get('daterange').get('start').setValue(initDate);
|
||||||
this.form.get('daterange').get('end').setValue(initDate);
|
this.form.get('daterange').get('end').setValue(initDate);
|
||||||
this.service.fetchDashboardStationTo(this.station.id, initDate, initDate).then((source) => {
|
await this.service.fetchDashboardStationTo(this.station.id, initDate, initDate).then((source) => {
|
||||||
this.stationToSource = source;
|
this.stationToSource = source;
|
||||||
this.changeDetectorRefs.detectChanges();
|
this.changeDetectorRefs.detectChanges();
|
||||||
});
|
});
|
||||||
this.service.fetchDashboardStationFrom(this.station.id, initDate, initDate).then((source) => {
|
await this.service.fetchDashboardStationFrom(this.station.id, initDate, initDate).then((source) => {
|
||||||
this.stationFromSource = source;
|
this.stationFromSource = source;
|
||||||
this.changeDetectorRefs.detectChanges();
|
this.changeDetectorRefs.detectChanges();
|
||||||
});
|
});
|
||||||
@ -323,16 +328,16 @@ export class DashboardComponent implements OnInit {
|
|||||||
text: 'amount of drives',
|
text: 'amount of drives',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
opposite: true,
|
opposite: true,
|
||||||
title: {
|
title: {
|
||||||
text: 'average Duration'
|
text: 'average Duration'
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
formatter: (val: number): string => {
|
formatter: (val: number): string => {
|
||||||
return val + ' min';
|
return val + ' min';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
legend: {
|
legend: {
|
||||||
horizontalAlign: 'left'
|
horizontalAlign: 'left'
|
||||||
},
|
},
|
||||||
@ -495,4 +500,98 @@ export class DashboardComponent implements OnInit {
|
|||||||
humanizeAvgDuration(avgDuration: number): string {
|
humanizeAvgDuration(avgDuration: number): string {
|
||||||
return stht(avgDuration);
|
return stht(avgDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isToAllSelected(): boolean {
|
||||||
|
const numSelected = this.selectionTo.selected.length;
|
||||||
|
const numRows = this.stationToSource.data.length;
|
||||||
|
return numSelected === numRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFromAllSelected(): boolean {
|
||||||
|
const numSelected = this.selectionFrom.selected.length;
|
||||||
|
const numRows = this.stationFromSource.data.length;
|
||||||
|
return numSelected === numRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkboxLabelTo(row?: any): string {
|
||||||
|
if (!row) {
|
||||||
|
return `${this.isToAllSelected() ? 'select' : 'deselect'} all`;
|
||||||
|
}
|
||||||
|
return `${this.selectionTo.isSelected(row) ? 'deselect' : 'select'} row ${row.position + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkboxLabelFrom(row?: any): string {
|
||||||
|
if (!row) {
|
||||||
|
return `${this.isFromAllSelected() ? 'select' : 'deselect'} all`;
|
||||||
|
}
|
||||||
|
return `${this.selectionFrom.isSelected(row) ? 'deselect' : 'select'} row ${row.position + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectRowTo(selection: MatCheckboxChange, row): void {
|
||||||
|
this.selectionTo.toggle(row);
|
||||||
|
this.selectionTo.selected.forEach(point => {
|
||||||
|
if (point.stationId === row.stationId) {
|
||||||
|
point.color = this.getColorTo(row);
|
||||||
|
}
|
||||||
|
this.bikePointWithColor.push(point);
|
||||||
|
});
|
||||||
|
this.map.drawTableStationMarker(this.bikePointWithColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectRowFrom(selection: MatCheckboxChange, row): void {
|
||||||
|
this.selectionFrom.toggle(row);
|
||||||
|
this.selectionFrom.selected.forEach(point => {
|
||||||
|
if (point.stationId === row.stationId) {
|
||||||
|
point.color = this.getColorFrom(row);
|
||||||
|
}
|
||||||
|
this.bikePointWithColor.push(point);
|
||||||
|
});
|
||||||
|
this.map.drawTableStationMarker(this.bikePointWithColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorTo(value): string {
|
||||||
|
switch (value.stationName) {
|
||||||
|
case this.stationToSource[0].stationName:
|
||||||
|
if (this.stationToSource[0].stationName === this.station.commonName) {
|
||||||
|
return 'blue';
|
||||||
|
} else {
|
||||||
|
return 'black';
|
||||||
|
}
|
||||||
|
case this.stationToSource[1].stationName:
|
||||||
|
if (this.stationToSource[1].stationName === this.station.commonName) {
|
||||||
|
return 'blue';
|
||||||
|
} else {
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
case this.stationToSource[2].stationName:
|
||||||
|
if (this.stationToSource[2].stationName === this.station.commonName) {
|
||||||
|
return 'blue';
|
||||||
|
} else {
|
||||||
|
return 'green';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorFrom(value): string {
|
||||||
|
switch (value.stationName) {
|
||||||
|
case this.stationFromSource[0].stationName:
|
||||||
|
if (this.stationFromSource[0].stationName === this.station.commonName) {
|
||||||
|
return 'blue';
|
||||||
|
} else {
|
||||||
|
return 'orange';
|
||||||
|
}
|
||||||
|
case this.stationFromSource[1].stationName:
|
||||||
|
if (this.stationFromSource[1].stationName === this.station.commonName) {
|
||||||
|
return 'blue';
|
||||||
|
} else {
|
||||||
|
return 'purple';
|
||||||
|
}
|
||||||
|
case this.stationFromSource[2].stationName:
|
||||||
|
if (this.stationFromSource[2].stationName === this.station.commonName) {
|
||||||
|
return 'blue';
|
||||||
|
} else {
|
||||||
|
return 'gray';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {MapService} from '../service/map.service';
|
import {MapService} from '../service/map.service';
|
||||||
import {BehaviorSubject, Observable, of, Subject} from 'rxjs';
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -25,7 +24,7 @@ export class MapComponent implements OnInit {
|
|||||||
console.log(this.isRefreshActive);
|
console.log(this.isRefreshActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initMapView(): Promise<any> {
|
async initMapView(): Promise<any> {
|
||||||
this.service.initMap(51.509865, -0.118092, 14);
|
this.service.initMap(51.509865, -0.118092, 14);
|
||||||
await this.service.drawStationMarkers();
|
await this.service.drawStationMarkers();
|
||||||
this.service.drawHeatmap();
|
this.service.drawHeatmap();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export interface IDashboardCommonBikePoint {
|
export interface IDashboardCommonBikePoint {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
color?: string;
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
lat?: number;
|
lat?: number;
|
||||||
lon?: number;
|
lon?: number;
|
||||||
@ -10,6 +11,7 @@ export interface IDashboardCommonBikePoint {
|
|||||||
export class DashboardCommonBikePoint implements IDashboardCommonBikePoint {
|
export class DashboardCommonBikePoint implements IDashboardCommonBikePoint {
|
||||||
constructor(
|
constructor(
|
||||||
public id?: string,
|
public id?: string,
|
||||||
|
public color?: string,
|
||||||
public commonName?: string,
|
public commonName?: string,
|
||||||
public lat?: number,
|
public lat?: number,
|
||||||
public lon?: number,
|
public lon?: number,
|
||||||
|
@ -9,8 +9,8 @@ import {IMapBikePoint} from './domain/map-bike-point';
|
|||||||
import {Observable, Subject} from 'rxjs';
|
import {Observable, Subject} from 'rxjs';
|
||||||
|
|
||||||
|
|
||||||
const createIcon = L.icon({
|
const createIcon = color => L.icon({
|
||||||
iconUrl: '../../assets/bike-point.png',
|
iconUrl: `../../assets/bike-point-${color}.png`,
|
||||||
iconSize: [45, 45],
|
iconSize: [45, 45],
|
||||||
iconAnchor: [21, 40],
|
iconAnchor: [21, 40],
|
||||||
popupAnchor: [1, -35]
|
popupAnchor: [1, -35]
|
||||||
@ -27,6 +27,9 @@ export class MapService {
|
|||||||
public miniMap;
|
public miniMap;
|
||||||
bikePoints: Array<IMapBikePoint> = [];
|
bikePoints: Array<IMapBikePoint> = [];
|
||||||
mapOverlays: any = {};
|
mapOverlays: any = {};
|
||||||
|
miniMapMarker: L.layerGroup;
|
||||||
|
markerLayer = [];
|
||||||
|
dashBoardMarker = L.marker;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private client: HttpClient,
|
private client: HttpClient,
|
||||||
@ -78,7 +81,7 @@ export class MapService {
|
|||||||
this.mapOverlays.Bikepoints = markerClusters;
|
this.mapOverlays.Bikepoints = markerClusters;
|
||||||
this.map.addLayer(markerClusters);
|
this.map.addLayer(markerClusters);
|
||||||
for (const station of data) {
|
for (const station of data) {
|
||||||
const marker = L.marker([station.lat, station.lon], {icon: createIcon});
|
const marker = L.marker([station.lat, station.lon], {icon: createIcon('blue')});
|
||||||
markerClusters.addLayer(marker);
|
markerClusters.addLayer(marker);
|
||||||
marker.on('click', e => {
|
marker.on('click', e => {
|
||||||
e.target.bindPopup(this.popUpService.makeAvailabilityPopUp(station), {maxWidth: 'auto'})
|
e.target.bindPopup(this.popUpService.makeAvailabilityPopUp(station), {maxWidth: 'auto'})
|
||||||
@ -137,7 +140,20 @@ export class MapService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public drawDashboardStationMarker(lat: number, lon: number): void {
|
public drawDashboardStationMarker(lat: number, lon: number): void {
|
||||||
L.marker([lat, lon], {icon: createIcon}).addTo(this.miniMap);
|
this.dashBoardMarker = L.marker([lat, lon], {icon: createIcon('blue')}).addTo(this.miniMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public drawTableStationMarker(bikePoints: any[]): void {
|
||||||
|
if (this.markerLayer) {
|
||||||
|
this.markerLayer.forEach(marker => {
|
||||||
|
this.miniMap.removeLayer(marker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const point of bikePoints) {
|
||||||
|
const marker = L.marker([point.stationLat, point.stationLon], {icon: createIcon(point.color)}).addTo(this.miniMap);
|
||||||
|
this.markerLayer.push(marker);
|
||||||
|
this.miniMap.fitBounds(L.featureGroup([...this.markerLayer, this.dashBoardMarker]).getBounds());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawMapControl(): void {
|
private drawMapControl(): void {
|
||||||
|
BIN
projects/project-3/frontend/src/assets/bike-point-black.png
Normal file
After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
BIN
projects/project-3/frontend/src/assets/bike-point-gray.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
projects/project-3/frontend/src/assets/bike-point-green.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
projects/project-3/frontend/src/assets/bike-point-orange.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
projects/project-3/frontend/src/assets/bike-point-purple.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
projects/project-3/frontend/src/assets/bike-point-red.png
Normal file
After Width: | Height: | Size: 76 KiB |