diff --git a/static/src/app/statistic/stats.routing.ts b/static/src/app/statistic/stats.routing.ts
index a1e914a..92b1188 100644
--- a/static/src/app/statistic/stats.routing.ts
+++ b/static/src/app/statistic/stats.routing.ts
@@ -9,6 +9,7 @@ import {ModuleWithProviders} from "@angular/core";
import {CampaignSubmitComponent} from "./campaign-submit/campaign-submit.component";
import {CampaignPlayerDetailComponent} from "./campaign-player-detail/campaign-player-detail.component";
import {WarDetailHeaderComponent} from "./war-detail-header/war-detail-header.component";
+import {WarDetailFractionComponent} from "./war-detail-fraction/war-detail-fraction.component";
export const statsRoutes: Routes = [{
@@ -49,6 +50,6 @@ export const statsRoutes: Routes = [{
export const statsRouterModule: ModuleWithProviders = RouterModule.forChild(statsRoutes);
export const statsRoutingComponents = [StatisticComponent, StatisticOverviewComponent, CampaignSubmitComponent,
- WarListComponent, WarSubmitComponent, WarDetailHeaderComponent, WarDetailComponent, CampaignPlayerDetailComponent,
- WarItemComponent];
+ WarListComponent, WarSubmitComponent, WarDetailHeaderComponent, WarDetailComponent, WarDetailFractionComponent,
+ CampaignPlayerDetailComponent, WarItemComponent];
diff --git a/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.css b/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.css
new file mode 100644
index 0000000..ac69342
--- /dev/null
+++ b/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.css
@@ -0,0 +1,129 @@
+.vertical-spacer {
+ height: 205px;
+ float: left;
+ width: 4%;
+}
+
+.head-field {
+ font-size: 24px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+@media screen and (min-width: 1500px) {
+ .vertical-spacer {
+ width: 15%;
+ }
+}
+
+@media screen and (min-width: 2000px) {
+ .vertical-spacer {
+ width: 20%;
+ }
+}
+
+.overview {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ border-left: thin solid lightgrey;
+ bottom: 20px;
+ height: 100vh;
+}
+
+.player-name {
+ font-weight: bold;
+}
+
+/* ########### TABS ########### */
+
+:host /deep/ .nav-tabs {
+ padding-left: 35% !important;
+}
+
+:host /deep/ .nav-link {
+ background: #4b4b4b;
+ color: white;
+}
+
+:host /deep/ .nav-link:hover {
+ background: #afafaf;
+ color: #000;
+}
+
+:host /deep/ .nav-tabs > li.active > a {
+ background: #222222;
+ color: white;
+}
+
+/* ########### DATATABLE ########### */
+
+:host /deep/ .datatable-header {
+ background: #222222;
+ font-weight: 700;
+ border-radius: 10px 10px 0 0;
+ color: white;
+}
+
+:host /deep/ span.datatable-header-cell-label, :host /deep/ div.datatable-body-cell-label {
+ padding-left: 8px;
+}
+
+:host /deep/ .ngx-datatable .datatable-header {
+ /*vertical center alignment*/
+ display: table-cell;
+ vertical-align: middle;
+}
+
+:host /deep/ .ngx-datatable .datatable-body .datatable-body-row > div {
+ /*vertical alignment*/
+ position: relative;
+ top: 10px;
+}
+
+:host /deep/ .datatable-body-row {
+ color: #222222;
+ border-bottom: 1px solid grey;
+ cursor: pointer;
+}
+
+:host /deep/ .datatable-body-row:hover {
+ background-color: #f7f7f7;
+}
+
+/* ########### CHART-TAB ######## */
+
+.btn-dark {
+ background: #4b4b4b;
+ color: #f5f5f5;
+ border-color: #000;
+}
+
+.btn-dark:hover {
+ background: #afafaf;
+ color: #f5f5f5;
+}
+
+.btn-dark.active {
+ background: #222222;
+}
+
+.chart-container {
+ width: 95%;
+ margin: 2%;
+ min-width: 900px;
+ height: 600px;
+ padding: 15px;
+ float: left;
+}
+
+.chart-select-group {
+ width: 50%;
+ margin: auto;
+ position: inherit;
+ display: block;
+ vertical-align: middle;
+}
+
+/*.dropdown-menu > li > a {*/
+ /*cursor: pointer;*/
+/*}*/
diff --git a/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.html b/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.html
new file mode 100644
index 0000000..82719c5
--- /dev/null
+++ b/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.html
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.ts b/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.ts
new file mode 100644
index 0000000..cec9da3
--- /dev/null
+++ b/static/src/app/statistic/war-detail-fraction/war-detail-fraction.component.ts
@@ -0,0 +1,344 @@
+import {Component, ElementRef, SimpleChanges, ViewChild} from "@angular/core";
+import {LogsService} from "../../services/logs/logs.service";
+import * as d3 from "d3";
+import {ChartUtils} from "../../utils/chart-utils";
+import {Fraction} from "../../utils/fraction.enum";
+import {War} from "../../models/model-interfaces";
+
+
+@Component({
+ selector: 'war-detail-fraction',
+ templateUrl: './war-detail-fraction.component.html',
+ inputs: ['war'],
+ styleUrls: ['./war-detail-fraction.component.css', '../../style/list-entry.css', '../../style/hide-scrollbar.css']
+})
+export class WarDetailFractionComponent {
+
+ readonly fraction = Fraction;
+
+ @ViewChild('overview') private overviewContainer: ElementRef;
+
+ war: War;
+
+ logData: any;
+
+ startDateObj: Date;
+
+ initialized: any;
+
+ public chartSelectModel: string;
+
+ lineChartData: any[] = [];
+ areaChartData: any[] = [];
+
+ tmpPointData;
+ tmpBudgetData;
+ tmpKillData;
+ tmpFrienlyFireData;
+ tmpTransportData;
+ tmpReviveData;
+ tmpStabilizeData;
+ tmpFlagCaptureData;
+
+ colorScheme = {
+ domain: [Fraction.COLOR_BLUFOR, Fraction.COLOR_OPFOR]
+ };
+
+ labelPoints = 'Punkte';
+ labelBudget = 'Budget';
+ labelKill = 'Kills';
+ labelFriendlyFire = 'FriendlyFire';
+ labelTransport = 'Lufttransport';
+ labelRevive = 'Revive';
+ labelStabilize = 'Stabilisiert';
+ labelFlag = 'Flaggenbesitz';
+ lineChartLabel: string = this.labelPoints;
+
+ showLineChart = true;
+ stepCurve = d3.curveStepAfter;
+ gradient = false;
+ yAxis = true;
+ xAxis = true;
+ legend = false;
+ legendTitle = false;
+ showXAxisLabel = false;
+ showYAxisLabel = true;
+ autoscale = true;
+ timeline = false;
+ roundDomains = true;
+
+ constructor(private logsService: LogsService) {
+ }
+
+ ngOnInit() {
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.war) {
+ this.initialized = {
+ budget: false,
+ kill: false,
+ revive: false,
+ transport: false,
+ flag: false
+ };
+ Object.assign(this, [this.lineChartData, this.areaChartData]);
+ this.chartSelectModel = this.labelPoints;
+
+ this.startDateObj = new Date(this.war.date);
+ this.startDateObj.setHours(0);
+ this.startDateObj.setMinutes(1);
+ this.loadFractionData();
+ }
+ }
+
+ selectChart() {
+ if (this.chartSelectModel !== this.labelFlag) {
+ this.showLineChart = true;
+ this.lineChartLabel = this.chartSelectModel;
+ switch (this.chartSelectModel) {
+ case this.labelPoints:
+ this.lineChartData = this.tmpPointData;
+ break;
+ case this.labelBudget:
+ this.initBudgetData();
+ this.lineChartData = this.tmpBudgetData;
+ break;
+ case this.labelKill:
+ this.initKillData();
+ this.lineChartData = this.tmpKillData;
+ break;
+ case this.labelFriendlyFire:
+ this.initKillData();
+ this.lineChartData = this.tmpFrienlyFireData;
+ break;
+ case this.labelRevive:
+ this.initRevive();
+ this.lineChartData = this.tmpReviveData;
+ break;
+ case this.labelStabilize:
+ this.initRevive();
+ this.lineChartData = this.tmpStabilizeData;
+ break;
+ case this.labelTransport:
+ this.initTransportData();
+ this.lineChartData = this.tmpTransportData;
+ break;
+ }
+ } else {
+ this.initFlagHoldData();
+ this.showLineChart = false;
+ this.areaChartData = this.tmpFlagCaptureData;
+ }
+ }
+
+ loadFractionData() {
+ this.initializeTempCollections();
+
+ this.logsService.getFullLog(this.war._id).subscribe((data) => {
+ this.logData = data;
+ this.initPointData();
+ this.showLineChart = true;
+ this.lineChartLabel = this.labelPoints;
+ this.lineChartData = this.tmpPointData;
+ });
+ }
+
+ initPointData() {
+ this.logData.points.forEach(pointEntry => {
+ this.tmpPointData[0].series.push(ChartUtils.getSeriesEntry(new Date(pointEntry.time), pointEntry.ptBlufor));
+ this.tmpPointData[1].series.push(ChartUtils.getSeriesEntry(new Date(pointEntry.time), pointEntry.ptOpfor));
+ });
+ this.addFinalTimeData(this.tmpPointData);
+ }
+
+ initBudgetData() {
+ if (this.initialized.budget) {
+ return;
+ }
+ this.logData.budget.forEach(budgetEntry => {
+ const budgetEntryDate = new Date(budgetEntry.time);
+ const fractionChange = budgetEntry.fraction === 'BLUFOR' ? 0 : 1;
+ const fractionOld = budgetEntry.fraction !== 'BLUFOR' ? 0 : 1;
+
+ if (this.isTwoMinutesAhead(budgetEntryDate, this.tmpBudgetData)) {
+ this.tmpBudgetData[fractionChange].series.push(ChartUtils.getSeriesEntry(new Date(budgetEntry.time), budgetEntry.newBudget));
+ this.tmpBudgetData[fractionOld].series.push(ChartUtils.getSeriesEntry(new Date(budgetEntry.time),
+ this.tmpBudgetData[fractionOld].series[this.tmpBudgetData[fractionOld].series.length - 1].value));
+ }
+ });
+ this.addFinalTimeData(this.tmpBudgetData);
+ this.initialized.budget = true;
+ }
+
+ initKillData() {
+ if (this.initialized.kill) {
+ return;
+ }
+ let killCountBlufor = 0, killCountOpfor = 0, ffKillCountBlufor = 0, ffKillCountOpfor = 0;
+
+ for (const {killEntry, index} of this.logData.kill.map((killEntry, index) => ({killEntry, index}))) {
+ const killEntryDate = new Date(killEntry.time);
+ if (killEntry.friendlyFire === false) {
+ if (killEntry.fraction === 'BLUFOR') {
+ killCountBlufor++;
+ }
+ if (killEntry.fraction === 'OPFOR') {
+ killCountOpfor++;
+ }
+ if (this.isTwoMinutesAhead(killEntryDate, this.tmpKillData)) {
+ this.tmpKillData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountBlufor));
+ this.tmpKillData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountOpfor));
+ }
+ } else {
+ if (killEntry.fraction === 'BLUFOR') {
+ ffKillCountBlufor++;
+ }
+ if (killEntry.fraction === 'OPFOR') {
+ ffKillCountOpfor++;
+ }
+ if (this.isTwoMinutesAhead(killEntryDate, this.tmpFrienlyFireData)) {
+ this.tmpFrienlyFireData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountBlufor));
+ this.tmpFrienlyFireData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountOpfor));
+ }
+ }
+ if (index === this.logData.kill.length - 1) {
+ this.tmpKillData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountBlufor));
+ this.tmpKillData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, killCountOpfor));
+ this.tmpFrienlyFireData[0].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountBlufor));
+ this.tmpFrienlyFireData[1].series.push(ChartUtils.getSeriesEntry(killEntryDate, ffKillCountOpfor));
+ }
+ }
+
+ this.addFinalTimeData(this.tmpKillData);
+ this.addFinalTimeData(this.tmpFrienlyFireData)
+ this.initialized.kill = true;
+ }
+
+ initRevive() {
+ if (this.initialized.revive) {
+ return;
+ }
+ let reviveCountBlufor = 0, reviveCountOpfor = 0, stabilizeCountBlufor = 0, stabilizeCountOpfor = 0;
+ for (const {reviveEntry, index} of this.logData.revive.map((reviveEntry, index) => ({reviveEntry, index}))) {
+ const reviveEntryDate = new Date(reviveEntry.time);
+ if (reviveEntry.stabilized === false) {
+ if (reviveEntry.fraction === 'BLUFOR') {
+ reviveCountBlufor++;
+ } else {
+ reviveCountOpfor++;
+ }
+ if (this.isTwoMinutesAhead(reviveEntryDate, this.tmpReviveData)) {
+ this.tmpReviveData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountBlufor));
+ this.tmpReviveData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountOpfor));
+ }
+ } else {
+ if (reviveEntry.fraction === 'BLUFOR') {
+ stabilizeCountBlufor++;
+ } else {
+ stabilizeCountOpfor++;
+ }
+ if (this.isTwoMinutesAhead(reviveEntryDate, this.tmpStabilizeData)) {
+ this.tmpStabilizeData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountBlufor));
+ this.tmpStabilizeData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountOpfor));
+ }
+ }
+ if (index === this.logData.revive.length - 1) {
+ this.tmpReviveData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountBlufor));
+ this.tmpReviveData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, reviveCountOpfor));
+ this.tmpStabilizeData[0].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountBlufor));
+ this.tmpStabilizeData[1].series.push(ChartUtils.getSeriesEntry(reviveEntryDate, stabilizeCountOpfor));
+ }
+ }
+ this.addFinalTimeData(this.tmpReviveData);
+ this.addFinalTimeData(this.tmpStabilizeData);
+ this.initialized.revive = true;
+ }
+
+ initTransportData() {
+ if (this.initialized.transport) {
+ return;
+ }
+ let transportCountBlufor = 0, transportCountOpfor = 0;
+ for (const {transportEntry, index} of this.logData.transport.map((transportEntry, index) => ({
+ transportEntry,
+ index
+ }))) {
+ const transportEntryDate = new Date(transportEntry.time);
+ if (transportEntry.fraction === 'BLUFOR') {
+ transportCountBlufor++;
+ } else {
+ transportCountOpfor++;
+ }
+ if (this.isTwoMinutesAhead(transportEntryDate, this.tmpTransportData) || index === this.logData.transport.length - 1) {
+ this.tmpTransportData[0].series.push(ChartUtils.getSeriesEntry(transportEntryDate, transportCountBlufor));
+ this.tmpTransportData[1].series.push(ChartUtils.getSeriesEntry(transportEntryDate, transportCountOpfor));
+ }
+ }
+ this.addFinalTimeData(this.tmpTransportData);
+ this.initialized.transport = true;
+
+ }
+
+ initFlagHoldData() {
+ if (this.initialized.flag) {
+ return;
+ }
+ let flagStatusBlufor = true;
+ let flagStatusOpfor = true;
+ this.tmpFlagCaptureData[0].series.push(ChartUtils.getSeriesEntry(this.startDateObj, flagStatusBlufor));
+ this.tmpFlagCaptureData[1].series.push(ChartUtils.getSeriesEntry(this.startDateObj, flagStatusOpfor));
+
+ this.logData.flag.forEach(flagEntry => {
+ if (flagEntry.flagFraction === 'BLUFOR') {
+ flagStatusBlufor = !flagEntry.capture;
+ } else {
+ flagStatusOpfor = !flagEntry.capture;
+ }
+ this.tmpFlagCaptureData[flagEntry.flagFraction === 'BLUFOR' ? 0 : 1].series.push(
+ ChartUtils.getSeriesEntry(new Date(flagEntry.time), flagEntry.flagFraction === 'BLUFOR' ? flagStatusBlufor : flagStatusOpfor)
+ )
+ });
+
+ this.addFinalTimeData(this.tmpFlagCaptureData);
+ this.initialized.flag = true;
+ }
+
+ protected isTwoMinutesAhead(entryDate: Date, tmpData: any): boolean {
+ return entryDate.getTime() >= tmpData[0].series[tmpData[0].series.length - 1].name.getTime() + (1.5 * 60000)
+ }
+
+ initializeTempCollections() {
+ this.tmpPointData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+ this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
+
+ [this.tmpKillData, this.tmpFrienlyFireData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => {
+ [0, 1].forEach(index => {
+ tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0));
+ })
+ });
+ this.tmpBudgetData[0].series.push(ChartUtils.getSeriesEntry(this.startDateObj, this.war.budgetBlufor));
+ this.tmpBudgetData[1].series.push(ChartUtils.getSeriesEntry(this.startDateObj, this.war.budgetOpfor));
+ }
+
+ addFinalTimeData(tmpCollection) {
+ const endDate = new Date(this.war.endDate);
+ if (tmpCollection === this.tmpBudgetData) {
+ this.tmpBudgetData[0].series.push(ChartUtils.getSeriesEntry(endDate, this.war.endBudgetBlufor));
+ this.tmpBudgetData[1].series.push(ChartUtils.getSeriesEntry(endDate, this.war.endBudgetOpfor));
+ } else {
+ for (let j in [0, 1]) {
+ if (tmpCollection[j].series[tmpCollection[j].series.length - 1].name < endDate) {
+ tmpCollection[j].series.push(ChartUtils.getSeriesEntry(endDate, tmpCollection[j].series[tmpCollection[j].series.length - 1].value));
+ }
+ }
+ }
+ }
+
+}
diff --git a/static/src/app/statistic/war-detail-header/war-detail-header.component.css b/static/src/app/statistic/war-detail-header/war-detail-header.component.css
index d727ec1..9273124 100644
--- a/static/src/app/statistic/war-detail-header/war-detail-header.component.css
+++ b/static/src/app/statistic/war-detail-header/war-detail-header.component.css
@@ -13,3 +13,16 @@
.war-header {
border-bottom: thin solid lightgrey;
}
+
+.nav-tabs > li.active > a {
+ background: #222222;
+}
+
+.nav-tabs > li > a {
+ background: #4b4b4b;
+}
+
+.nav-link {
+ cursor: pointer !important;
+ color: #FFF !important;
+}
diff --git a/static/src/app/statistic/war-detail-header/war-detail-header.component.html b/static/src/app/statistic/war-detail-header/war-detail-header.component.html
index a5c31c3..c0f77ec 100644
--- a/static/src/app/statistic/war-detail-header/war-detail-header.component.html
+++ b/static/src/app/statistic/war-detail-header/war-detail-header.component.html
@@ -58,9 +58,13 @@
-
-
+
+
+
+
+
+
+
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 3a1b31e..780c37d 100644
--- a/static/src/app/statistic/war-detail/war-detail.component.html
+++ b/static/src/app/statistic/war-detail/war-detail.component.html
@@ -1,5 +1,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-