activity charts!

This commit is contained in:
Luke Berndt
2023-04-13 22:11:36 -04:00
parent ba2a62fa2b
commit 7b56dc70a6
8 changed files with 440 additions and 19 deletions

View File

@@ -0,0 +1,69 @@
import React, { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from "react";
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom';
import FilterModal from "./components/FilterModal";
import GroupModal from "./components/GroupModal";
import CalendarModal from "./components/CalendarModal";
import CallPlayer from "./CallPlayer";
import { useSelector, useDispatch } from 'react-redux'
import { setFilter, setDateFilter } from "../features/callPlayer/callPlayerSlice";
import { useGetStatsQuery,useGetTalkgroupsQuery } from '../features/api/apiSlice'
import ActivityChart from "./ActivityChart";
import { selectSystem } from "../features/systems/systemsSlice";
import {
Container,
Label,
Rail,
Sticky,
Menu,
Icon,
Sidebar,
Header
} from "semantic-ui-react";
import "./CallPlayer.css";
import queryString from '../query-string';
import io from 'socket.io-client';
import { useCallLink } from "./components/CallLinks";
function Activity(props) {
const { shortName } = useParams();
const { data: statsData, isSuccess: isStatsSuccess } = useGetStatsQuery(shortName);
const { data: talkgroupData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName);
const talkgroupStats = useMemo(() => {
if (statsData ) {
const result =[]
for (const tgNum in statsData.talkgroupStats) {
let tg = tgNum;
if (talkgroupData && talkgroupData.talkgroups[tgNum] !== undefined) {
tg = talkgroupData.talkgroups[tgNum].description;
}
result.push(<ActivityChart key={tgNum} tg={tg} data={statsData.talkgroupStats[tgNum].callCountHistory} />);
}
return result;
} else {
return [];
}
}, [statsData, talkgroupData]);
return (
<Container style={{ marginTop: "65px"}}>
<Header as="h2">Talkgroup Activity</Header>
{
talkgroupStats.map( chart => {
return chart
})
}
</Container>
)
}
export default Activity;

View File

@@ -0,0 +1,102 @@
import React from "react";
import { Group } from '@visx/group';
import { curveStep} from '@visx/curve';
import { LinePath } from '@visx/shape';
import { scaleLinear, scaleTime } from '@visx/scale';
import { timeParse, timeFormat } from 'd3-time-format';
import { AxisBottom, AxisLeft } from '@visx/axis';
import { Loader } from "semantic-ui-react";
import {
Header
} from "semantic-ui-react";
function ActivityChart(props) {
if (!props.data) {
return (
<Loader size='large'>Loading</Loader>
)
}
var callTotals = [];
const now = new Date();
var maxDate = now;
var minDate = now;
var minValue = 0;
var maxValue = 0;
var MS_PER_MINUTE = 60000;
for (let j = 0; j < props.data.length; j++) {
let spotsBack = j;//statistic.callTotals.length - j;
let time = new Date(now - spotsBack * 15 * MS_PER_MINUTE);
if (time < minDate) minDate = time;
if (props.data[j] > maxValue)
maxValue = props.data[j];
callTotals.push({ y: props.data[j], x: time });
}
// Define the graph dimensions and margins
const width = 500; //props.width;
const height = 250;
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
const parseDate = timeParse("%Y%m%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));
const parseTime = timeParse('%Y-%m-%dT%H:%M:%S.%LZ');
// Then we'll create some bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
var xScale, yScale;
// We'll use some mock data from `@vx/mock-data` for this.
// scales
xScale = scaleTime({
range: [0, xMax],
domain: [minDate, maxDate],
tickFormat: () => (val) => formatDate(val)
});
yScale = scaleLinear({
range: [yMax, 0],
domain: [0, maxValue],
});
const handleClick = (event) => {
console.log('You clicked on:', event);
};
// accessors
const x = d => xScale(d.x);
const y = d => yScale(d.y);
return (
<div style={{float: 'left'}}>
<Header as='h3'>{props.tg}</Header>
<svg width={width} height={height}>
<Group left={margin.left} top={margin.top}>
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
<AxisLeft scale={yScale} />
<LinePath
data={callTotals}
x={x}
y={y}
onClick={handleClick}
strokeWidth={2}
stroke="blue"
curve={curveStep}
/>
</Group>
</svg>
</div>
);
}
export default ActivityChart;

View File

@@ -1,19 +1,17 @@
import React, { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from "react";
import React, { useEffect, useLayoutEffect, useState, useRef, useMemo } from "react";
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom';
import FilterModal from "./components/FilterModal";
import GroupModal from "./components/GroupModal";
import CalendarModal from "./components/CalendarModal";
import CallPlayer from "./CallPlayer";
import Activity from "./Activity";
import { useSelector, useDispatch } from 'react-redux'
import { setFilter, setDateFilter } from "../features/callPlayer/callPlayerSlice";
import { getCalls, addCall, getOlderCalls, getNewerCalls } from "../features/calls/callsSlice";
import { useGetGroupsQuery, useGetSystemsQuery, } from '../features/api/apiSlice'
import { selectSystem } from "../features/systems/systemsSlice";
import {
Container,
Label,
Rail,
Sticky,
Menu,
Icon,
Sidebar
@@ -21,7 +19,6 @@ import {
import "./CallPlayer.css";
import queryString from '../query-string';
import io from 'socket.io-client';
import { useCallLink } from "./components/CallLinks";
@@ -43,6 +40,7 @@ function Calls(props) {
const [filterVisible, setFilterVisible] = useState(false);
const [groupVisible, setGroupVisible] = useState(false);
const [calendarVisible, setCalendarVisible] = useState(false);
const [activityVisible, setActivityVisible] = useState(false);
const [isConnected, setIsConnected] = useState(socket.connected);
const [selectCallId, setSelectCallId] = useState(false);
const [live, setLive] = useState(true);
@@ -85,6 +83,7 @@ function Calls(props) {
const handleSidebarToggle = () => setSidebarOpened(!sidebarOpened);
const handleFilterToggle = () => setFilterVisible(!filterVisible);
const handleCalendarToggle = () => setCalendarVisible(!calendarVisible);
const handleActivityToggle = () => setActivityVisible(!activityVisible);
const getFilterDescription = () => {
let filter = { type: 'all', code: "", filterStarred: false };
@@ -400,6 +399,10 @@ function Calls(props) {
{system && system.name}
</Container>
<Menu.Menu position="right">
<Menu.Item name='atcivity-btn' onClick={handleActivityToggle} active={activityVisible}>
<Icon name="line graph" />
<span >Activity</span>
</Menu.Item>
<Menu.Item name='archive-btn' onClick={handleCalendarToggle} active={!live}>
<Icon name="calendar" />
<span className="desktop-only">
@@ -416,7 +419,10 @@ function Calls(props) {
</Menu.Item>
</Menu.Menu>
</Menu>
<CallPlayer callsData={callsData} selectCallId={selectCallId} handleNewer={handleNewer} handleOlder={handleOlder} />
{activityVisible
? <Activity />
: <CallPlayer callsData={callsData} selectCallId={selectCallId} handleNewer={handleNewer} handleOlder={handleOlder} />
}
</div>
);
}