diff --git a/src/components/D3Diagram.tsx b/src/components/D3Diagram.tsx index 1e543d4..e53d8f5 100644 --- a/src/components/D3Diagram.tsx +++ b/src/components/D3Diagram.tsx @@ -13,6 +13,13 @@ const D3Diagram = ({ data, deviceColorMap }: D3DiagramProps) => { useEffect(() => { drawDiagram(d3Ref, data, deviceColorMap); + + const currentD3Ref = d3Ref.current; + return () => { + if (currentD3Ref && currentD3Ref.firstChild) { + currentD3Ref.firstChild.remove(); + } + }; }, [data, deviceColorMap]); return ; diff --git a/src/components/Diagram.tsx b/src/components/Diagram.tsx index ac8af16..c787191 100644 --- a/src/components/Diagram.tsx +++ b/src/components/Diagram.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useMemo, useState, useEffect } from "react"; import styled from "styled-components"; import DataType from "../../functions/src/models/DataType"; import GetMeasurementsParams from "../../functions/src/models/GetMeasurementsParams"; @@ -8,11 +8,13 @@ import { FlexColumn } from "../elements/Flex"; import { Heading2 } from "../elements/Typography"; import getMeasurements from "../api/getMeasurements"; import measurementsContainDataPoints from "./measurementsContainDataPoints"; +import Overlay from "../elements/Overlay"; interface DiagramProps { dataType: DataType; queryParams: GetMeasurementsParams; deviceColorMap: Map; + autoRefreshPeriod: number; } const DiagramContainer = styled(FlexColumn)` @@ -26,37 +28,62 @@ const DiagramContainer = styled(FlexColumn)` const DiagramInner = styled.div` display: flex; + position: relative; flex-grow: 1; width: 100%; background-color: ${({ theme }) => theme.palette.background.paper}; `; -const Diagram = ({ dataType, queryParams, deviceColorMap }: DiagramProps) => { +const Diagram = ({ + dataType, + queryParams, + deviceColorMap, + autoRefreshPeriod, +}: DiagramProps) => { + const [updateKey, setUpdateKey] = useState(0); + useEffect(() => { + let interval: any = 0; + if (autoRefreshPeriod !== 0) { + interval = setInterval( + () => setUpdateKey(Date.now()), + autoRefreshPeriod * 1000 + ); + } else { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [autoRefreshPeriod]); const query = useMemo(() => { return getMeasurements(queryParams); }, [queryParams]); const { responseData: measurements, error, isLoading } = useQuery({ query, + compare: updateKey, }); + const canShowRealData = + measurements && + measurements.length && + measurementsContainDataPoints(measurements); + return ( - {dataType.name} (unit: {dataType.unit}) + {dataType.name} ({dataType.unit}) + {isLoading ? ( - "Loading data..." + Loading data... ) : error ? ( - "Failed to load data." - ) : measurements && - measurements.length && - measurementsContainDataPoints(measurements) ? ( - - ) : ( - "No data to display." - )} + Error loading data. + ) : !canShowRealData ? ( + No data to display + ) : null} ); diff --git a/src/elements/Overlay.ts b/src/elements/Overlay.ts new file mode 100644 index 0000000..82ac1c9 --- /dev/null +++ b/src/elements/Overlay.ts @@ -0,0 +1,17 @@ +import styled from "styled-components"; + +const Overlay = styled.div` + backdrop-filter: blur(50px); + opacity: 0.9; + background-color: ${({ theme }) => theme.palette.background.paper}; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; +`; + +export default Overlay; diff --git a/src/elements/Switch.tsx b/src/elements/Switch.tsx new file mode 100644 index 0000000..5228fa1 --- /dev/null +++ b/src/elements/Switch.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import styled from "styled-components"; + +interface SwitchProps { + checked: boolean; + label: string; + onChange: () => void; +} + +const HiddenSwitch = styled.input.attrs({ + type: "checkbox", +})` + left: -100%; + opacity: 0; + position: absolute; + margin: 0; + padding: 0; +`; + +const SwitchContainer = styled.label` + position: relative; + display: inline-flex; + flex-flow: row nowrap; + flex-grow: 0; + overflow: hidden; + padding-top: ${({ theme }) => theme.spacing.d1}px; + padding-bottom: ${({ theme }) => theme.spacing.d1}px; +`; + +const SwitchLabel = styled.span` + display: block; + margin-left: ${({ theme }) => theme.spacing.d1}px; +`; + +const SwitchBackground = styled.span>` + display: block; + width: ${({ theme }) => theme.spacing.d4 + theme.spacing.d2}px; + height: ${({ theme }) => theme.spacing.d2}px; + border-radius: ${({ theme }) => theme.shapes.borderRadius}px; + background-color: ${({ theme }) => theme.palette.grey[100]}; +`; + +const SwitchBullet = styled.span>` + display: block; + position: absolute; + left: ${({ theme, checked }) => (!checked ? "0" : theme.spacing.d4 + "px")}; + transition: all 0.1s ease-in-out; + height: ${({ theme }) => theme.spacing.d2}px; + width: ${({ theme }) => theme.spacing.d2}px; + border-radius: ${({ theme }) => theme.shapes.borderRadius}px; + background-color: ${({ theme, checked }) => + !checked ? theme.palette.grey[400] : theme.palette.primary.main}; +`; + +const Switch = ({ checked, label, onChange }: SwitchProps) => ( + + + + + + {label} + +); + +export default Switch; diff --git a/src/views/MainView.tsx b/src/views/MainView.tsx index 6a84d3b..81e97e5 100644 --- a/src/views/MainView.tsx +++ b/src/views/MainView.tsx @@ -6,13 +6,19 @@ import TimeRangeRow from "../components/TimeRangeRow"; import { FlexColumn } from "../elements/Flex"; import GridContainer from "../elements/GridContainer"; import Label from "../elements/Label"; +import Switch from "../elements/Switch"; import useQuery from "../hooks/useQuery"; +const REFRESH_FREQUENCY = 10; + const MainView = () => { const { responseData: dataTypes, error, isLoading } = useQuery({ query: getDataTypes, }); + // 0 is false + const [autoRefreshPeriod, setAutoRefreshPeriod] = useState(0); + // empty list state means are all selected const [selectedDevices, setSelectedDevices] = useState([]); @@ -46,6 +52,15 @@ const MainView = () => { setSelectedDevices={setSelectedDevices} setDeviceColorMap={setDeviceColorMap} /> + + setAutoRefreshPeriod((currentPeriod) => + !!currentPeriod ? 0 : REFRESH_FREQUENCY + ) + } + label={`Automatically refresh measurements every ${REFRESH_FREQUENCY} seconds`} + /> {isLoading ? ( @@ -59,6 +74,7 @@ const MainView = () => { deviceColorMap={deviceColorMap} key={dataType.id} dataType={dataType} + autoRefreshPeriod={autoRefreshPeriod} queryParams={{ dataTypes: [dataType.id], devices: selectedDevices,