better graphs - group modal fix - system list

This commit is contained in:
Luke Berndt
2023-04-24 20:37:07 -04:00
parent 7b56dc70a6
commit 8c9e74e008
16 changed files with 1005 additions and 13 deletions

View File

@@ -9,7 +9,7 @@ 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 ActivityChart from "./BetterActivityChart";
import { selectSystem } from "../features/systems/systemsSlice";
import {
Container,

View File

@@ -0,0 +1,524 @@
import React, { useMemo, useCallback } from 'react';
import { AreaClosed, Line, Bar } from '@visx/shape';
import appleStock, { AppleStock } from '@visx/mock-data/lib/mocks/appleStock';
import { curveMonotoneX } from '@visx/curve';
import { GridRows, GridColumns } from '@visx/grid';
import { scaleTime, scaleLinear } from '@visx/scale';
import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } 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 { LinearGradient } from '@visx/gradient';
import { max, extent, bisector } from 'd3-array';
import { timeFormat } from 'd3-time-format';
const data = [
0,
3,
1,
0,
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 accentColorDark = '#75daad';
const tooltipStyles = {
...defaultStyles,
background,
border: '1px solid white',
color: 'white',
};
// util
const formatDate = timeFormat("%b %d, %H:%m");
// accessors
const getDate = (d) => {
return d.x;
}
const getCallActivity = (d) => {
return d.y;
}
const ChartWithTooltip = () => {
const props = {data};
const {
tooltipData,
tooltipLeft,
tooltipTop,
tooltipOpen,
showTooltip,
hideTooltip,
} = useTooltip();
const width = 500;
const height = 250;
// If you don't want to use a Portal, simply replace `TooltipInPortal` below with
// `Tooltip` or `TooltipWithBounds` and remove `containerRef`
const { containerRef, TooltipInPortal } = useTooltipInPortal({
// use TooltipWithBounds
detectBounds: true,
// when tooltip containers are scrolled, this will correctly update the Tooltip position
scroll: true,
})
const bisectDate = bisector((d) => d.x).left;
const getMouseData = (event) => {
const coords = localPoint(event.target.ownerSVGElement, event);
const { x } = coords;
const x0 = dateScale.invert(x);
const index = bisectDate(calls, x0, 1);
const d0 = calls[index - 1];
const d1 = calls[index];
let d = d0;
if (d1 && getDate(d1)) {
d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0;
}
return { data: d, coords }
}
const handleMouseClick = (event) => {
const { data } = getMouseData(event);
console.log(data);
}
const handleMouseOver = (event, datum) => {
const { coords, data } = getMouseData(event);
showTooltip({
tooltipLeft: coords.x,
tooltipTop: callActivityScale(getCallActivity(data)),
tooltipData: data
});
};
// bounds
const margin = { top: 0, right: 0, bottom: 0, left: 0 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const calls = useMemo(
() => {
let callTotals = [];
const now = new Date();
var MS_PER_MINUTE = 60000;
for (let j = 0; j < props.data.length; j++) {
let spotsBack = props.data.length - j;
let time = new Date(now - spotsBack * 15 * MS_PER_MINUTE);
callTotals.push({ y: props.data[j], x: time });
}
return callTotals
}, [props.data]
);
const dateScale = useMemo(
() =>
scaleTime({
range: [margin.left, innerWidth + margin.left],
domain: extent(calls, getDate),
}),
[innerWidth, margin.left],
);
const callActivityScale = useMemo(
() => {
return scaleLinear({
range: [innerHeight + margin.top, margin.top],
domain: [0, (max(calls, getCallActivity) || 0)], // + innerHeight / 3],
nice: true,
})
},
[margin.top, innerHeight],
);
return (
// Set `ref={containerRef}` on the element corresponding to the coordinate system that
// `left/top` (passed to `TooltipInPortal`) are relative to.
<>
<svg ref={containerRef} width={width} height={height}>
<rect
x={0}
y={0}
width={width}
height={height}
fill="url(#area-background-gradient)"
rx={14}
/>
<LinearGradient id="area-background-gradient" from={background} to={background2} />
<LinearGradient id="area-gradient" from={accentColor} to={accentColor} toOpacity={0.1} />
<GridRows
left={margin.left}
scale={callActivityScale}
width={innerWidth}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0}
pointerEvents="none"
/>
<GridColumns
top={margin.top}
scale={dateScale}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.2}
pointerEvents="none"
/>
<AreaClosed
data={calls}
x={(d) => dateScale(getDate(d)) ?? 0}
y={(d) => callActivityScale(getCallActivity(d)) ?? 0}
yScale={callActivityScale}
strokeWidth={1}
stroke="url(#area-gradient)"
fill="url(#area-gradient)"
curve={curveMonotoneX}
/>
<Bar
x={margin.left}
y={margin.top}
width={innerWidth}
height={innerHeight}
fill="transparent"
rx={14}
onMouseMove={handleMouseOver}
onMouseUp={handleMouseClick}
onMouseOut={hideTooltip}
/>
{tooltipData && (
<g>
<Line
from={{ x: tooltipLeft, y: margin.top }}
to={{ x: tooltipLeft, y: innerHeight + margin.top }}
stroke={accentColorDark}
strokeWidth={2}
pointerEvents="none"
strokeDasharray="5,2"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop + 1}
r={4}
fill="black"
fillOpacity={0.1}
stroke="black"
strokeOpacity={0.1}
strokeWidth={2}
pointerEvents="none"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop}
r={4}
fill={accentColorDark}
stroke="white"
strokeWidth={2}
pointerEvents="none"
/>
</g>
)}
</svg>
{tooltipOpen && (
<div>
<TooltipWithBounds
key={Math.random()}
top={tooltipTop - 12}
left={tooltipLeft + 12}
style={tooltipStyles}
>
{`$${getCallActivity(tooltipData)}`}
</TooltipWithBounds>
<Tooltip
top={innerHeight + margin.top - 14}
left={tooltipLeft}
style={{
...defaultStyles,
minWidth: 72,
textAlign: 'center',
transform: 'translateX(-50%)',
}}
>
{formatDate(getDate(tooltipData))}
</Tooltip>
</div>
)}
</>
)
};
export default ChartWithTooltip;
/*
export default withTooltip(({
width = 250,
height = 250,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
showTooltip,
hideTooltip,
data,
tooltipTop = 0,
tooltipLeft = 0,
}) => {
//if (width < 10) return null;
// bounds
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const stock = buildData();
const tooltipData = stock;
// scales
const dateScale = useMemo(
() =>
scaleTime({
range: [margin.left, innerWidth + margin.left],
domain: extent(stock, getDate),
}),
[innerWidth, margin.left],
);
const stockValueScale = useMemo(
() =>
scaleLinear({
range: [innerHeight + margin.top, margin.top],
domain: [0, (max(stock, getStockValue) || 0) + innerHeight / 3],
nice: true,
}),
[margin.top, innerHeight],
);
// tooltip handler
const handleTooltip = useCallback(
(event) => {
const { x } = localPoint(event) || { x: 0 };
const x0 = dateScale.invert(x);
const index = bisectDate(stock, x0, 1);
const d0 = stock[index - 1];
const d1 = stock[index];
let d = d0;
if (d1 && getDate(d1)) {
d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0;
}
showTooltip({
tooltipData: d,
tooltipLeft: x,
tooltipTop: stockValueScale(getStockValue(d)),
});
},
[showTooltip, stockValueScale, dateScale],
);
return (
<div>
<svg width={width} height={height}>
<rect
x={0}
y={0}
width={width}
height={height}
fill="url(#area-background-gradient)"
rx={14}
/>
<LinearGradient id="area-background-gradient" from={background} to={background2} />
<LinearGradient id="area-gradient" from={accentColor} to={accentColor} toOpacity={0.1} />
<GridRows
left={margin.left}
scale={stockValueScale}
width={innerWidth}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0}
pointerEvents="none"
/>
<GridColumns
top={margin.top}
scale={dateScale}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.2}
pointerEvents="none"
/>
<AreaClosed
data={stock}
x={(d) => dateScale(getDate(d)) ?? 0}
y={(d) => stockValueScale(getStockValue(d)) ?? 0}
yScale={stockValueScale}
strokeWidth={1}
stroke="url(#area-gradient)"
fill="url(#area-gradient)"
curve={curveMonotoneX}
/>
<Bar
x={margin.left}
y={margin.top}
width={innerWidth}
height={innerHeight}
fill="transparent"
rx={14}
onTouchStart={handleTooltip}
onTouchMove={handleTooltip}
onMouseMove={handleTooltip}
onMouseLeave={() => hideTooltip()}
/>
<g>
<Line
from={{ x: tooltipLeft, y: margin.top }}
to={{ x: tooltipLeft, y: innerHeight + margin.top }}
stroke={accentColorDark}
strokeWidth={2}
pointerEvents="none"
strokeDasharray="5,2"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop + 1}
r={4}
fill="black"
fillOpacity={0.1}
stroke="black"
strokeOpacity={0.1}
strokeWidth={2}
pointerEvents="none"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop}
r={4}
fill={accentColorDark}
stroke="white"
strokeWidth={2}
pointerEvents="none"
/>
</g>
</svg>
<div>
<TooltipWithBounds
key={Math.random()}
top={tooltipTop - 12}
left={tooltipLeft + 12}
style={tooltipStyles}
>
{getStockValue(tooltipData)}
</TooltipWithBounds>
<Tooltip
top={innerHeight + margin.top - 14}
left={tooltipLeft}
style={{
...defaultStyles,
minWidth: 72,
textAlign: 'center',
transform: 'translateX(-50%)',
}}
>
{formatDate(getDate(tooltipData))}
</Tooltip>
</div>
</div>
);
}
);
*/

