mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-13 17:27:37 -05:00
better charts
This commit is contained in:
18541
frontend/package-lock.json
generated
Normal file
18541
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
3262
frontend/yarn.lock
3262
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user