Loading analyst/package.json +0 −2 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ "@tanstack/react-router": "^1.109.2", "d3-array": "^3.2.4", "d3-axis": "^3.0.0", "d3-brush": "^3.0.0", "d3-format": "^3.1.0", "d3-hierarchy": "^3.1.2", "d3-polygon": "^3.0.1", Loading Loading @@ -64,7 +63,6 @@ "@types/d3": "^7.4.3", "@types/d3-array": "^3.2.1", "@types/d3-axis": "^3.0.6", "@types/d3-brush": "^3.0.6", "@types/d3-format": "^3.0.4", "@types/d3-hierarchy": "^3", "@types/d3-polygon": "^3", Loading analyst/src/components/ActionLog/index.tsx +19 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ import { ActionLogIcon } from '../ActionLogTitle/ActionLogIcon' import useActionLogs from '../dataHooks/useActionLogs' import { useTools } from '../dataHooks/useTools' import type { SelectedAction, SelectedState } from '../Overview/selectedReducer' import type { TimeRange } from '../Plots/TimeScatterPlot/types' const centerChildren = css` display: flex; Loading @@ -30,6 +31,8 @@ interface ActionLogProps { teamLimit?: number displayAction: DisplayActionType teamIds: string[] selectedTimeRange: TimeRange earliestStartTime: number } export const ActionLog: FC<ActionLogProps> = ({ Loading @@ -38,16 +41,26 @@ export const ActionLog: FC<ActionLogProps> = ({ teamLimit, displayAction, teamIds, selectedTimeRange, earliestStartTime, }) => { const tools = useTools() const actionLogs = useActionLogs({ teamLimit, teamIds }).filter(actionLog => const actionLogs = useActionLogs({ teamLimit, teamIds }) .filter(actionLog => displayActionFilter({ actionLog, displayAction, tools, }) ) .filter(actionLog => { const actionLogTime = new Date(actionLog.timestamp).getTime() return ( actionLogTime >= earliestStartTime + selectedTimeRange.rangeStart && actionLogTime <= earliestStartTime + selectedTimeRange.rangeEnd ) }) const getHandleClick = useCallback( (actionLog: IAnalystActionLogSimple) => () => Loading analyst/src/components/Overview/OverviewPlot.tsx +10 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import useAutoInjects from '../dataHooks/useAutoInjects' import { ExerciseContext } from '../ExerciseContext' import LegendText from '../Plots/LegendText' import ScatterPlot from '../Plots/TimeScatterPlot' import type { TimeRange } from '../Plots/TimeScatterPlot/types' import type { ScatterPlotDataRenderer } from '../Plots/types' import { LEGEND_ELEMENT_SIZE, Loading @@ -36,6 +37,9 @@ interface OverviewPlotProps { teams: Team[] showLegend: boolean setShowLegend: Dispatch<SetStateAction<boolean>> earliestStartTime: number latestStartTime: number selectedTimeRange: TimeRange } const OverviewPlot: FC<OverviewPlotProps> = ({ Loading @@ -47,6 +51,9 @@ const OverviewPlot: FC<OverviewPlotProps> = ({ teams, showLegend, setShowLegend, earliestStartTime, latestStartTime, selectedTimeRange, }) => { const { exercise } = useContext(ExerciseContext) Loading Loading @@ -173,6 +180,9 @@ const OverviewPlot: FC<OverviewPlotProps> = ({ dataRenderer={dataRenderer} showLegend={showLegend} setShowLegend={setShowLegend} earliestStartTime={earliestStartTime} latestStartTime={latestStartTime} selectedTimeRange={selectedTimeRange} /> ) } Loading analyst/src/components/Overview/index.tsx +33 −67 Original line number Diff line number Diff line import { Divider } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import { css } from '@emotion/css' import type { Team, VariablesOf } from '@inject/graphql' import { AnalystActionLogsSimpleSubscription, useSubscription, } from '@inject/graphql' import { CenteredSpinner, HelpIcon } from '@inject/shared' import { CenteredSpinner } from '@inject/shared' import type { Dispatch, FC } from 'react' import { Suspense, useState } from 'react' import { ActionLog } from '../ActionLog' import { ActionLogOptions } from '../ActionLogOptions' import { useActionLogOptionsWithAutoProps } from '../ActionLogOptions/useActionLogOptionsProps' import type { ActionLogOptionsProps } from '../ActionLogOptions' import type { TimeRange } from '../Plots/TimeScatterPlot/types' import Detail from './Detail' import OverviewPlot from './OverviewPlot' import type { SelectedAction, SelectedState } from './selectedReducer' Loading @@ -22,11 +22,6 @@ const wrapper = css` flex-direction: column; ` const options = css` display: flex; justify-content: space-between; ` const plot = ({ teamCount, showLegend, Loading @@ -35,58 +30,41 @@ const plot = ({ showLegend: boolean }) => css` /* make sure the legend is visible even for 1 team */ min-height: ${(showLegend ? 20 : 10) + 1.5 * teamCount}rem; min-height: ${Math.max(showLegend ? 20 : 10, 10 + 1.5 * teamCount)}rem; ` const footer = css` flex: 1; display: flex; overflow: auto; ` const footerAllTeams = css` min-height: 20rem; min-height: 15rem; ` const footerElement = css` flex: 1; overflow: auto; display: flex; min-height: 15rem; ` const footerElementAllTeams = css` min-height: 20rem; ` const HELP_TEXT = `\ ZOOM: Select an area in the graph using the left mouse button\ and pull it over the part of the graph you would like to see. To reset, click on the button below the graph. FILTERS: Click on the filter icon in the top right corner\ to open the filters menu. SELECTION: Click on an event to select it. Selecting an event will highlight it in the graph. Selecting an event will display its details below the graph. Selecting an event will highlight other similar events. To reset, click on the button below the graph or click on the\ selected event again.` interface OverviewProps { selectedState: SelectedState selectedDispatch: Dispatch<SelectedAction> teams: Team[] earliestStartTime: number latestStartTime: number selectedTimeRange: TimeRange actionLogOptionsProps: Required<ActionLogOptionsProps> } export const Overview: FC<OverviewProps> = ({ selectedState, selectedDispatch, teams, earliestStartTime, latestStartTime, selectedTimeRange, actionLogOptionsProps, }) => { useSubscription({ document: AnalystActionLogsSimpleSubscription, Loading @@ -96,22 +74,12 @@ export const Overview: FC<OverviewProps> = ({ pause: !teams.length, }) const actionLogOptionsProps = useActionLogOptionsWithAutoProps({ teamCount: teams.length, teamLimitDefault: 20, selectedTeamId: teams.length === 1 ? teams[0].id : null, }) const { displayAuto, displayAction, teamLimit } = actionLogOptionsProps const [showLegend, setShowLegend] = useState(true) return ( <div className={wrapper}> <div className={options}> <HelpIcon text={HELP_TEXT} /> <ActionLogOptions {...actionLogOptionsProps} /> </div> <div className={plot({ teamCount: teams.length, showLegend })}> <Suspense fallback={<CenteredSpinner />}> <OverviewPlot Loading @@ -123,34 +91,30 @@ export const Overview: FC<OverviewProps> = ({ teams={teams} showLegend={showLegend} setShowLegend={setShowLegend} earliestStartTime={earliestStartTime} latestStartTime={latestStartTime} selectedTimeRange={selectedTimeRange} /> </Suspense> </div> <Divider /> <Divider className={css` margin: 0.5rem 1rem; `} /> <div className={cx({ [footer]: true, [footerAllTeams]: teams.length > 1, })} > <div className={cx({ [footerElement]: true, [footerElementAllTeams]: teams.length > 1, })} > <div className={footer}> <div className={footerElement}> <Detail selectedState={selectedState} displayAuto={displayAuto} /> </div> <Divider /> <div className={cx({ [footerElement]: true, [footerElementAllTeams]: teams.length > 1, })} > <Divider className={css` margin: 1rem 0.5rem; `} /> <div className={footerElement}> <Suspense fallback={<CenteredSpinner />}> <ActionLog selectedState={selectedState} Loading @@ -158,6 +122,8 @@ export const Overview: FC<OverviewProps> = ({ teamLimit={teamLimit} displayAction={displayAction} teamIds={teams.map(team => team.id)} selectedTimeRange={selectedTimeRange} earliestStartTime={earliestStartTime} /> </Suspense> </div> Loading analyst/src/components/Plots/TimeScatterPlot/Brush.tsxdeleted 100644 → 0 +0 −47 Original line number Diff line number Diff line import { brushX } from 'd3-brush' import { select } from 'd3-selection' import type { FC } from 'react' import { useEffect, useRef } from 'react' import type { ScatterPlotTime, XScale } from './types' interface BrushProps { setTime: ({ startTime, endTime }: ScatterPlotTime) => void width: number height: number xScale: XScale } const Brush: FC<BrushProps> = ({ setTime, width, height, xScale }) => { const ref = useRef<SVGGElement>(null) const brush = brushX() .extent([ [0, 0], [width, Math.max(height - 1, 0)], ]) // eslint-disable-next-line react-compiler/react-compiler .on('end', event => { if (event.selection == null) { return } if (ref.current) { select(ref.current).call(brush.move, null) } setTime({ startTime: xScale.invert(event.selection[0]).getTime(), endTime: xScale.invert(event.selection[1]).getTime(), }) }) useEffect(() => { if (ref.current) { select(ref.current).call(brush) } }, [brush, xScale]) return <g ref={ref} /> } export default Brush Loading
analyst/package.json +0 −2 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ "@tanstack/react-router": "^1.109.2", "d3-array": "^3.2.4", "d3-axis": "^3.0.0", "d3-brush": "^3.0.0", "d3-format": "^3.1.0", "d3-hierarchy": "^3.1.2", "d3-polygon": "^3.0.1", Loading Loading @@ -64,7 +63,6 @@ "@types/d3": "^7.4.3", "@types/d3-array": "^3.2.1", "@types/d3-axis": "^3.0.6", "@types/d3-brush": "^3.0.6", "@types/d3-format": "^3.0.4", "@types/d3-hierarchy": "^3", "@types/d3-polygon": "^3", Loading
analyst/src/components/ActionLog/index.tsx +19 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ import { ActionLogIcon } from '../ActionLogTitle/ActionLogIcon' import useActionLogs from '../dataHooks/useActionLogs' import { useTools } from '../dataHooks/useTools' import type { SelectedAction, SelectedState } from '../Overview/selectedReducer' import type { TimeRange } from '../Plots/TimeScatterPlot/types' const centerChildren = css` display: flex; Loading @@ -30,6 +31,8 @@ interface ActionLogProps { teamLimit?: number displayAction: DisplayActionType teamIds: string[] selectedTimeRange: TimeRange earliestStartTime: number } export const ActionLog: FC<ActionLogProps> = ({ Loading @@ -38,16 +41,26 @@ export const ActionLog: FC<ActionLogProps> = ({ teamLimit, displayAction, teamIds, selectedTimeRange, earliestStartTime, }) => { const tools = useTools() const actionLogs = useActionLogs({ teamLimit, teamIds }).filter(actionLog => const actionLogs = useActionLogs({ teamLimit, teamIds }) .filter(actionLog => displayActionFilter({ actionLog, displayAction, tools, }) ) .filter(actionLog => { const actionLogTime = new Date(actionLog.timestamp).getTime() return ( actionLogTime >= earliestStartTime + selectedTimeRange.rangeStart && actionLogTime <= earliestStartTime + selectedTimeRange.rangeEnd ) }) const getHandleClick = useCallback( (actionLog: IAnalystActionLogSimple) => () => Loading
analyst/src/components/Overview/OverviewPlot.tsx +10 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import useAutoInjects from '../dataHooks/useAutoInjects' import { ExerciseContext } from '../ExerciseContext' import LegendText from '../Plots/LegendText' import ScatterPlot from '../Plots/TimeScatterPlot' import type { TimeRange } from '../Plots/TimeScatterPlot/types' import type { ScatterPlotDataRenderer } from '../Plots/types' import { LEGEND_ELEMENT_SIZE, Loading @@ -36,6 +37,9 @@ interface OverviewPlotProps { teams: Team[] showLegend: boolean setShowLegend: Dispatch<SetStateAction<boolean>> earliestStartTime: number latestStartTime: number selectedTimeRange: TimeRange } const OverviewPlot: FC<OverviewPlotProps> = ({ Loading @@ -47,6 +51,9 @@ const OverviewPlot: FC<OverviewPlotProps> = ({ teams, showLegend, setShowLegend, earliestStartTime, latestStartTime, selectedTimeRange, }) => { const { exercise } = useContext(ExerciseContext) Loading Loading @@ -173,6 +180,9 @@ const OverviewPlot: FC<OverviewPlotProps> = ({ dataRenderer={dataRenderer} showLegend={showLegend} setShowLegend={setShowLegend} earliestStartTime={earliestStartTime} latestStartTime={latestStartTime} selectedTimeRange={selectedTimeRange} /> ) } Loading
analyst/src/components/Overview/index.tsx +33 −67 Original line number Diff line number Diff line import { Divider } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import { css } from '@emotion/css' import type { Team, VariablesOf } from '@inject/graphql' import { AnalystActionLogsSimpleSubscription, useSubscription, } from '@inject/graphql' import { CenteredSpinner, HelpIcon } from '@inject/shared' import { CenteredSpinner } from '@inject/shared' import type { Dispatch, FC } from 'react' import { Suspense, useState } from 'react' import { ActionLog } from '../ActionLog' import { ActionLogOptions } from '../ActionLogOptions' import { useActionLogOptionsWithAutoProps } from '../ActionLogOptions/useActionLogOptionsProps' import type { ActionLogOptionsProps } from '../ActionLogOptions' import type { TimeRange } from '../Plots/TimeScatterPlot/types' import Detail from './Detail' import OverviewPlot from './OverviewPlot' import type { SelectedAction, SelectedState } from './selectedReducer' Loading @@ -22,11 +22,6 @@ const wrapper = css` flex-direction: column; ` const options = css` display: flex; justify-content: space-between; ` const plot = ({ teamCount, showLegend, Loading @@ -35,58 +30,41 @@ const plot = ({ showLegend: boolean }) => css` /* make sure the legend is visible even for 1 team */ min-height: ${(showLegend ? 20 : 10) + 1.5 * teamCount}rem; min-height: ${Math.max(showLegend ? 20 : 10, 10 + 1.5 * teamCount)}rem; ` const footer = css` flex: 1; display: flex; overflow: auto; ` const footerAllTeams = css` min-height: 20rem; min-height: 15rem; ` const footerElement = css` flex: 1; overflow: auto; display: flex; min-height: 15rem; ` const footerElementAllTeams = css` min-height: 20rem; ` const HELP_TEXT = `\ ZOOM: Select an area in the graph using the left mouse button\ and pull it over the part of the graph you would like to see. To reset, click on the button below the graph. FILTERS: Click on the filter icon in the top right corner\ to open the filters menu. SELECTION: Click on an event to select it. Selecting an event will highlight it in the graph. Selecting an event will display its details below the graph. Selecting an event will highlight other similar events. To reset, click on the button below the graph or click on the\ selected event again.` interface OverviewProps { selectedState: SelectedState selectedDispatch: Dispatch<SelectedAction> teams: Team[] earliestStartTime: number latestStartTime: number selectedTimeRange: TimeRange actionLogOptionsProps: Required<ActionLogOptionsProps> } export const Overview: FC<OverviewProps> = ({ selectedState, selectedDispatch, teams, earliestStartTime, latestStartTime, selectedTimeRange, actionLogOptionsProps, }) => { useSubscription({ document: AnalystActionLogsSimpleSubscription, Loading @@ -96,22 +74,12 @@ export const Overview: FC<OverviewProps> = ({ pause: !teams.length, }) const actionLogOptionsProps = useActionLogOptionsWithAutoProps({ teamCount: teams.length, teamLimitDefault: 20, selectedTeamId: teams.length === 1 ? teams[0].id : null, }) const { displayAuto, displayAction, teamLimit } = actionLogOptionsProps const [showLegend, setShowLegend] = useState(true) return ( <div className={wrapper}> <div className={options}> <HelpIcon text={HELP_TEXT} /> <ActionLogOptions {...actionLogOptionsProps} /> </div> <div className={plot({ teamCount: teams.length, showLegend })}> <Suspense fallback={<CenteredSpinner />}> <OverviewPlot Loading @@ -123,34 +91,30 @@ export const Overview: FC<OverviewProps> = ({ teams={teams} showLegend={showLegend} setShowLegend={setShowLegend} earliestStartTime={earliestStartTime} latestStartTime={latestStartTime} selectedTimeRange={selectedTimeRange} /> </Suspense> </div> <Divider /> <Divider className={css` margin: 0.5rem 1rem; `} /> <div className={cx({ [footer]: true, [footerAllTeams]: teams.length > 1, })} > <div className={cx({ [footerElement]: true, [footerElementAllTeams]: teams.length > 1, })} > <div className={footer}> <div className={footerElement}> <Detail selectedState={selectedState} displayAuto={displayAuto} /> </div> <Divider /> <div className={cx({ [footerElement]: true, [footerElementAllTeams]: teams.length > 1, })} > <Divider className={css` margin: 1rem 0.5rem; `} /> <div className={footerElement}> <Suspense fallback={<CenteredSpinner />}> <ActionLog selectedState={selectedState} Loading @@ -158,6 +122,8 @@ export const Overview: FC<OverviewProps> = ({ teamLimit={teamLimit} displayAction={displayAction} teamIds={teams.map(team => team.id)} selectedTimeRange={selectedTimeRange} earliestStartTime={earliestStartTime} /> </Suspense> </div> Loading
analyst/src/components/Plots/TimeScatterPlot/Brush.tsxdeleted 100644 → 0 +0 −47 Original line number Diff line number Diff line import { brushX } from 'd3-brush' import { select } from 'd3-selection' import type { FC } from 'react' import { useEffect, useRef } from 'react' import type { ScatterPlotTime, XScale } from './types' interface BrushProps { setTime: ({ startTime, endTime }: ScatterPlotTime) => void width: number height: number xScale: XScale } const Brush: FC<BrushProps> = ({ setTime, width, height, xScale }) => { const ref = useRef<SVGGElement>(null) const brush = brushX() .extent([ [0, 0], [width, Math.max(height - 1, 0)], ]) // eslint-disable-next-line react-compiler/react-compiler .on('end', event => { if (event.selection == null) { return } if (ref.current) { select(ref.current).call(brush.move, null) } setTime({ startTime: xScale.invert(event.selection[0]).getTime(), endTime: xScale.invert(event.selection[1]).getTime(), }) }) useEffect(() => { if (ref.current) { select(ref.current).call(brush) } }, [brush, xScale]) return <g ref={ref} /> } export default Brush