better charts

This commit is contained in:
Luke Berndt
2023-04-29 08:44:14 -04:00
parent 8c9e74e008
commit 6fbf2b6e2b
5 changed files with 20291 additions and 1703 deletions

18541
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -41,7 +41,7 @@ function Activity(props) {
if (talkgroupData && talkgroupData.talkgroups[tgNum] !== undefined) { if (talkgroupData && talkgroupData.talkgroups[tgNum] !== undefined) {
tg = talkgroupData.talkgroups[tgNum].description; tg = talkgroupData.talkgroups[tgNum].description;
} }
result.push(<ActivityChart key={tgNum} tg={tg} data={statsData.talkgroupStats[tgNum].callCountHistory} />); result.push(<ActivityChart key={tgNum} tg={tg} tgNum={tgNum} data={statsData.talkgroupStats[tgNum].callCountHistory} navigate={props.navigate}/>);
} }
return result; return result;
} else { } else {

View File

@@ -1,121 +1,24 @@
import React, { useMemo, useCallback } from 'react'; import React, { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useSearchParams } from "react-router-dom";
import { AreaClosed, Line, Bar } from '@visx/shape'; import { AreaClosed, Line, Bar } from '@visx/shape';
import appleStock, { AppleStock } from '@visx/mock-data/lib/mocks/appleStock';
import { curveMonotoneX } from '@visx/curve'; import { curveMonotoneX } from '@visx/curve';
import { GridRows, GridColumns } from '@visx/grid'; import { GridRows, GridColumns } from '@visx/grid';
import { scaleTime, scaleLinear } from '@visx/scale'; import { scaleTime, scaleLinear } from '@visx/scale';
import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } from '@visx/tooltip'; import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } from '@visx/tooltip';
import { useTooltip, useTooltipInPortal } from '@visx/tooltip'; import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
import { Loader } from "semantic-ui-react";
import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip';
import { localPoint } from '@visx/event'; import { localPoint } from '@visx/event';
import { LinearGradient } from '@visx/gradient'; import { LinearGradient } from '@visx/gradient';
import { max, extent, bisector } from 'd3-array'; import { max, extent, bisector } from 'd3-array';
import { timeFormat } from 'd3-time-format'; import { timeFormat } from 'd3-time-format';
import { AxisLeft } from '@visx/axis';
const data = [ import {
0, Header
3, } from "semantic-ui-react";
1, export const background = '#9f0000'; //'#3b6978';
0, export const background2 = '#9f0000'; //'#204051';
0,
0,
0,
6,
5,
2,
7,
0,
0,
2,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
];
export const background = '#3b6978';
export const background2 = '#204051';
export const accentColor = '#edffea'; export const accentColor = '#edffea';
export const accentColorDark = '#75daad'; export const accentColorDark = '#ff7543'; //#75daad';
const tooltipStyles = { const tooltipStyles = {
...defaultStyles, ...defaultStyles,
background, background,
@@ -140,8 +43,8 @@ const getCallActivity = (d) => {
const ChartWithTooltip = () => { const ChartWithTooltip = (props) => {
const props = {data}; //const props = {data};
const { const {
tooltipData, tooltipData,
tooltipLeft, tooltipLeft,
@@ -152,6 +55,8 @@ const ChartWithTooltip = () => {
} = useTooltip(); } = useTooltip();
const width = 500; const width = 500;
const height = 250; const height = 250;
const navigate = useNavigate();
let [searchParams, setSearchParams] = useSearchParams();
// If you don't want to use a Portal, simply replace `TooltipInPortal` below with // If you don't want to use a Portal, simply replace `TooltipInPortal` below with
// `Tooltip` or `TooltipWithBounds` and remove `containerRef` // `Tooltip` or `TooltipWithBounds` and remove `containerRef`
const { containerRef, TooltipInPortal } = useTooltipInPortal({ const { containerRef, TooltipInPortal } = useTooltipInPortal({
@@ -179,7 +84,15 @@ const ChartWithTooltip = () => {
} }
const handleMouseClick = (event) => { const handleMouseClick = (event) => {
const { data } = getMouseData(event); const { data } = getMouseData(event);
console.log(data); const timestamp = data.x.getTime();
const params ={
"filter-type": "talkgroups",
"filter-code": props.tgNum,
"time": timestamp
}
setSearchParams(params);
props.navigate(props.tgNum, timestamp);
} }
const handleMouseOver = (event, datum) => { const handleMouseOver = (event, datum) => {
const { coords, data } = getMouseData(event); const { coords, data } = getMouseData(event);
@@ -190,7 +103,7 @@ const ChartWithTooltip = () => {
}); });
}; };
// bounds // bounds
const margin = { top: 0, right: 0, bottom: 0, left: 0 }; const margin = { top: 10, right: 0, bottom: 0, left: 25 };
const innerWidth = width - margin.left - margin.right; const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom; const innerHeight = height - margin.top - margin.bottom;
@@ -201,10 +114,11 @@ const ChartWithTooltip = () => {
var MS_PER_MINUTE = 60000; var MS_PER_MINUTE = 60000;
for (let j = 0; j < props.data.length; j++) { for (let j = 0; j < props.data.length; j++) {
let spotsBack = props.data.length - j; let spotsBack = j;
let time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); let time = new Date(now - spotsBack * 15 * MS_PER_MINUTE);
callTotals.push({ y: props.data[j], x: time }); callTotals.push({ y: props.data[j], x: time });
} }
callTotals.sort((a,b) => a.x-b.x);
return callTotals return callTotals
@@ -218,7 +132,7 @@ const ChartWithTooltip = () => {
range: [margin.left, innerWidth + margin.left], range: [margin.left, innerWidth + margin.left],
domain: extent(calls, getDate), domain: extent(calls, getDate),
}), }),
[innerWidth, margin.left], [innerWidth, margin.left, calls],
); );
const callActivityScale = useMemo( const callActivityScale = useMemo(
@@ -229,22 +143,26 @@ const ChartWithTooltip = () => {
nice: true, nice: true,
}) })
}, },
[margin.top, innerHeight], [margin.top, innerHeight, calls],
); );
return ( return (
// Set `ref={containerRef}` on the element corresponding to the coordinate system that // Set `ref={containerRef}` on the element corresponding to the coordinate system that
// `left/top` (passed to `TooltipInPortal`) are relative to. // `left/top` (passed to `TooltipInPortal`) are relative to.
<> <div style={{ float: "left", margin: "15px"}}>
<svg ref={containerRef} width={width} height={height}> <Header as='h3'>{props.tg}</Header>
<div style={{position: 'relative'}}>
<svg ref={containerRef} width={width} height={height} >
<rect <rect
x={0} x={margin.left}
y={0} y={0}
width={width} width={width-margin.left-margin.right}
height={height} height={height}
fill="url(#area-background-gradient)" fill="url(#area-background-gradient)"
rx={14} rx={14}
/> />
<LinearGradient id="area-background-gradient" from={background} to={background2} /> <LinearGradient id="area-background-gradient" from={background} to={background2} />
<LinearGradient id="area-gradient" from={accentColor} to={accentColor} toOpacity={0.1} /> <LinearGradient id="area-gradient" from={accentColor} to={accentColor} toOpacity={0.1} />
<GridRows <GridRows
@@ -287,6 +205,7 @@ const ChartWithTooltip = () => {
onMouseUp={handleMouseClick} onMouseUp={handleMouseClick}
onMouseOut={hideTooltip} onMouseOut={hideTooltip}
/> />
<AxisLeft scale={callActivityScale} left={margin.left} numTicks={2} hideAxisLine={true} hideZero={true} />
{tooltipData && ( {tooltipData && (
<g> <g>
<Line <Line
@@ -325,13 +244,13 @@ const ChartWithTooltip = () => {
<TooltipWithBounds <TooltipWithBounds
key={Math.random()} key={Math.random()}
top={tooltipTop - 12} top={tooltipTop - 12}
left={tooltipLeft + 12} left={tooltipLeft}
style={tooltipStyles} style={tooltipStyles}
> >
{`$${getCallActivity(tooltipData)}`} {`${getCallActivity(tooltipData)}`}
</TooltipWithBounds> </TooltipWithBounds>
<Tooltip <Tooltip
top={innerHeight + margin.top - 14} top={-28}
left={tooltipLeft} left={tooltipLeft}
style={{ style={{
...defaultStyles, ...defaultStyles,
@@ -344,7 +263,8 @@ const ChartWithTooltip = () => {
</Tooltip> </Tooltip>
</div> </div>
)} )}
</> </div>
</div>
) )
}; };
@@ -516,7 +436,7 @@ export default withTooltip(({
{formatDate(getDate(tooltipData))} {formatDate(getDate(tooltipData))}
</Tooltip> </Tooltip>
</div> </div>
</div>
</div> </div>
); );
} }

View File

@@ -51,14 +51,14 @@ function Calls(props) {
const pageYOffset = useRef(); // Store the current Scroll Offset in a way that guarantees the latest value is available when Call Data is updated const pageYOffset = useRef(); // Store the current Scroll Offset in a way that guarantees the latest value is available when Call Data is updated
const shouldPlayAddCallRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback const shouldPlayAddCallRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback
shouldPlayAddCallRef.current = (!isPlaying && autoPlay) ? true : false; shouldPlayAddCallRef.current = (!isPlaying && autoPlay) ? true : false;
const uri = queryString.parse(useLocation().search);
const filterType = useSelector((state) => state.callPlayer.filterType); const filterType = useSelector((state) => state.callPlayer.filterType);
const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId); const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId);
const filterTalkgroups = useSelector((state) => state.callPlayer.filterTalkgroups); const filterTalkgroups = useSelector((state) => state.callPlayer.filterTalkgroups);
const filterStarred = useSelector((state) => state.callPlayer.filterStarred); const filterStarred = useSelector((state) => state.callPlayer.filterStarred);
const filterDate = useSelector((state) => state.callPlayer.filterDate); const filterDate = useSelector((state) => state.callPlayer.filterDate);
//const live = useSelector((state) => state.callPlayer.live); //const live = useSelector((state) => state.callPlayer.live);
const uri = queryString.parse(useLocation().search);
const pathname = useLocation().pathname; const pathname = useLocation().pathname;
let currentCallId = false; let currentCallId = false;
@@ -84,6 +84,22 @@ function Calls(props) {
const handleFilterToggle = () => setFilterVisible(!filterVisible); const handleFilterToggle = () => setFilterVisible(!filterVisible);
const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); const handleCalendarToggle = () => setCalendarVisible(!calendarVisible);
const handleActivityToggle = () => setActivityVisible(!activityVisible); const handleActivityToggle = () => setActivityVisible(!activityVisible);
const handleActivityNavigate = (tgNum, timestamp) =>{
let filter = {
filterDate: timestamp,
filterType: 2, // this is "all"
filterTalkgroups: [tgNum],
filterGroupId: false,
filterStarred: false,
shortName: shortName
};
dispatch(setFilter(filter));
setLive(false);
stopSocket();
setActivityVisible(false)
}
const getFilterDescription = () => { const getFilterDescription = () => {
let filter = { type: 'all', code: "", filterStarred: false }; let filter = { type: 'all', code: "", filterStarred: false };
@@ -235,6 +251,7 @@ function Calls(props) {
const setStateFromUri = async () => { const setStateFromUri = async () => {
let filter = { let filter = {
filterDate: false, filterDate: false,
filterType: 0, // this is "all" filterType: 0, // this is "all"
@@ -420,7 +437,7 @@ function Calls(props) {
</Menu.Menu> </Menu.Menu>
</Menu> </Menu>
{activityVisible {activityVisible
? <Activity /> ? <Activity navigate={handleActivityNavigate} />
: <CallPlayer callsData={callsData} selectCallId={selectCallId} handleNewer={handleNewer} handleOlder={handleOlder} /> : <CallPlayer callsData={callsData} selectCallId={selectCallId} handleNewer={handleNewer} handleOlder={handleOlder} />
} }
</div> </div>

File diff suppressed because it is too large Load Diff