q3stats/frontend/src/views/PlayerStatsView.js

330 lines
8.0 KiB
JavaScript
Raw Normal View History

2017-03-06 19:33:09 +00:00
/**
* Copyright (c) 2017 Tomek Wójcik <tomek@bthlabs.pl>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import ClassName from "classnames";
import React from "react";
import BaseView from "../lib/BaseView";
import ChartComponent from "../components/ChartComponent";
const WINS_CHART_TITLE_SESSION = "Wins and losses by session";
const WINS_CHART_TITLE_MAP = "Wins and losses by map";
const ACCURACY_CHART_TITLE_SESSION = "Average weapon accuracy by session";
const ACCURACY_CHART_TITLE_MAP = "Average weapon accuracy by map";
class ModeLinkComponent extends React.Component {
constructor (props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick (event) {
event.stopPropagation();
event.preventDefault();
this.props.onSwitchMode(this.props.code);
}
render () {
let className = ClassName({
active: (this.props.mode == this.props.code)
});
return (
<li className={className}>
<a onClick={this.onClick} href="#">{this.props.name}</a>
</li>
);
}
}
ModeLinkComponent.propTypes = {
mode: React.PropTypes.string.isRequired,
name: React.PropTypes.string.isRequired,
code: React.PropTypes.string.isRequired,
onSwitchMode: React.PropTypes.func.isRequired
};
class BaseStatsChartComponent extends BaseView {
constructor (props) {
super(props);
this.chartConfig = {};
this.state = {
sessionCategories: [],
sessionSeries: [],
mapCategories: [],
mapSeries: []
};
}
componentDidMount () {
this.load();
}
componentWillReceiveProps (nextProps) {
if (nextProps.player != this.props.player) {
this.setState({
sessionCategories: [],
sessionSeries: [],
mapCategories: [],
mapSeries: []
});
}
}
componentDidUpdate (prevProps, prevState) {
if (prevProps.player != this.props.player || prevProps.mode != this.props.mode) {
this.load();
}
}
url () {
throw new Error("Not Implemented");
}
parse () {
throw new Error("Not Implemented");
}
load () {
let hasData = true;
if (this.props.mode == "session") {
hasData = hasData && this.state.sessionCategories.length > 0;
hasData = hasData && this.state.sessionSeries.length > 0;
} else {
hasData = hasData && this.state.mapCategories.length > 0;
hasData = hasData && this.state.mapSeries.length > 0;
}
if (!hasData) {
this.fetch(this.url()).then((data) => {
let newState = {};
let parsedData = this.parse(data);
if (this.props.mode == "session") {
newState.sessionCategories = parsedData.categories;
newState.sessionSeries = parsedData.series;
} else {
newState.mapCategories = parsedData.categories;
newState.mapSeries = parsedData.series;
}
this.setState(newState);
});
}
}
title () {
throw new Error("Not Implemented");
}
render () {
let categories = this.state.sessionCategories;
let series = this.state.sessionSeries;
if (this.props.mode == "map") {
categories = this.state.mapCategories;
series = this.state.mapSeries;
}
return (
<ChartComponent
config={this.chartConfig}
categories={categories}
series={series}
title={this.title()} />
);
}
}
class WinsChartComponent extends BaseStatsChartComponent {
constructor (props) {
super(props);
this.chartConfig = {
"chart": {
"type": "column"
},
"credits": {
"enabled": false
},
"yAxis": {
"title": {
"text": "Score"
}
},
"plotOptions": {
"column": {
"pointPadding": 0.2,
"borderWidth": 0,
"stacking": "normal",
"dataLabels": {
"enabled": true,
"color": "white",
"style": {
"textShadow": "0 0 3px black"
}
}
}
},
"tooltip": {
"formatter": function () {
return (
"<b>" + this.x + "</b><br/>" + this.series.name + ": " +
this.y + "<br/>" + "Total: " + this.point.stackTotal
);
}
}
};
}
url () {
return (
"/api/v1/charts/player/" + this.props.player + "/wins/" + this.props.mode
);
}
parse (data) {
let result = {
categories: [],
series: [
{"name": "Wins", "data": data.wins},
{"name": "Losses", "data": data.losses}
]
};
if (this.props.mode == "session") {
result.categories = data.dates;
} else {
result.categories = data.maps;
}
return result;
}
title () {
if (this.props.mode == "map") {
return WINS_CHART_TITLE_MAP;
}
return WINS_CHART_TITLE_SESSION;
}
render () {
return super.render();
}
}
class AccuracyChartComponent extends BaseStatsChartComponent {
constructor (props) {
super(props);
this.chartConfig = {
"credits": {
"enabled": false
},
"tooltip": {
"valueDecimals": 2,
"valueSuffix": "%"
},
"yAxis": {
"title": {
"text": "Avg accuracy [%]"
}
}
};
}
url () {
return (
"/api/v1/charts/player/" + this.props.player + "/accuracy/" +
this.props.mode
);
}
parse (data) {
let result = {
categories: [],
series: data.series
};
if (this.props.mode == "session") {
result.categories = data.dates;
} else {
result.categories = data.maps;
}
return result;
}
title () {
if (this.props.mode == "map") {
return ACCURACY_CHART_TITLE_MAP;
}
return ACCURACY_CHART_TITLE_SESSION;
}
render () {
return super.render();
}
}
WinsChartComponent.propTypes = {
player: React.PropTypes.string.isRequired,
mode: React.PropTypes.string.isRequired
};
class PlayerStatsView extends React.Component {
constructor (props) {
super(props);
this.onSwitchMode = this.onSwitchMode.bind(this);
this.state = {
mode: "session"
};
}
componentWillReceiveProps (nextProps) {
this.setState({mode: "session"});
}
onSwitchMode (mode) {
this.setState({mode: mode});
}
render () {
return (
<div>
<h2>{this.props.params.player} stats</h2>
<ul className="nav nav-tabs">
<ModeLinkComponent
mode={this.state.mode}
name="By session"
code="session"
onSwitchMode={this.onSwitchMode} />
<ModeLinkComponent
mode={this.state.mode}
name="By map"
code="map"
onSwitchMode={this.onSwitchMode} />
</ul>
<WinsChartComponent
player={this.props.params.player}
mode={this.state.mode}
router={this.props.router} />
<AccuracyChartComponent
player={this.props.params.player}
mode={this.state.mode}
router={this.props.router} />
</div>
);
}
}
export default PlayerStatsView;