From 8138926e1c15fed5dee93861467083ecadcc8f02 Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Sun, 1 Oct 2017 13:12:55 +0200 Subject: [PATCH 1/7] Add players endpoint --- api/config/api-url.js | 1 + api/package.json | 2 +- api/routes/players.js | 56 ++++++++++++++++++++++++++++++++++++ api/server.js | 2 ++ static/src/app/app.config.ts | 1 + 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 api/routes/players.js diff --git a/api/config/api-url.js b/api/config/api-url.js index 4e68b70..87a179e 100644 --- a/api/config/api-url.js +++ b/api/config/api-url.js @@ -9,6 +9,7 @@ module.exports = { command: rootRoute + '/cmd', decorations: rootRoute + '/decorations', overview: rootRoute + '/overview', + players: rootRoute + '/players', ranks: rootRoute + '/ranks', request: rootRoute + '/request', signatures: '/signatures', diff --git a/api/package.json b/api/package.json index 9cfdf82..b1c4c3f 100644 --- a/api/package.json +++ b/api/package.json @@ -17,7 +17,7 @@ "cors": "^2.8.3", "cron": "^1.2.1", "debug": "~2.2.0", - "express": "~4.13.1", + "express": "^4.16.1", "imagemin": "^5.2.2", "imagemin-pngquant": "^5.0.0", "jimp": "^0.2.27", diff --git a/api/routes/players.js b/api/routes/players.js new file mode 100644 index 0000000..c296f8d --- /dev/null +++ b/api/routes/players.js @@ -0,0 +1,56 @@ +"use strict"; + +// modules +const express = require('express'); +const logger = require('debug')('cc:wars'); + +// HTTP status codes by name +const codes = require('./http-codes'); + +const routerHandling = require('../middleware/router-handling'); + +// Mongoose Model using mongoDB +const CampaignModel = require('../models/campaign'); +const PlayerModel = require('../models/player'); +const WarModel = require('../models/war'); + +const campaignPlayer = express.Router(); + +// routes ********************** +campaignPlayer.route('/:campaignId/:playerName') + .get((req, res, next) => { + CampaignModel.findById(req.params.campaignId, (err, campaign) => { + if (err) return next(err); + WarModel.find({campaign: req.params.campaignId}, '_id', (err, wars) => { + if (err) return next(err); + const warIds = wars.map((obj) => { + return obj._id; + }); + PlayerModel.find({name: req.params.playerName, warId: {"$in": warIds}}) + .populate('warId') + .exec((err, items) => { + if (err) return next(err); + if (!items || items.length === 0) { + const err = new Error('unknown player name'); + err.status = codes.notfound; + return next(err) + } + res.locals.items = { + name: req.params.playerName, + campaign: campaign, + players: items + }; + next(); + }) + }) + }) + }) + + .all( + routerHandling.httpMethodNotAllowed + ); + + +campaignPlayer.use(routerHandling.emptyResponse); + +module.exports = campaignPlayer; diff --git a/api/server.js b/api/server.js index 9c203a9..b961de2 100644 --- a/api/server.js +++ b/api/server.js @@ -30,6 +30,7 @@ const rankRouter = require('./routes/ranks'); const decorationRouter = require('./routes/decorations'); const awardingRouter = require('./routes/awardings'); const requestRouter = require('./routes/request'); +const playerRouter = require('./routes/players'); const signatureRouter = require('./routes/signatures'); const commandRouter = require('./routes/command'); const campaignRouter = require('./routes/campaigns'); @@ -80,6 +81,7 @@ app.use(urls.decorations, decorationRouter); app.use(urls.request, requestRouter); app.use(urls.awards, awardingRouter); app.use(urls.wars, warRouter); +app.use(urls.players, playerRouter); app.use(urls.campaigns,campaignRouter); app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter); app.use(urls.account, apiAuthenticationMiddleware, checkAdmin, accountRouter); diff --git a/static/src/app/app.config.ts b/static/src/app/app.config.ts index 2eb172f..868fc3a 100644 --- a/static/src/app/app.config.ts +++ b/static/src/app/app.config.ts @@ -28,6 +28,7 @@ export const RouteConfig = { statsPath: 'stats', userPath: 'users', overviewPath: 'overview', + playersPath: 'players', request: 'request', requestAwardPath: 'award', requestPromotionPath: 'promotion', From 0621d1d2bf502e67261df41e144e3c3ae5dc5ee7 Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Sun, 1 Oct 2017 20:24:35 +0200 Subject: [PATCH 2/7] Add clickable playername, playerservice + detail page routing --- static/src/app/app.config.ts | 2 +- static/src/app/app.routing.ts | 8 ++--- static/src/app/models/model-interfaces.ts | 6 ++++ .../services/player-service/player.service.ts | 18 +++++++++++ .../campaign-player-detail.component.css | 12 +++++++ .../campaign-player-detail.component.html | 5 +++ .../campaign-player-detail.component.ts | 32 +++++++++++++++++++ static/src/app/statistic/stats.module.ts | 3 +- static/src/app/statistic/stats.routing.ts | 10 ++++-- .../war-detail/war-detail.component.css | 1 + .../war-detail/war-detail.component.html | 3 +- .../war-detail/war-detail.component.ts | 8 ++++- 12 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 static/src/app/services/player-service/player.service.ts create mode 100644 static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css create mode 100644 static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html create mode 100644 static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts diff --git a/static/src/app/app.config.ts b/static/src/app/app.config.ts index 868fc3a..ad97dbe 100644 --- a/static/src/app/app.config.ts +++ b/static/src/app/app.config.ts @@ -11,6 +11,7 @@ export class AppConfig { public readonly apiSquadPath = this.apiUrl + '/squads/'; public readonly apiUserPath = this.apiUrl + '/users/'; public readonly apiOverviewPath = this.apiUrl + '/overview'; + public readonly apiPlayersPath = this.apiUrl + '/players'; public readonly apiRequestAwardPath = this.apiUrl + '/request/award'; public readonly apiPromotionPath = this.apiUrl + '/request/promotion'; public readonly apiWarPath = this.apiUrl + '/wars'; @@ -28,7 +29,6 @@ export const RouteConfig = { statsPath: 'stats', userPath: 'users', overviewPath: 'overview', - playersPath: 'players', request: 'request', requestAwardPath: 'award', requestPromotionPath: 'promotion', diff --git a/static/src/app/app.routing.ts b/static/src/app/app.routing.ts index 47e9acd..767f6cd 100644 --- a/static/src/app/app.routing.ts +++ b/static/src/app/app.routing.ts @@ -62,10 +62,10 @@ export const appRoutes: Routes = [ path: '404', component: NotFoundComponent }, - { - path: '**', - redirectTo: '/404' - } // always configure this last - first matching route gets processed + // { + // path: '**', + // redirectTo: '/404' + // } // always configure this last - first matching route gets processed ]; export const appRouting = RouterModule.forRoot(appRoutes); diff --git a/static/src/app/models/model-interfaces.ts b/static/src/app/models/model-interfaces.ts index a4ccc0a..d5b536f 100644 --- a/static/src/app/models/model-interfaces.ts +++ b/static/src/app/models/model-interfaces.ts @@ -29,6 +29,12 @@ export interface Player { flagTouch?: number; } +export interface CampaignPlayer { + name?: string; + campaign?: Campaign; + players?: Player[]; +} + export interface Campaign { _id?: string; title?: string; diff --git a/static/src/app/services/player-service/player.service.ts b/static/src/app/services/player-service/player.service.ts new file mode 100644 index 0000000..ae231cc --- /dev/null +++ b/static/src/app/services/player-service/player.service.ts @@ -0,0 +1,18 @@ +import {Injectable} from "@angular/core"; +import {AppConfig} from "../../app.config"; +import {HttpClient} from "../http-client"; + +@Injectable() +export class PlayerService { + + constructor(private http: HttpClient, + private config: AppConfig) { + } + + getCampaignPlayer(campaignId: string, playerName: string) { + return this.http.get(this.config.apiPlayersPath + '/' + campaignId + '/' + playerName) + .map(res => res.json()) + } + +} + diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css new file mode 100644 index 0000000..039c8c0 --- /dev/null +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css @@ -0,0 +1,12 @@ +.overview { + position: fixed; + overflow-y: scroll; + overflow-x: hidden; + border-left: thin solid lightgrey; + bottom: 20px; + width: 100%; + padding-left: 50px; + padding-top: 70px; + margin-left: 10px; + height: 100vh; +} diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html new file mode 100644 index 0000000..803341a --- /dev/null +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -0,0 +1,5 @@ +
+ +

{{campaignPlayer.name}}

+ +
diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts new file mode 100644 index 0000000..95fdf41 --- /dev/null +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts @@ -0,0 +1,32 @@ +import {Component} from "@angular/core"; +import {ActivatedRoute} from "@angular/router"; +import {CampaignPlayer} from "../../models/model-interfaces"; +import {PlayerService} from "../../services/player-service/player.service"; + + +@Component({ + selector: 'campaign-player-detail', + templateUrl: './campaign-player-detail.component.html', + styleUrls: ['./campaign-player-detail.component.css'] +}) +export class CampaignPlayerDetailComponent { + + campaignPlayer: CampaignPlayer = {}; + + fractionRadioSelect: string; + + + constructor(private route: ActivatedRoute, + private playerService: PlayerService) { + } + + ngOnInit() { + this.route.params + .map(params => [params['id'], params['playerName']]) + .flatMap(id => this.playerService.getCampaignPlayer(id[0], id[1])) + .subscribe(campaignPlayer => { + this.campaignPlayer = campaignPlayer; + }); + } + +} diff --git a/static/src/app/statistic/stats.module.ts b/static/src/app/statistic/stats.module.ts index 15b9e9d..ed3e8ab 100644 --- a/static/src/app/statistic/stats.module.ts +++ b/static/src/app/statistic/stats.module.ts @@ -7,12 +7,13 @@ import {LineChartModule, PieChartModule} from "@swimlane/ngx-charts"; import {AccordionModule, CarouselModule} from "ngx-bootstrap"; import {CampaignService} from "../services/campaign-service/campaign.service"; import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {PlayerService} from "../services/player-service/player.service"; @NgModule({ declarations: statsRoutingComponents, imports: [CommonModule, SharedModule, statsRouterModule, LineChartModule, PieChartModule, AccordionModule.forRoot(), CarouselModule.forRoot(), NgxDatatableModule], - providers: [WarService, CampaignService] + providers: [WarService, CampaignService, PlayerService] }) export class StatsModule { static routes = statsRouterModule; diff --git a/static/src/app/statistic/stats.routing.ts b/static/src/app/statistic/stats.routing.ts index acff442..dbc6951 100644 --- a/static/src/app/statistic/stats.routing.ts +++ b/static/src/app/statistic/stats.routing.ts @@ -7,6 +7,7 @@ import {StatisticOverviewComponent} from "./overview/stats-overview.component"; import {WarItemComponent} from "./war-list/war-item.component"; import {ModuleWithProviders} from "@angular/core"; import {CampaignSubmitComponent} from "./campaign-submit/campaign-submit.component"; +import {CampaignPlayerDetailComponent} from "./campaign-player-detail/campaign-player-detail.component"; export const statsRoutes: Routes = [{ @@ -37,10 +38,15 @@ export const statsRoutes: Routes = [{ path: 'war/:id', component: WarDetailComponent, outlet: 'right' - }]; + }, + { + path: 'campaign-player/:id/:playerName', + component: CampaignPlayerDetailComponent, + outlet: 'right' + },]; export const statsRouterModule: ModuleWithProviders = RouterModule.forChild(statsRoutes); export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, CampaignSubmitComponent, - WarListComponent, WarSubmitComponent, WarDetailComponent, WarItemComponent]; + WarListComponent, WarSubmitComponent, WarDetailComponent, CampaignPlayerDetailComponent, WarItemComponent]; diff --git a/static/src/app/statistic/war-detail/war-detail.component.css b/static/src/app/statistic/war-detail/war-detail.component.css index ac08fbe..06ae73a 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.css +++ b/static/src/app/statistic/war-detail/war-detail.component.css @@ -13,6 +13,7 @@ .player-name { font-weight: bold; + cursor: pointer; } .text-opfor { diff --git a/static/src/app/statistic/war-detail/war-detail.component.html b/static/src/app/statistic/war-detail/war-detail.component.html index 6ff3d83..1e9367d 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.html +++ b/static/src/app/statistic/war-detail/war-detail.component.html @@ -60,7 +60,8 @@ [cssClasses]='customClasses'> - + {{value}} diff --git a/static/src/app/statistic/war-detail/war-detail.component.ts b/static/src/app/statistic/war-detail/war-detail.component.ts index 4d84ef6..5fa8448 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.ts +++ b/static/src/app/statistic/war-detail/war-detail.component.ts @@ -1,5 +1,5 @@ import {Component} from "@angular/core"; -import {ActivatedRoute} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; import {WarService} from "../../services/war-service/war.service"; import {War} from "../../models/model-interfaces"; @@ -29,6 +29,7 @@ export class WarDetailComponent { }; constructor(private route: ActivatedRoute, + private router: Router, private warService: WarService) { Object.assign(this, this.playerChart) } @@ -54,6 +55,11 @@ export class WarDetailComponent { }); } + selectPlayerDetail(playerName) { + this.router.navigate(['../../campaign-player/' + this.war.campaign + '/' + playerName], + {relativeTo: this.route}); + } + filterPlayersByFraction(fraction: string) { if (fraction) { this.rows = this.war.players.filter((player) => { From 1bfa8ae539fad712a4ba9cd6c865ebe268a190b3 Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Mon, 2 Oct 2017 14:41:17 +0200 Subject: [PATCH 3/7] Add player kill chart --- .../campaign-player-detail.component.css | 10 ++++- .../campaign-player-detail.component.html | 24 +++++++++- .../campaign-player-detail.component.ts | 45 ++++++++++++++++++- .../overview/stats-overview.component.ts | 7 +-- static/src/app/utils/chart-utils.ts | 10 +++++ 5 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 static/src/app/utils/chart-utils.ts diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css index 039c8c0..7afe078 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css @@ -4,9 +4,17 @@ overflow-x: hidden; border-left: thin solid lightgrey; bottom: 20px; - width: 100%; + width: 80%; padding-left: 50px; + padding-right: 5%; padding-top: 70px; margin-left: 10px; height: 100vh; } + +.chart-container { + width: 90%; + min-width: 600px; + height: 300px; + margin: auto; +} diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html index 803341a..5b3d47f 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -1,5 +1,27 @@
-

{{campaignPlayer.name}}

+

Spielerstatistik - {{campaignPlayer.name}}

+

{{campaignPlayer.campaign.title}} Kampagne

+ +
+ + +
diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts index 95fdf41..ce9a989 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts @@ -2,6 +2,7 @@ import {Component} from "@angular/core"; import {ActivatedRoute} from "@angular/router"; import {CampaignPlayer} from "../../models/model-interfaces"; import {PlayerService} from "../../services/player-service/player.service"; +import {ChartUtils} from "../../utils/chart-utils"; @Component({ @@ -11,9 +12,26 @@ import {PlayerService} from "../../services/player-service/player.service"; }) export class CampaignPlayerDetailComponent { - campaignPlayer: CampaignPlayer = {}; + campaignPlayer: CampaignPlayer = {campaign: {}, players: []}; - fractionRadioSelect: string; + killData: any[] = []; + + colorScheme = { + domain: ['#00ce12'] + }; + showRefLines = true; + showRefLabels = true; + refLines = []; + gradient = false; + xAxis = true; + yAxis = true; + legend = false; + legendTitle = ''; + showXAxisLabel = true; + showYAxisLabel = true; + autoscale = false; + timeline = false; + roundDomains = true; constructor(private route: ActivatedRoute, @@ -26,7 +44,30 @@ export class CampaignPlayerDetailComponent { .flatMap(id => this.playerService.getCampaignPlayer(id[0], id[1])) .subscribe(campaignPlayer => { this.campaignPlayer = campaignPlayer; + this.assignKillData(); }); } + private assignKillData() { + let killObj = { + "name": "Kills", + "series": [] + }; + let total = 0; + for (let i = 0; i < this.campaignPlayer.players.length; i++) { + const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date); + const warKills = this.campaignPlayer.players[i].kill; + killObj.series.push({ + name: warDateString, + value: warKills + }); + total += warKills; + } + + this.killData = [killObj]; + Object.assign(this, this.killData); + this.refLines.push({value: total / this.campaignPlayer.players.length, name: 'Durchschnitt'}); + + } + } diff --git a/static/src/app/statistic/overview/stats-overview.component.ts b/static/src/app/statistic/overview/stats-overview.component.ts index 2b624aa..8af5c9e 100644 --- a/static/src/app/statistic/overview/stats-overview.component.ts +++ b/static/src/app/statistic/overview/stats-overview.component.ts @@ -2,6 +2,7 @@ import {Component} from "@angular/core"; import {ActivatedRoute} from "@angular/router"; import {CarouselConfig} from "ngx-bootstrap"; import {CampaignService} from "../../services/campaign-service/campaign.service"; +import {ChartUtils} from "../../utils/chart-utils"; @Component({ @@ -94,11 +95,7 @@ export class StatisticOverviewComponent { for (let i = wars.length - 1; i >= 0; i--) { let j = wars.length - i - 1; - // const warDateString = new Date(wars[i].date); TODO: use ngx-chart timeline - const isoDate = wars[i].date.slice(0, 10); - const dayDate = parseInt(isoDate.slice(8, 10)) + 1; - const warDateString = (dayDate < 10 ? "0" + dayDate : dayDate) + '.' - + isoDate.slice(5, 7) + '.' + isoDate.slice(2, 4); + const warDateString = ChartUtils.getShortDateString(wars[i].date); pointsObj[0].series.push({ name: warDateString, diff --git a/static/src/app/utils/chart-utils.ts b/static/src/app/utils/chart-utils.ts new file mode 100644 index 0000000..0761dca --- /dev/null +++ b/static/src/app/utils/chart-utils.ts @@ -0,0 +1,10 @@ +export class ChartUtils { + + public static getShortDateString(date) : string { + const isoDate = date.slice(0, 10); + const dayDate = parseInt(isoDate.slice(8, 10)) + 1; + return (dayDate < 10 ? "0" + dayDate : dayDate) + '.' + + isoDate.slice(5, 7) + '.' + isoDate.slice(2, 4); + } + +} From aa74a0de9b68f0556c3618c5867b8420f19c064d Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Mon, 2 Oct 2017 17:09:30 +0200 Subject: [PATCH 4/7] implement user detail charts --- .../campaign-player-detail.component.css | 43 +++- .../campaign-player-detail.component.html | 184 ++++++++++++++++-- .../campaign-player-detail.component.ts | 124 ++++++++++-- static/src/app/statistic/stats.module.ts | 4 +- 4 files changed, 315 insertions(+), 40 deletions(-) diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css index 7afe078..3733481 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css @@ -5,16 +5,47 @@ border-left: thin solid lightgrey; bottom: 20px; width: 80%; - padding-left: 50px; + padding-left: 20px; padding-right: 5%; padding-top: 70px; - margin-left: 10px; height: 100vh; } -.chart-container { - width: 90%; - min-width: 600px; - height: 300px; +h2 { + padding: 10px; +} + +.sum-container { + width: 100%; + margin: auto; + clear: left; + padding: 2%; +} + +.gauge-container { + padding: 35px; +} + +.sum-bar-container { + width: 70%; + height: 400px; + margin: auto; + padding-left: 4%; +} + +.charts-parent { + clear: left; + padding-top: 50px; + width: 100%; margin: auto; } + +.chart-container { + width: 42%; + min-width: 500px; + height: 300px; + padding: 15px; + margin: 2%; + float: left; +} + diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html index 5b3d47f..ec93516 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -3,25 +3,169 @@

Spielerstatistik - {{campaignPlayer.name}}

{{campaignPlayer.campaign.title}} Kampagne

-
- - -
+ +
+
+ + + + + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts index ce9a989..27840e7 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts @@ -14,14 +14,39 @@ export class CampaignPlayerDetailComponent { campaignPlayer: CampaignPlayer = {campaign: {}, players: []}; + sumData: any[] = []; killData: any[] = []; + friendlyFireData: any[] = []; + deathData: any[] = []; + respawnData: any[] = []; + reviveData: any[] = []; + captureData: any[] = []; + + yAxisKill = 'Kills'; + yAxisFriendlyFire = 'FriendlyFire'; + yAxisDeath = 'Tode'; + yAxisRespawn = 'Respawn'; + yAxisRevive = 'Revive'; + yAxisCapture = 'Eroberungen'; + colorScheme = { domain: ['#00ce12'] }; + colorSchemeBar = { + domain: [ + '#2d5a00', '#455600', '#00561f', '#3f3b00', '#003c19', '#083c00' + ] + }; showRefLines = true; showRefLabels = true; - refLines = []; + killRefLines = []; + deathRefLines = []; + captureRefLines = []; + friendlyFireRefLines = []; + reviveRefLines = []; + respawnRefLines = []; + gradient = false; xAxis = true; yAxis = true; @@ -33,6 +58,19 @@ export class CampaignPlayerDetailComponent { timeline = false; roundDomains = true; + avgLabel = 'Durchschnitt'; + + totalKills; + totalFriendlyFire; + totalDeath; + totalRespawn; + totalRevive; + totalCapture; + + kdRatio = 0; + maxKd = 1.7; + respawnDeathRatio = 0; + constructor(private route: ActivatedRoute, private playerService: PlayerService) { @@ -44,30 +82,92 @@ export class CampaignPlayerDetailComponent { .flatMap(id => this.playerService.getCampaignPlayer(id[0], id[1])) .subscribe(campaignPlayer => { this.campaignPlayer = campaignPlayer; - this.assignKillData(); + this.killData = this.assignData(this.yAxisKill, "kill"); + this.friendlyFireData = this.assignData(this.yAxisFriendlyFire, "friendlyFire"); + this.deathData = this.assignData(this.yAxisDeath, "death"); + this.respawnData = this.assignData(this.yAxisRespawn, "respawn"); + this.reviveData = this.assignData(this.yAxisRevive, "revive"); + this.captureData = this.assignData(this.yAxisCapture, "flagTouch"); + + this.kdRatio = parseFloat((this.totalKills / this.totalDeath).toFixed(2)); + if (this.kdRatio > 1) this.maxKd = this.kdRatio * 1.7; + + this.respawnDeathRatio = parseFloat((this.totalRespawn / this.totalDeath).toFixed(2)); + + this.sumData = [ + { + name: this.yAxisKill, + value: this.totalKills + }, + { + name: this.yAxisFriendlyFire, + value: this.totalFriendlyFire + }, + { + name: this.yAxisDeath, + value: this.totalDeath + }, + { + name: this.yAxisRespawn, + value: this.totalDeath + }, + { + name: this.yAxisRevive, + value: this.totalRevive + }, + { + name: this.yAxisCapture, + value: this.totalCapture + } + ]; + + Object.assign(this, [this.sumData, this.killData, this.friendlyFireData, this.deathData, this.respawnData, this.reviveData, this.captureData]); }); } - private assignKillData() { + private assignData(label, field) { let killObj = { - "name": "Kills", - "series": [] + name: label, + series: [] }; + const playerLength = this.campaignPlayer.players.length; let total = 0; - for (let i = 0; i < this.campaignPlayer.players.length; i++) { + for (let i = 0; i < playerLength; i++) { const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date); - const warKills = this.campaignPlayer.players[i].kill; + const warKills = this.campaignPlayer.players[i][field]; killObj.series.push({ name: warDateString, value: warKills }); total += warKills; } - - this.killData = [killObj]; - Object.assign(this, this.killData); - this.refLines.push({value: total / this.campaignPlayer.players.length, name: 'Durchschnitt'}); - + switch (field) { + case 'kill': + this.killRefLines.push({value: total / playerLength, name: this.avgLabel}); + this.totalKills = total; + break; + case 'friendlyFire': + this.friendlyFireRefLines.push({value: total / playerLength, name: this.avgLabel}); + this.totalFriendlyFire = total; + break; + case 'death': + this.deathRefLines.push({value: total / playerLength, name: this.avgLabel}); + this.totalDeath = total; + break; + case 'respawn': + this.respawnRefLines.push({value: total / playerLength, name: this.avgLabel}); + this.totalRespawn = total; + break; + case 'revive': + this.reviveRefLines.push({value: total / playerLength, name: this.avgLabel}); + this.totalRevive = total; + break; + case 'flagTouch': + this.captureRefLines.push({value: total / playerLength, name: this.avgLabel}); + this.totalCapture = total; + break; + } + return [killObj]; } } diff --git a/static/src/app/statistic/stats.module.ts b/static/src/app/statistic/stats.module.ts index ed3e8ab..58965d7 100644 --- a/static/src/app/statistic/stats.module.ts +++ b/static/src/app/statistic/stats.module.ts @@ -3,7 +3,7 @@ import {CommonModule} from "@angular/common"; import {SharedModule} from "../shared.module"; import {statsRouterModule, statsRoutingComponents} from "./stats.routing"; import {WarService} from "../services/war-service/war.service"; -import {LineChartModule, PieChartModule} from "@swimlane/ngx-charts"; +import {GaugeModule, LineChartModule, NgxChartsModule, PieChartModule} from "@swimlane/ngx-charts"; import {AccordionModule, CarouselModule} from "ngx-bootstrap"; import {CampaignService} from "../services/campaign-service/campaign.service"; import {NgxDatatableModule} from "@swimlane/ngx-datatable"; @@ -11,7 +11,7 @@ import {PlayerService} from "../services/player-service/player.service"; @NgModule({ declarations: statsRoutingComponents, - imports: [CommonModule, SharedModule, statsRouterModule, LineChartModule, PieChartModule, + imports: [CommonModule, SharedModule, statsRouterModule, NgxChartsModule, AccordionModule.forRoot(), CarouselModule.forRoot(), NgxDatatableModule], providers: [WarService, CampaignService, PlayerService] }) From 325ead18c724998620df7176fa6b86bc4fc249ae Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Mon, 2 Oct 2017 18:10:57 +0200 Subject: [PATCH 5/7] Optimize style & fix username encoding --- package.json | 2 +- .../campaign-player-detail.component.css | 7 +++++++ .../campaign-player-detail.component.html | 4 +++- .../campaign-player-detail.component.ts | 14 +++++++++----- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 15f21aa..fe735aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opt-cc", - "version": "1.4.4", + "version": "1.5.0", "license": "MIT", "private": true, "scripts": { diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css index 3733481..0332e44 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.css @@ -15,6 +15,13 @@ h2 { padding: 10px; } +.btn-back { + clear: both; + float: left; + width: 120px; + margin-left: 10px; +} + .sum-container { width: 100%; margin: auto; diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html index ec93516..1aa64aa 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -3,6 +3,8 @@

Spielerstatistik - {{campaignPlayer.name}}

{{campaignPlayer.campaign.title}} Kampagne

+ < Zurück +
@@ -15,7 +17,7 @@ [min]="0" [units]="'Kill/Death'"> - + [params['id'], params['playerName']]) - .flatMap(id => this.playerService.getCampaignPlayer(id[0], id[1])) + .flatMap(id => this.playerService.getCampaignPlayer(id[0], encodeURIComponent(id[1]))) .subscribe(campaignPlayer => { this.campaignPlayer = campaignPlayer; this.killData = this.assignData(this.yAxisKill, "kill"); @@ -109,7 +109,7 @@ export class CampaignPlayerDetailComponent { }, { name: this.yAxisRespawn, - value: this.totalDeath + value: this.totalRespawn }, { name: this.yAxisRevive, @@ -170,4 +170,8 @@ export class CampaignPlayerDetailComponent { return [killObj]; } + navigateBack() { + this.location.back(); + } + } From 2e2879240949a29f5c069811c62a995e29d01fe7 Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Mon, 2 Oct 2017 19:29:53 +0200 Subject: [PATCH 6/7] Add fade in --- .../campaign-player-detail.component.html | 2 +- .../campaign-player-detail/campaign-player-detail.component.ts | 2 +- static/src/app/statistic/overview/stats-overview.component.html | 2 +- static/src/app/statistic/overview/stats-overview.component.ts | 2 +- static/src/app/statistic/war-detail/war-detail.component.html | 2 +- static/src/app/statistic/war-detail/war-detail.component.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html index 1aa64aa..272d3e6 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -1,4 +1,4 @@ -
+

Spielerstatistik - {{campaignPlayer.name}}

{{campaignPlayer.campaign.title}} Kampagne

diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts index a602f15..5733f16 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts @@ -9,7 +9,7 @@ import {Location} from '@angular/common'; @Component({ selector: 'campaign-player-detail', templateUrl: './campaign-player-detail.component.html', - styleUrls: ['./campaign-player-detail.component.css'] + styleUrls: ['./campaign-player-detail.component.css', '../../style/list-entry.css'] }) export class CampaignPlayerDetailComponent { diff --git a/static/src/app/statistic/overview/stats-overview.component.html b/static/src/app/statistic/overview/stats-overview.component.html index cff395c..91163ea 100644 --- a/static/src/app/statistic/overview/stats-overview.component.html +++ b/static/src/app/statistic/overview/stats-overview.component.html @@ -12,7 +12,7 @@ - +

Gesamtpunktzahl

diff --git a/static/src/app/statistic/overview/stats-overview.component.ts b/static/src/app/statistic/overview/stats-overview.component.ts index 8af5c9e..0a01419 100644 --- a/static/src/app/statistic/overview/stats-overview.component.ts +++ b/static/src/app/statistic/overview/stats-overview.component.ts @@ -8,7 +8,7 @@ import {ChartUtils} from "../../utils/chart-utils"; @Component({ selector: 'stats-overview', templateUrl: './stats-overview.component.html', - styleUrls: ['./stats-overview.component.css'], + styleUrls: ['./stats-overview.component.css', '../../style/list-entry.css'], inputs: ['campaigns'], providers: [{provide: CarouselConfig, useValue: {interval: false}}] }) diff --git a/static/src/app/statistic/war-detail/war-detail.component.html b/static/src/app/statistic/war-detail/war-detail.component.html index 1e9367d..dd55dba 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.html +++ b/static/src/app/statistic/war-detail/war-detail.component.html @@ -1,4 +1,4 @@ -
+

{{war.title}} - vom {{war.date | date: 'dd.MM.yyyy'}}

diff --git a/static/src/app/statistic/war-detail/war-detail.component.ts b/static/src/app/statistic/war-detail/war-detail.component.ts index 5fa8448..c112347 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.ts +++ b/static/src/app/statistic/war-detail/war-detail.component.ts @@ -7,7 +7,7 @@ import {War} from "../../models/model-interfaces"; @Component({ selector: 'war-detail', templateUrl: './war-detail.component.html', - styleUrls: ['./war-detail.component.css'] + styleUrls: ['./war-detail.component.css', '../../style/list-entry.css'] }) export class WarDetailComponent { From 0fa03820cb9d1f8777bc5510c184ea3f3edb5376 Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Mon, 2 Oct 2017 20:03:42 +0200 Subject: [PATCH 7/7] make war detail row clickable --- static/src/app/app.routing.ts | 8 ++++---- .../campaign-player-detail.component.html | 8 +------- .../campaign-player-detail.component.ts | 3 ++- .../statistic/overview/stats-overview.component.html | 2 -- static/src/app/statistic/stats.module.ts | 2 +- .../statistic/war-detail/war-detail.component.css | 2 +- .../statistic/war-detail/war-detail.component.html | 6 ++++-- .../app/statistic/war-detail/war-detail.component.ts | 12 +++++++----- 8 files changed, 20 insertions(+), 23 deletions(-) diff --git a/static/src/app/app.routing.ts b/static/src/app/app.routing.ts index 767f6cd..47e9acd 100644 --- a/static/src/app/app.routing.ts +++ b/static/src/app/app.routing.ts @@ -62,10 +62,10 @@ export const appRoutes: Routes = [ path: '404', component: NotFoundComponent }, - // { - // path: '**', - // redirectTo: '/404' - // } // always configure this last - first matching route gets processed + { + path: '**', + redirectTo: '/404' + } // always configure this last - first matching route gets processed ]; export const appRouting = RouterModule.forRoot(appRoutes); diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html index 272d3e6..9542f3f 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.html @@ -23,7 +23,7 @@ [scheme]="colorScheme" [value]="respawnDeathRatio" [previousValue]="0.5" - [max]="1" + [max]="maxRespawnDeathRatio" [min]="0" [units]="'Respawn/Death'"> @@ -54,7 +54,6 @@ [xAxis]="xAxis" [yAxis]="yAxis" [legend]="legend" - [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel" [yAxisLabel]="yAxisKill" @@ -75,7 +74,6 @@ [xAxis]="xAxis" [yAxis]="yAxis" [legend]="legend" - [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel" [yAxisLabel]="yAxisFriendlyFire" @@ -96,7 +94,6 @@ [xAxis]="xAxis" [yAxis]="yAxis" [legend]="legend" - [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel" [yAxisLabel]="yAxisDeath" @@ -117,7 +114,6 @@ [xAxis]="xAxis" [yAxis]="yAxis" [legend]="legend" - [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel" [yAxisLabel]="yAxisRespawn" @@ -138,7 +134,6 @@ [xAxis]="xAxis" [yAxis]="yAxis" [legend]="legend" - [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel" [yAxisLabel]="yAxisRevive" @@ -159,7 +154,6 @@ [xAxis]="xAxis" [yAxis]="yAxis" [legend]="legend" - [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel" [yAxisLabel]="yAxisCapture" diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts index 5733f16..7ccd760 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts @@ -52,7 +52,6 @@ export class CampaignPlayerDetailComponent { xAxis = true; yAxis = true; legend = false; - legendTitle = ''; showXAxisLabel = true; showYAxisLabel = true; autoscale = false; @@ -68,7 +67,9 @@ export class CampaignPlayerDetailComponent { kdRatio = 0; maxKd = 1.7; + respawnDeathRatio = 0; + maxRespawnDeathRatio = 1; constructor(private route: ActivatedRoute, diff --git a/static/src/app/statistic/overview/stats-overview.component.html b/static/src/app/statistic/overview/stats-overview.component.html index 91163ea..7ab2b59 100644 --- a/static/src/app/statistic/overview/stats-overview.component.html +++ b/static/src/app/statistic/overview/stats-overview.component.html @@ -10,8 +10,6 @@ (click)="goToSlide(1)">Spielerzahlen
- -
diff --git a/static/src/app/statistic/stats.module.ts b/static/src/app/statistic/stats.module.ts index 58965d7..e2edbc4 100644 --- a/static/src/app/statistic/stats.module.ts +++ b/static/src/app/statistic/stats.module.ts @@ -3,7 +3,7 @@ import {CommonModule} from "@angular/common"; import {SharedModule} from "../shared.module"; import {statsRouterModule, statsRoutingComponents} from "./stats.routing"; import {WarService} from "../services/war-service/war.service"; -import {GaugeModule, LineChartModule, NgxChartsModule, PieChartModule} from "@swimlane/ngx-charts"; +import {NgxChartsModule} from "@swimlane/ngx-charts"; import {AccordionModule, CarouselModule} from "ngx-bootstrap"; import {CampaignService} from "../services/campaign-service/campaign.service"; import {NgxDatatableModule} from "@swimlane/ngx-datatable"; diff --git a/static/src/app/statistic/war-detail/war-detail.component.css b/static/src/app/statistic/war-detail/war-detail.component.css index 06ae73a..454c430 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.css +++ b/static/src/app/statistic/war-detail/war-detail.component.css @@ -13,7 +13,6 @@ .player-name { font-weight: bold; - cursor: pointer; } .text-opfor { @@ -52,6 +51,7 @@ :host /deep/ .datatable-body-row { color: #222222; border-bottom: 1px solid grey; + cursor: pointer; } :host /deep/ .datatable-body-row:hover { diff --git a/static/src/app/statistic/war-detail/war-detail.component.html b/static/src/app/statistic/war-detail/war-detail.component.html index dd55dba..fa69568 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.html +++ b/static/src/app/statistic/war-detail/war-detail.component.html @@ -57,10 +57,12 @@ [messages]="{emptyMessage: 'Loading...'}" [headerHeight]="cellHeight" [rowHeight]="cellHeight" - [cssClasses]='customClasses'> + [cssClasses]='customClasses' + [selectionType]="'single'" + (select)="selectPlayerDetail($event)"> - {{value}} diff --git a/static/src/app/statistic/war-detail/war-detail.component.ts b/static/src/app/statistic/war-detail/war-detail.component.ts index c112347..3695170 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.ts +++ b/static/src/app/statistic/war-detail/war-detail.component.ts @@ -55,11 +55,6 @@ export class WarDetailComponent { }); } - selectPlayerDetail(playerName) { - this.router.navigate(['../../campaign-player/' + this.war.campaign + '/' + playerName], - {relativeTo: this.route}); - } - filterPlayersByFraction(fraction: string) { if (fraction) { this.rows = this.war.players.filter((player) => { @@ -70,4 +65,11 @@ export class WarDetailComponent { } } + selectPlayerDetail(player) { + if (player && player.selected && player.selected.length > 0) { + this.router.navigate(['../../campaign-player/' + this.war.campaign + '/' + player.selected[0].name], + {relativeTo: this.route}); + } + } + }