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:
Aurora Lahtela 2022-10-16 10:47:18 +03:00
parent 428a0c5fde
commit 1c3cfbfe4b
6 changed files with 105 additions and 19 deletions

View File

@ -24,9 +24,11 @@ public class Cookie {
private final String value;
public Cookie(String rawRepresentation) {
String[] split = StringUtils.split(rawRepresentation, "=", 2);
name = split[0];
value = split[1];
this(StringUtils.split(rawRepresentation, "=", 2));
}
private Cookie(String[] splitRawRepresentation) {
this(splitRawRepresentation[0], splitRawRepresentation[1]);
}
public Cookie(String name, String value) {

View File

@ -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 {useTranslation} from "react-i18next";
import {useDataRequest} from "../../../hooks/dataFetchHook";
@ -9,6 +9,24 @@ import DateInputField from "../../input/DateInputField";
import TimeInputField from "../../input/TimeInputField";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
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 {t} = useTranslation();
@ -17,12 +35,48 @@ const QueryOptionsCard = () => {
const [fromTime, setFromTime] = useState(undefined);
const [toDate, setToDate] = useState(undefined);
const [toTime, setToTime] = useState(undefined);
const [invalidFields, setInvalidFields] = useState([]);
const setAsInvalid = id => setInvalidFields([...invalidFields, id]);
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 [graphData, setGraphData] = useState(undefined);
useEffect(() => {
if (options) {
setGraphData({playersOnline: options.viewPoints, color: '#9E9E9E'})
}
}, [options, setGraphData]);
if (loadingError) return <ErrorViewCard error={loadingError}/>
if (!options) return (<Card>
@ -31,7 +85,7 @@ const QueryOptionsCard = () => {
</Card.Body>
</Card>)
const view = options.view;
const view = options?.view;
return (
<Card>
@ -39,7 +93,7 @@ const QueryOptionsCard = () => {
<label>{t('html.query.label.view')}</label>
<Row className={"my-2 justify-content-start justify-content-md-center"}>
<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>
</Col>
@ -60,7 +114,7 @@ const QueryOptionsCard = () => {
/>
</Col>
<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>
</Col>
@ -81,6 +135,16 @@ const QueryOptionsCard = () => {
/>
</Col>
</Row>
<Row>
<Col md={12}>
<PlayersOnlineGraph
data={graphData}
selectedRange={3}
extremes={extremes}
onSetExtremes={onSetExtremes}
/>
</Col>
</Row>
</Card.Body>
<button id={"query-button"} className={"btn bg-plan m-2"} disabled={Boolean(invalidFields.length)}>
<FontAwesomeIcon icon={faSearch}/> {t('html.query.performQuery')}

View File

@ -1,29 +1,35 @@
import {useTheme} from "../../hooks/themeHook";
import React, {useEffect} from "react";
import React, {useEffect, useState} from "react";
import {linegraphButtons} from "../../util/graphs";
import Highcharts from "highcharts/highstock";
import NoDataDisplay from "highcharts/modules/no-data-to-display"
import Accessibility from "highcharts/modules/accessibility"
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 {graphTheming, nightModeEnabled} = useTheme();
const [graph, setGraph] = useState(undefined);
useEffect(() => {
NoDataDisplay(Highcharts);
Accessibility(Highcharts);
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
Highcharts.setOptions(graphTheming);
Highcharts.stockChart(id, {
setGraph(Highcharts.stockChart(id, {
rangeSelector: {
selected: 2,
selected: selectedRange !== undefined ? selectedRange : 2,
buttons: linegraphButtons
},
yAxis: yAxis || {
softMax: 2,
softMin: 0
},
xAxis: {
events: {
afterSetExtremes: (event) => onSetExtremes(event)
}
},
title: {text: ''},
plotOptions: {
areaspline: {
@ -34,8 +40,13 @@ const LineGraph = ({id, series, legendEnabled, tall, yAxis}) => {
enabled: legendEnabled
},
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;

View File

@ -4,11 +4,12 @@ import {tooltip} from "../../util/graphs";
import LineGraph from "./LineGraph";
import {ChartLoader} from "../navigation/Loader";
const PlayersOnlineGraph = ({data}) => {
const PlayersOnlineGraph = ({data, selectedRange, extremes, onSetExtremes}) => {
const {t} = useTranslation();
const [series, setSeries] = useState([]);
useEffect(() => {
if (!data) return;
const playersOnlineSeries = {
name: t('html.label.playersOnline'),
type: 'areaspline',
@ -23,7 +24,11 @@ const PlayersOnlineGraph = ({data}) => {
if (!data) return <ChartLoader/>;
return (
<LineGraph id="players-online-graph" series={series}/>
<LineGraph id="players-online-graph"
series={series}
selectedRange={selectedRange}
extremes={extremes}
onSetExtremes={onSetExtremes}/>
)
}

View File

@ -48,12 +48,14 @@ const DateInputField = ({id, setValue, value, placeholder, setAsInvalid, setAsVa
const invalid = !isValidDate(value);
setInvalid(invalid);
// Value has to change before invalidity events
// because all-valid fields triggers graph refresh with the current value
setValue(value);
if (invalid) {
setAsInvalid(id);
} else {
setAsValid(id);
}
setValue(value);
}
return (

View File

@ -5,7 +5,7 @@ import {faClock} from "@fortawesome/free-regular-svg-icons";
const isValidTime = value => {
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);
};
@ -27,12 +27,14 @@ const TimeInputField = ({id, setValue, value, placeholder, setAsInvalid, setAsVa
const invalid = !isValidTime(value);
setInvalid(invalid);
// Value has to change before invalidity events
// because all-valid fields triggers graph refresh with the current value
setValue(value);
if (invalid) {
setAsInvalid(id);
} else {
setAsValid(id);
}
setValue(value);
}
return (