mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-02-10 17:31:36 +01:00
Implemented query page graph selector
The range selector on the graph was quite quirky so functionality in LineGraph needed to be expanded a lot
This commit is contained in:
parent
428a0c5fde
commit
1c3cfbfe4b
@ -24,9 +24,11 @@ public class Cookie {
|
|||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
public Cookie(String rawRepresentation) {
|
public Cookie(String rawRepresentation) {
|
||||||
String[] split = StringUtils.split(rawRepresentation, "=", 2);
|
this(StringUtils.split(rawRepresentation, "=", 2));
|
||||||
name = split[0];
|
}
|
||||||
value = split[1];
|
|
||||||
|
private Cookie(String[] splitRawRepresentation) {
|
||||||
|
this(splitRawRepresentation[0], splitRawRepresentation[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cookie(String name, String value) {
|
public Cookie(String name, String value) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
import {Card, Col, Row} from "react-bootstrap-v5";
|
import {Card, Col, Row} from "react-bootstrap-v5";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useDataRequest} from "../../../hooks/dataFetchHook";
|
import {useDataRequest} from "../../../hooks/dataFetchHook";
|
||||||
@ -9,6 +9,24 @@ import DateInputField from "../../input/DateInputField";
|
|||||||
import TimeInputField from "../../input/TimeInputField";
|
import TimeInputField from "../../input/TimeInputField";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faSearch} from "@fortawesome/free-solid-svg-icons";
|
import {faSearch} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import PlayersOnlineGraph from "../../graphs/PlayersOnlineGraph";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
|
||||||
|
const parseTime = (dateString, timeString) => {
|
||||||
|
const d = dateString.match(
|
||||||
|
/^(0\d|\d{2})[\/|\-]?(0\d|\d{2})[\/|\-]?(\d{4,5})$/
|
||||||
|
);
|
||||||
|
const t = timeString.match(/^(0\d|\d{2}):?(0\d|\d{2})$/);
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
|
||||||
|
const parsedDay = Number(d[1]);
|
||||||
|
const parsedMonth = Number(d[2]) - 1; // 0=January, 11=December
|
||||||
|
const parsedYear = Number(d[3]);
|
||||||
|
let hour = Number(t[1]);
|
||||||
|
let minute = Number(t[2]);
|
||||||
|
const date = new Date(parsedYear, parsedMonth, parsedDay, hour, minute);
|
||||||
|
return date.getTime() - (date.getTimezoneOffset() * 60000);
|
||||||
|
};
|
||||||
|
|
||||||
const QueryOptionsCard = () => {
|
const QueryOptionsCard = () => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
@ -17,12 +35,48 @@ const QueryOptionsCard = () => {
|
|||||||
const [fromTime, setFromTime] = useState(undefined);
|
const [fromTime, setFromTime] = useState(undefined);
|
||||||
const [toDate, setToDate] = useState(undefined);
|
const [toDate, setToDate] = useState(undefined);
|
||||||
const [toTime, setToTime] = useState(undefined);
|
const [toTime, setToTime] = useState(undefined);
|
||||||
|
|
||||||
const [invalidFields, setInvalidFields] = useState([]);
|
const [invalidFields, setInvalidFields] = useState([]);
|
||||||
const setAsInvalid = id => setInvalidFields([...invalidFields, id]);
|
const setAsInvalid = id => setInvalidFields([...invalidFields, id]);
|
||||||
const setAsValid = id => setInvalidFields(invalidFields.filter(invalid => id !== invalid));
|
const setAsValid = id => setInvalidFields(invalidFields.filter(invalid => id !== invalid));
|
||||||
|
|
||||||
|
const [extremes, setExtremes] = useState(undefined);
|
||||||
|
const updateExtremes = useCallback(() => {
|
||||||
|
if (invalidFields.length || !options) return;
|
||||||
|
const newMin = parseTime(
|
||||||
|
fromDate ? fromDate : options.view.afterDate,
|
||||||
|
fromTime ? fromTime : options.view.afterTime
|
||||||
|
);
|
||||||
|
const newMax = parseTime(
|
||||||
|
toDate ? toDate : options.view.beforeDate,
|
||||||
|
toTime ? toTime : options.view.beforeTime
|
||||||
|
);
|
||||||
|
setExtremes({
|
||||||
|
min: newMin,
|
||||||
|
max: newMax
|
||||||
|
});
|
||||||
|
}, [fromDate, fromTime, toDate, toTime, invalidFields]);
|
||||||
|
useEffect(updateExtremes, [invalidFields]);
|
||||||
|
|
||||||
|
const onSetExtremes = useCallback((event) => {
|
||||||
|
if (event && (event.trigger === "navigator" || event.trigger === 'rangeSelectorButton')) {
|
||||||
|
const afterDate = Highcharts.dateFormat('%d/%m/%Y', event.min);
|
||||||
|
const afterTime = Highcharts.dateFormat('%H:%M', event.min);
|
||||||
|
const beforeDate = Highcharts.dateFormat('%d/%m/%Y', event.max);
|
||||||
|
const beforeTime = Highcharts.dateFormat('%H:%M', event.max);
|
||||||
|
setFromDate(afterDate);
|
||||||
|
setFromTime(afterTime);
|
||||||
|
setToDate(beforeDate);
|
||||||
|
setToTime(beforeTime);
|
||||||
|
}
|
||||||
|
}, [setFromTime, setFromDate, setToTime, setToDate]);
|
||||||
|
|
||||||
const {data: options, loadingError} = useDataRequest(fetchFilters, []);
|
const {data: options, loadingError} = useDataRequest(fetchFilters, []);
|
||||||
|
const [graphData, setGraphData] = useState(undefined);
|
||||||
|
useEffect(() => {
|
||||||
|
if (options) {
|
||||||
|
setGraphData({playersOnline: options.viewPoints, color: '#9E9E9E'})
|
||||||
|
}
|
||||||
|
}, [options, setGraphData]);
|
||||||
|
|
||||||
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
||||||
if (!options) return (<Card>
|
if (!options) return (<Card>
|
||||||
@ -31,7 +85,7 @@ const QueryOptionsCard = () => {
|
|||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>)
|
</Card>)
|
||||||
|
|
||||||
const view = options.view;
|
const view = options?.view;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@ -39,7 +93,7 @@ const QueryOptionsCard = () => {
|
|||||||
<label>{t('html.query.label.view')}</label>
|
<label>{t('html.query.label.view')}</label>
|
||||||
<Row className={"my-2 justify-content-start justify-content-md-center"}>
|
<Row className={"my-2 justify-content-start justify-content-md-center"}>
|
||||||
<Col className={"my-2"}>
|
<Col className={"my-2"}>
|
||||||
<label>{t('html.query.label.from')
|
<label>{t('html.query.label.from') // TODO Remove locale hack when the old frontend is disabled
|
||||||
.replace('</label>', '')
|
.replace('</label>', '')
|
||||||
.replace('>', '')}</label>
|
.replace('>', '')}</label>
|
||||||
</Col>
|
</Col>
|
||||||
@ -60,7 +114,7 @@ const QueryOptionsCard = () => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={1} className={"my-2 text-center"}>
|
<Col md={1} className={"my-2 text-center"}>
|
||||||
<label>{t('html.query.label.to')
|
<label>{t('html.query.label.to') // TODO Remove locale hack when the old frontend is disabled
|
||||||
.replace('</label>', '')
|
.replace('</label>', '')
|
||||||
.replace('>', '')}</label>
|
.replace('>', '')}</label>
|
||||||
</Col>
|
</Col>
|
||||||
@ -81,6 +135,16 @@ const QueryOptionsCard = () => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col md={12}>
|
||||||
|
<PlayersOnlineGraph
|
||||||
|
data={graphData}
|
||||||
|
selectedRange={3}
|
||||||
|
extremes={extremes}
|
||||||
|
onSetExtremes={onSetExtremes}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
<button id={"query-button"} className={"btn bg-plan m-2"} disabled={Boolean(invalidFields.length)}>
|
<button id={"query-button"} className={"btn bg-plan m-2"} disabled={Boolean(invalidFields.length)}>
|
||||||
<FontAwesomeIcon icon={faSearch}/> {t('html.query.performQuery')}
|
<FontAwesomeIcon icon={faSearch}/> {t('html.query.performQuery')}
|
||||||
|
@ -1,29 +1,35 @@
|
|||||||
import {useTheme} from "../../hooks/themeHook";
|
import {useTheme} from "../../hooks/themeHook";
|
||||||
import React, {useEffect} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {linegraphButtons} from "../../util/graphs";
|
import {linegraphButtons} from "../../util/graphs";
|
||||||
import Highcharts from "highcharts/highstock";
|
import Highcharts from "highcharts/highstock";
|
||||||
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
import Accessibility from "highcharts/modules/accessibility"
|
import Accessibility from "highcharts/modules/accessibility"
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
const LineGraph = ({id, series, legendEnabled, tall, yAxis}) => {
|
const LineGraph = ({id, series, legendEnabled, tall, yAxis, selectedRange, extremes, onSetExtremes}) => {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {graphTheming, nightModeEnabled} = useTheme();
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
const [graph, setGraph] = useState(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
NoDataDisplay(Highcharts);
|
NoDataDisplay(Highcharts);
|
||||||
Accessibility(Highcharts);
|
Accessibility(Highcharts);
|
||||||
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
Highcharts.setOptions(graphTheming);
|
Highcharts.setOptions(graphTheming);
|
||||||
Highcharts.stockChart(id, {
|
setGraph(Highcharts.stockChart(id, {
|
||||||
rangeSelector: {
|
rangeSelector: {
|
||||||
selected: 2,
|
selected: selectedRange !== undefined ? selectedRange : 2,
|
||||||
buttons: linegraphButtons
|
buttons: linegraphButtons
|
||||||
},
|
},
|
||||||
yAxis: yAxis || {
|
yAxis: yAxis || {
|
||||||
softMax: 2,
|
softMax: 2,
|
||||||
softMin: 0
|
softMin: 0
|
||||||
},
|
},
|
||||||
|
xAxis: {
|
||||||
|
events: {
|
||||||
|
afterSetExtremes: (event) => onSetExtremes(event)
|
||||||
|
}
|
||||||
|
},
|
||||||
title: {text: ''},
|
title: {text: ''},
|
||||||
plotOptions: {
|
plotOptions: {
|
||||||
areaspline: {
|
areaspline: {
|
||||||
@ -34,8 +40,13 @@ const LineGraph = ({id, series, legendEnabled, tall, yAxis}) => {
|
|||||||
enabled: legendEnabled
|
enabled: legendEnabled
|
||||||
},
|
},
|
||||||
series: series
|
series: series
|
||||||
})
|
}));
|
||||||
}, [series, graphTheming, id, t, nightModeEnabled, legendEnabled, yAxis])
|
}, [series, graphTheming, id, t, nightModeEnabled, legendEnabled, yAxis, onSetExtremes, setGraph])
|
||||||
|
useEffect(() => {
|
||||||
|
if (graph && extremes) {
|
||||||
|
graph.xAxis[0].setExtremes(extremes.min, extremes.max);
|
||||||
|
}
|
||||||
|
}, [graph, extremes]);
|
||||||
|
|
||||||
const style = tall ? {height: "450px"} : undefined;
|
const style = tall ? {height: "450px"} : undefined;
|
||||||
|
|
||||||
|
@ -4,11 +4,12 @@ import {tooltip} from "../../util/graphs";
|
|||||||
import LineGraph from "./LineGraph";
|
import LineGraph from "./LineGraph";
|
||||||
import {ChartLoader} from "../navigation/Loader";
|
import {ChartLoader} from "../navigation/Loader";
|
||||||
|
|
||||||
const PlayersOnlineGraph = ({data}) => {
|
const PlayersOnlineGraph = ({data, selectedRange, extremes, onSetExtremes}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [series, setSeries] = useState([]);
|
const [series, setSeries] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
const playersOnlineSeries = {
|
const playersOnlineSeries = {
|
||||||
name: t('html.label.playersOnline'),
|
name: t('html.label.playersOnline'),
|
||||||
type: 'areaspline',
|
type: 'areaspline',
|
||||||
@ -23,7 +24,11 @@ const PlayersOnlineGraph = ({data}) => {
|
|||||||
if (!data) return <ChartLoader/>;
|
if (!data) return <ChartLoader/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LineGraph id="players-online-graph" series={series}/>
|
<LineGraph id="players-online-graph"
|
||||||
|
series={series}
|
||||||
|
selectedRange={selectedRange}
|
||||||
|
extremes={extremes}
|
||||||
|
onSetExtremes={onSetExtremes}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,12 +48,14 @@ const DateInputField = ({id, setValue, value, placeholder, setAsInvalid, setAsVa
|
|||||||
const invalid = !isValidDate(value);
|
const invalid = !isValidDate(value);
|
||||||
setInvalid(invalid);
|
setInvalid(invalid);
|
||||||
|
|
||||||
|
// Value has to change before invalidity events
|
||||||
|
// because all-valid fields triggers graph refresh with the current value
|
||||||
|
setValue(value);
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
setAsInvalid(id);
|
setAsInvalid(id);
|
||||||
} else {
|
} else {
|
||||||
setAsValid(id);
|
setAsValid(id);
|
||||||
}
|
}
|
||||||
setValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -5,7 +5,7 @@ import {faClock} from "@fortawesome/free-regular-svg-icons";
|
|||||||
|
|
||||||
const isValidTime = value => {
|
const isValidTime = value => {
|
||||||
if (!value) return true;
|
if (!value) return true;
|
||||||
const regex = /^[0-2][0-9]:[0-5][0-9]$/;
|
const regex = /^[0-2]\d:[0-5]\d$/;
|
||||||
return regex.test(value);
|
return regex.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,12 +27,14 @@ const TimeInputField = ({id, setValue, value, placeholder, setAsInvalid, setAsVa
|
|||||||
const invalid = !isValidTime(value);
|
const invalid = !isValidTime(value);
|
||||||
setInvalid(invalid);
|
setInvalid(invalid);
|
||||||
|
|
||||||
|
// Value has to change before invalidity events
|
||||||
|
// because all-valid fields triggers graph refresh with the current value
|
||||||
|
setValue(value);
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
setAsInvalid(id);
|
setAsInvalid(id);
|
||||||
} else {
|
} else {
|
||||||
setAsValid(id);
|
setAsValid(id);
|
||||||
}
|
}
|
||||||
setValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user