View File

@@ -0,0 +1,212 @@
import React, { useMemo, useCallback } from 'react';
import { AreaClosed, Line, Bar } from '@visx/shape';
import appleStock, { AppleStock } from '@visx/mock-data/lib/mocks/appleStock';
import { curveMonotoneX } from '@visx/curve';
import { GridRows, GridColumns } from '@visx/grid';
import { scaleTime, scaleLinear } from '@visx/scale';
import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } from '@visx/tooltip';
import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip';
import { localPoint } from '@visx/event';
import { LinearGradient } from '@visx/gradient';
import { max, extent, bisector } from 'd3-array';
import { timeFormat } from 'd3-time-format';
type TooltipData = AppleStock;
const stock = appleStock.slice(800);
export const background = '#3b6978';
export const background2 = '#204051';
export const accentColor = '#edffea';
export const accentColorDark = '#75daad';
const tooltipStyles = {
...defaultStyles,
background,
border: '1px solid white',
color: 'white',
};
// util
const formatDate = timeFormat("%b %d, '%y");
// accessors
const getDate = (d: AppleStock) => new Date(d.date);
const getStockValue = (d: AppleStock) => d.close;
const bisectDate = bisector<AppleStock, Date>((d) => new Date(d.date)).left;
export type AreaProps = {
width: number;
height: number;
margin?: { top: number; right: number; bottom: number; left: number };
};
export default withTooltip<AreaProps, TooltipData>(
({
width,
height,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
showTooltip,
hideTooltip,
tooltipData,
tooltipTop = 0,
tooltipLeft = 0,
}: AreaProps & WithTooltipProvidedProps<TooltipData>) => {
if (width < 10) return null;
// bounds
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
// scales
const dateScale = useMemo(
() =>
scaleTime({
range: [margin.left, innerWidth + margin.left],
domain: extent(stock, getDate) as [Date, Date],
}),
[innerWidth, margin.left],
);
const stockValueScale = useMemo(
() =>
scaleLinear({
range: [innerHeight + margin.top, margin.top],
domain: [0, (max(stock, getStockValue) || 0) + innerHeight / 3],
nice: true,
}),
[margin.top, innerHeight],
);
// tooltip handler
const handleTooltip = useCallback(
(event: React.TouchEvent<SVGRectElement> | React.MouseEvent<SVGRectElement>) => {
const { x } = localPoint(event) || { x: 0 };
const x0 = dateScale.invert(x);
const index = bisectDate(stock, x0, 1);
const d0 = stock[index - 1];
const d1 = stock[index];
let d = d0;
if (d1 && getDate(d1)) {
d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0;
}
showTooltip({
tooltipData: d,
tooltipLeft: x,
tooltipTop: stockValueScale(getStockValue(d)),
});
},
[showTooltip, stockValueScale, dateScale],
);
return (
<div>
<svg width={width} height={height}>
<rect
x={0}
y={0}
width={width}
height={height}
fill="url(#area-background-gradient)"
rx={14}
/>
<LinearGradient id="area-background-gradient" from={background} to={background2} />
<LinearGradient id="area-gradient" from={accentColor} to={accentColor} toOpacity={0.1} />
<GridRows
left={margin.left}
scale={stockValueScale}
width={innerWidth}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0}
pointerEvents="none"
/>
<GridColumns
top={margin.top}
scale={dateScale}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.2}
pointerEvents="none"
/>
<AreaClosed<AppleStock>
data={stock}
x={(d) => dateScale(getDate(d)) ?? 0}
y={(d) => stockValueScale(getStockValue(d)) ?? 0}
yScale={stockValueScale}
strokeWidth={1}
stroke="url(#area-gradient)"
fill="url(#area-gradient)"
curve={curveMonotoneX}
/>
<Bar
x={margin.left}
y={margin.top}
width={innerWidth}
height={innerHeight}
fill="transparent"
rx={14}
onTouchStart={handleTooltip}
onTouchMove={handleTooltip}
onMouseMove={handleTooltip}
onMouseLeave={() => hideTooltip()}
/>
{tooltipData && (
<g>
<Line
from={{ x: tooltipLeft, y: margin.top }}
to={{ x: tooltipLeft, y: innerHeight + margin.top }}
stroke={accentColorDark}
strokeWidth={2}
pointerEvents="none"
strokeDasharray="5,2"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop + 1}
r={4}
fill="black"
fillOpacity={0.1}
stroke="black"
strokeOpacity={0.1}
strokeWidth={2}
pointerEvents="none"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop}
r={4}
fill={accentColorDark}
stroke="white"
strokeWidth={2}
pointerEvents="none"
/>
</g>
)}
</svg>
{tooltipData && (
<div>
<TooltipWithBounds
key={Math.random()}
top={tooltipTop - 12}
left={tooltipLeft + 12}
style={tooltipStyles}
>
{`$${getStockValue(tooltipData)}`}
</TooltipWithBounds>
<Tooltip
top={innerHeight + margin.top - 14}
left={tooltipLeft}
style={{
...defaultStyles,
minWidth: 72,
textAlign: 'center',
transform: 'translateX(-50%)',
}}
>
{formatDate(getDate(tooltipData))}
</Tooltip>
</div>
)}
</div>
);
},
);

View File

@@ -19,6 +19,7 @@ import AboutComponent from "./About/AboutComponent"
// Event
import ListEvents from "./Event/ListEvents"
import ViewEvent from "./Event/ViewEvent"
import ActivityChart from './Call/BetterActivityChart';
const App = () => {
@@ -32,6 +33,7 @@ return (
<Route exact path="/events" element={<ListEvents/>} />
<Route exact path="/events/:id" element={<ViewEvent/>} />
<Route exact path="/about" element={<AboutComponent/>} />
<Route exact path="/test" element={<ActivityChart/>} />
</Routes>
)
}