mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-12 08:47:40 -05:00
better graphs - group modal fix - system list
This commit is contained in:
@@ -12,6 +12,66 @@ exports.isLoggedIn = function (req, res, next) {
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.listAllSystems = async function (req, res, next) {
|
||||
if (!req.user.admin) {
|
||||
res.status(401);
|
||||
res.json({ success: false, message: "Not Authorized" });
|
||||
return;
|
||||
}
|
||||
|
||||
const systems = await System.find().exec();
|
||||
if (systems == null) {
|
||||
res.status(400);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
|
||||
let returnSys = systems.map(obj => {
|
||||
var rObj = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup,
|
||||
lastActive,
|
||||
active
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup,
|
||||
lastActive,
|
||||
active
|
||||
}))(obj);
|
||||
|
||||
|
||||
rObj.id = obj._id;
|
||||
return rObj;
|
||||
});
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
systems: returnSys
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
exports.listSystems = async function (req, res, next) {
|
||||
const userId = new mongoose.Types.ObjectId(req.user._id);
|
||||
const systems = await System.find({ userId: userId }).exec();
|
||||
|
||||
@@ -79,7 +79,7 @@ function isOwner(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
function isAdmin(req, res, next) {
|
||||
if (req.params.shortName && req.user) {
|
||||
var short_name = req.params.shortName.toLowerCase();
|
||||
@@ -99,7 +99,7 @@ function isAdmin(req, res, next) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
function isLoggedIn(req, res, next) {
|
||||
if (req.isAuthenticated()) return next();
|
||||
@@ -109,6 +109,16 @@ function isLoggedIn(req, res, next) {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function isAdmin(req, res, next) {
|
||||
if (req.isAuthenticated() && req.user.admin) return next();
|
||||
res.status(401).send({
|
||||
success: false,
|
||||
message: "Not Authenticated."
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function uploadFile(req, res, next) {
|
||||
const upload = multer({dest: 'uploads/'}).single('file');
|
||||
|
||||
@@ -142,6 +152,7 @@ app.post("/groups/:shortName/:groupId?", isLoggedIn, groups.upsertGroup);
|
||||
app.get("/groups/:shortName/:groupId?", isLoggedIn, groups.getGroups);
|
||||
app.delete("/groups/:shortName/:groupId", isLoggedIn, groups.deleteGroup);
|
||||
app.get("/systems", isLoggedIn, systems.listSystems)
|
||||
app.get("/admin/systems", isAdmin, systems.listAllSystems)
|
||||
app.delete("/systems/:shortName", isLoggedIn, systems.deleteSystem)
|
||||
app.post("/systems/:shortName", [isLoggedIn, systems.ownSystem, systems.validateSystem, systems.updateSystem])
|
||||
app.post("/systems", [isLoggedIn, systems.uniqueShortName, systems.validateSystem, systems.createSystem])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var mongoose = require('mongoose');
|
||||
var bcrypt = require('bcrypt');
|
||||
var User = require('./user');
|
||||
|
||||
var systemSchema = mongoose.Schema({
|
||||
name: String,
|
||||
@@ -12,7 +13,9 @@ var systemSchema = mongoose.Schema({
|
||||
description: String,
|
||||
showScreenName: Boolean,
|
||||
ignoreUnknownTalkgroup : Boolean,
|
||||
userId: mongoose.Schema.Types.ObjectId,
|
||||
active: {type: Boolean, default: false},
|
||||
lastActive: Date,
|
||||
userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
|
||||
key: String
|
||||
});
|
||||
|
||||
|
||||
58
admin/src/AllSystems/AllSystems.js
Normal file
58
admin/src/AllSystems/AllSystems.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Container,
|
||||
Divider,
|
||||
Header,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
import { useGetAllSystemsQuery, } from '../features/api/apiSlice'
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
const ListSystems = () => {
|
||||
const { data, isSuccess } = useGetAllSystemsQuery();
|
||||
let systems = false;
|
||||
if (isSuccess) {
|
||||
systems = data.systems;
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<Header as="h1">Radio Systems</Header>
|
||||
|
||||
|
||||
{systems &&
|
||||
systems.map((system) =>
|
||||
<Table celled selectable>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>ShortName</Table.HeaderCell>
|
||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Last Active</Table.HeaderCell>
|
||||
<Table.HeaderCell>Owner</Table.HeaderCell>
|
||||
<Table.HeaderCell>Email</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>{system.shortName}</Table.Cell>
|
||||
<Table.Cell>{system.name}</Table.Cell>
|
||||
<Table.Cell>{system.lastActive}</Table.Cell>
|
||||
<Table.Cell>{system.userId.firstName} {system.userId.lastName}</Table.Cell>
|
||||
<Table.Cell>{system.userId.email}</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
)}
|
||||
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default ListSystems;
|
||||
@@ -1,7 +1,7 @@
|
||||
import Navigation from "./Navigation"
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
|
||||
import Restricted from "./Restricted"
|
||||
import {AdminRestricted, Restricted} from "./Restricted"
|
||||
|
||||
import About from "../Components/About"
|
||||
import System from "../System/System"
|
||||
@@ -20,6 +20,7 @@ const App = (params) => {
|
||||
<Route path="/update-system/:shortName" element={ <Restricted><UpdateSystem/></Restricted>} />
|
||||
<Route path="/create-system" element={ <Restricted><CreateSystem/></Restricted>} />
|
||||
<Route path="/list-systems" element={ <Restricted><ListSystems/></Restricted>} />
|
||||
<Route path="/all-systems" element={ <AdminRestricted><ListSystems/></AdminRestricted>} />
|
||||
<Route path="/" element={ <Restricted><ListSystems/></Restricted>} />
|
||||
<Route render={() => (<div>Miss</div>)} />
|
||||
</Routes>
|
||||
|
||||
@@ -9,7 +9,7 @@ const navStyle = {
|
||||
|
||||
const Navigation = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const { email } = useSelector((state) => state.user);
|
||||
const { email, admin } = useSelector((state) => state.user);
|
||||
|
||||
const logout = event => {
|
||||
event.preventDefault();
|
||||
@@ -24,6 +24,11 @@ const Navigation = (props) => {
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
{admin &&
|
||||
<Link to="/all-systems">
|
||||
<Menu.Item name="all-systems">All Systems</Menu.Item>
|
||||
</Link>
|
||||
}
|
||||
<Link to="/list-systems">
|
||||
<Menu.Item name="systems"> Systems</Menu.Item>
|
||||
</Link>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { authenticateUser } from "../features/user/userSlice";
|
||||
* Higher-order component (HOC) to wrap restricted pages
|
||||
*/
|
||||
|
||||
const Restricted = ({children }) => {
|
||||
export const Restricted = ({children }) => {
|
||||
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
@@ -26,4 +26,23 @@ const Restricted = ({children }) => {
|
||||
|
||||
};
|
||||
|
||||
export const AdminRestricted = ({children }) => {
|
||||
const { authenticated, hasAuthenticated, admin } = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(authenticateUser({}));
|
||||
},[]);
|
||||
|
||||
if (hasAuthenticated) {
|
||||
if (authenticated && admin) {
|
||||
return children;;
|
||||
}
|
||||
else {
|
||||
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
|
||||
}
|
||||
}
|
||||
return <div/>
|
||||
|
||||
};
|
||||
|
||||
export default Restricted
|
||||
@@ -77,7 +77,7 @@ const GroupModal = (props) => {
|
||||
}
|
||||
},[props.editGroupId, props.talkgroups]);
|
||||
|
||||
const removeTalkgroup = (index) => {
|
||||
const removeTalkgroup = (event, index) => {
|
||||
const item = {
|
||||
num: group.items[index].num,
|
||||
description: group.items[index].description
|
||||
|
||||
@@ -136,6 +136,10 @@ export const apiSlice = createApi({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: () => ({ url: `/systems`, credentials: "include" })
|
||||
}),
|
||||
getAllSystems: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: () => ({ url: `/admin/systems`, credentials: "include" })
|
||||
}),
|
||||
createSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems`,
|
||||
@@ -198,4 +202,4 @@ export const apiSlice = createApi({
|
||||
})
|
||||
|
||||
// Export the auto-generated hook for the `getPosts` query endpoint
|
||||
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice
|
||||
export const { useGetGroupsQuery, useGetSystemsQuery, useGetAllSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice
|
||||
|
||||
@@ -17,7 +17,6 @@ var systemSchema = mongoose.Schema({
|
||||
ignoreUnknownTalkgroup : Boolean,
|
||||
active: {type: Boolean, default: false},
|
||||
lastActive: Date,
|
||||
planType: {type: Number, default: 0},
|
||||
userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
|
||||
key: String
|
||||
});
|
||||
|
||||
@@ -11,10 +11,17 @@
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@visx/axis": "^3.1.0",
|
||||
"@visx/curve": "^3.0.0",
|
||||
"@visx/event": "^3.0.1",
|
||||
"@visx/gradient": "^3.0.0",
|
||||
"@visx/grid": "^3.0.1",
|
||||
"@visx/group": "^3.0.0",
|
||||
"@visx/mock-data": "^3.0.0",
|
||||
"@visx/scale": "^3.0.0",
|
||||
"@visx/shape": "^3.0.0",
|
||||
"@visx/tooltip": "^3.1.2",
|
||||
"axios": "1.2.1",
|
||||
"d3-array": "^3.2.3",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"date-fns": "2.29.3",
|
||||
"ejs": "3.1.8",
|
||||
"history": "^5.3.0",
|
||||
|
||||
@@ -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,
|
||||
|
||||
524
frontend/src/Call/BetterActivityChart.js
Normal file
524
frontend/src/Call/BetterActivityChart.js
Normal 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>
|
||||
);
|
||||
}
|
||||
);
|
||||
*/
|
||||
212
frontend/src/Call/Example.js
Normal file
212
frontend/src/Call/Example.js
Normal 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>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2691,6 +2691,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c"
|
||||
integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==
|
||||
|
||||
"@types/d3-random@^2.2.0":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-2.2.1.tgz#551edbb71cb317dea2cf9c76ebe059d311eefacb"
|
||||
integrity sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA==
|
||||
|
||||
"@types/d3-scale@^4.0.2":
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.3.tgz#7a5780e934e52b6f63ad9c24b105e33dd58102b5"
|
||||
@@ -2874,6 +2879,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-dom@*":
|
||||
version "18.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
|
||||
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@^18.0.0":
|
||||
version "18.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352"
|
||||
@@ -3099,6 +3111,15 @@
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
"@visx/bounds@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/bounds/-/bounds-3.0.0.tgz#cf357cbff90a1fe5f95eb9d9288dd8794b744a7f"
|
||||
integrity sha512-YQaSSER9erxlhppzRms6cvYdKqcIwk6eksrGdbJkBoHobhPo1JCIUXlmrA4qgrEnXInPJpueGE+PE5F+Dk12DA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react-dom" "*"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@visx/curve@3.0.0", "@visx/curve@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/curve/-/curve-3.0.0.tgz#c54568472e00a38483c58cf52e4a6ddb2887c2d4"
|
||||
@@ -3107,6 +3128,36 @@
|
||||
"@types/d3-shape" "^1.3.1"
|
||||
d3-shape "^1.0.6"
|
||||
|
||||
"@visx/event@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@visx/event/-/event-3.0.1.tgz#d5358f52ff5ef30036d955bd2b68b96472ff2d6f"
|
||||
integrity sha512-tK1EUYQLLStBuoCMbm8LJ3VbDyCVI8HjT0pMRQxm+C75FSIVWvrThgrfrC9sWOFnEMEYWspZO7hI5zjsPKjLQA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/point" "3.0.1"
|
||||
|
||||
"@visx/gradient@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/gradient/-/gradient-3.0.0.tgz#39b55dd5a0b34feb219fcb2ea5525388c5ae0c1b"
|
||||
integrity sha512-UoM9R9PIPLO/w7hCW9gFncrLdpKNqh13sLS9/0Iy6b75uP2l05FLG2HX4kXljeyOrj4/XyzRCMYm0HHk/p5iMA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
prop-types "^15.5.7"
|
||||
|
||||
"@visx/grid@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@visx/grid/-/grid-3.0.1.tgz#d91085ed92e7e1c0c2e58710bc33b2f0f33b8e74"
|
||||
integrity sha512-cln5CVvFG58C5Uz1Uf0KRBFmGmgD1NALOQdYDu5yPsTuY2yLzVYPvCIlYBMdUtE0uzfNq972SmkZHfZYs03jxQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/curve" "3.0.0"
|
||||
"@visx/group" "3.0.0"
|
||||
"@visx/point" "3.0.1"
|
||||
"@visx/scale" "3.0.0"
|
||||
"@visx/shape" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"@visx/group@3.0.0", "@visx/group@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/group/-/group-3.0.0.tgz#e7f9752599bcc7e141ff5317a2a9a502577ab8df"
|
||||
@@ -3116,6 +3167,14 @@
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"@visx/mock-data@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/mock-data/-/mock-data-3.0.0.tgz#d8ac030e8dc58b338cba87e6b65276e7f9ac63ad"
|
||||
integrity sha512-qi+G413zX1zIsyKDMvfKDTa8R9n+AOZygKq8qLGB9Uddx0ogXqjypDYN+gxvqSjzjxleuQ9Ml6PNTOdj9V9a1A==
|
||||
dependencies:
|
||||
"@types/d3-random" "^2.2.0"
|
||||
d3-random "^2.2.2"
|
||||
|
||||
"@visx/point@3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@visx/point/-/point-3.0.1.tgz#77587ddaabf6f3023f09f8a0ce33a2c27c9d64c8"
|
||||
@@ -3163,6 +3222,17 @@
|
||||
prop-types "^15.7.2"
|
||||
reduce-css-calc "^1.3.0"
|
||||
|
||||
"@visx/tooltip@^3.1.2":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@visx/tooltip/-/tooltip-3.1.2.tgz#6c7bb36a296f4501adb99b59487412e39fe06f44"
|
||||
integrity sha512-p46qztGRNkEDbxzc3V1virahvz3UQ29TzddUjA0oaTIBCrOd9UJuLvv1Tq9OpeUYPdbrO/ZRwaEeri2pbwv04Q==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/bounds" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.5.10"
|
||||
react-use-measure "^2.0.4"
|
||||
|
||||
"@webassemblyjs/ast@1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
|
||||
@@ -4480,7 +4550,7 @@ d3-array@2:
|
||||
dependencies:
|
||||
internmap "^1.0.0"
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3":
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.3.tgz#39f1f4954e4a09ff69ac597c2d61906b04e84740"
|
||||
integrity sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==
|
||||
@@ -4509,6 +4579,11 @@ d3-path@1, d3-path@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
||||
|
||||
d3-random@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-2.2.2.tgz#5eebd209ef4e45a2b362b019c1fb21c2c98cbb6e"
|
||||
integrity sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==
|
||||
|
||||
d3-scale@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||
@@ -4527,7 +4602,7 @@ d3-shape@^1.0.6, d3-shape@^1.2.0:
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
"d3-time-format@2 - 4":
|
||||
"d3-time-format@2 - 4", d3-time-format@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||
@@ -4567,6 +4642,11 @@ date-fns@2.29.3, date-fns@^2.24.0:
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
|
||||
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
|
||||
|
||||
debounce@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
|
||||
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
|
||||
|
||||
debug@2.6.9, debug@^2.6.0, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@@ -8507,7 +8587,7 @@ prompts@^2.0.1, prompts@^2.4.2:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@@ -8799,6 +8879,13 @@ react-scripts@5.0.1:
|
||||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
react-use-measure@^2.0.4:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.1.tgz#5824537f4ee01c9469c45d5f7a8446177c6cc4ba"
|
||||
integrity sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==
|
||||
dependencies:
|
||||
debounce "^1.2.1"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
|
||||
Reference in New Issue
Block a user