Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions src/Components/ChartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "../Helpers/Types";
import { Chart as ChartJS, ChartData, CategoryScale } from 'chart.js/auto';
import { createOptions, buildChartHtml } from "../Helpers/ChartHelpers";
import { Row, Spinner, Tooltip } from "react-bootstrap";
import { Row, Col, Card, ButtonGroup, Button, OverlayTrigger, Ratio, Spinner, Tooltip } from "react-bootstrap";
import { ThemeContext } from "../contexts/ThemeContext";
import { Line, Bar, Doughnut } from 'react-chartjs-2';
import { Const } from "../Helpers/Constants";
Expand Down Expand Up @@ -70,7 +70,7 @@ const BEST_SOLVES_COLS = [

export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState & { _propsKey?: string }> {
static contextType = ThemeContext;
state: ChartPanelState & { _propsKey?: string } = { chartData: null, isComputing: false };
state: ChartPanelState & { _propsKey?: string } = { chartData: null, isComputing: false, solvesPerPeriod: 'day' };

static getDerivedStateFromProps(
nextProps: ChartPanelProps,
Expand Down Expand Up @@ -100,6 +100,7 @@ export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState
private _lastMethodNameRef: MethodName = MethodName.CFOP;
private _lastUse4SegmentTimingRef: boolean = false;
private _lastIsDarkRef: boolean = false;
private _lastAllDaysRef: boolean = false;

private _isDark(): boolean {
return (this.context as { isDark?: boolean } | undefined)?.isDark ?? false;
Expand All @@ -119,7 +120,8 @@ export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState
this._lastBadTimeRef !== p.badTime ||
this._lastMethodNameRef !== p.methodName ||
this._lastUse4SegmentTimingRef !== p.use4SegmentTiming ||
this._lastIsDarkRef !== isDark
this._lastIsDarkRef !== isDark ||
this._lastAllDaysRef !== p.allDays
);
}

Expand All @@ -136,6 +138,7 @@ export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState
this._lastMethodNameRef = p.methodName;
this._lastUse4SegmentTimingRef = p.use4SegmentTiming;
this._lastIsDarkRef = isDark;
this._lastAllDaysRef = p.allDays;
}

private _sendWork(): void {
Expand All @@ -155,6 +158,7 @@ export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState
methodName: p.methodName,
use4SegmentTiming: p.use4SegmentTiming,
isDark: this._isDark(),
allDays: p.allDays,
});
}

Expand Down Expand Up @@ -248,7 +252,48 @@ export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState
charts.push(buildChartHtml(<Line data={c.runningInspection as ChartData<"line">} options={createOptions(ChartType.Line, "Solve Number", "Time (s)", p.useLogScale, true, false, isDark)} />, "Average Inspection Time", "This chart shows how much inspection time you use on average"));
}
charts.push(buildChartHtml(<div style={chartDataGridWrapStyle}><DataGrid style={chartDataGridStyle} rows={c.streakRows as StreakRow[]} columns={STREAK_COLS} /></div>, "Longest Daily Streaks", "How many days in a row you've achieved solves of each time"));
charts.push(buildChartHtml(<Line data={c.dailyRecord as ChartData<"line">} options={createOptions(ChartType.Line, "Date", "Time (s)", p.useLogScale, true, true, isDark)} />, "Daily Fastest Solve", "This chart shows the fastest solve for each day, based on the selected filters"));
{
const dr = c.dailyRecord as { datasets: unknown[]; xAxisMin?: Date; xAxisMax?: Date };
const drOptions = createOptions(ChartType.Line, "Date", "Time (s)", p.useLogScale, true, true, isDark);
if (p.allDays) {
(drOptions as any).scales.x.type = 'time';
const DAY_MS = 86_400_000;
if (dr.xAxisMin) (drOptions as any).scales.x.min = dr.xAxisMin.valueOf() - 3 * DAY_MS;
if (dr.xAxisMax) (drOptions as any).scales.x.max = dr.xAxisMax.valueOf() + 3 * DAY_MS;
}
charts.push(buildChartHtml(<Line data={dr as unknown as ChartData<"line">} options={drOptions} />, "Daily Fastest Solve", "This chart shows the fastest solve for each day, based on the selected filters"));
}
{
const period = this.state.solvesPerPeriod;
const periodLabels: Record<typeof period, string> = { day: 'Daily', week: 'Weekly', month: 'Monthly' };
const periodData = { day: c.solvesPerDay, week: c.solvesPerWeek, month: c.solvesPerMonth }[period];
const xAxisLabel = { day: 'Day', week: 'Week', month: 'Month' }[period];
charts.push(
<Col key="solvesPerPeriod" className="col-12 col-md-6">
<Card className="p-2 p-md-3 shadow-sm">
<div className="d-flex align-items-center mb-1">
<span style={{ flex: 1 }} />
<OverlayTrigger placement="top" overlay={<Tooltip id="tooltip-solves-period">Number of solves completed per day, week, or month</Tooltip>}>
<span className="fw-bold">Solve Count ⓘ</span>
</OverlayTrigger>
<span style={{ flex: 1, display: 'flex', justifyContent: 'flex-end' }}>
<ButtonGroup size="sm">
{(['day', 'week', 'month'] as const).map(p => (
<Button key={p} variant={period === p ? 'primary' : 'outline-secondary'}
onClick={() => this.setState({ solvesPerPeriod: p })}>
{periodLabels[p]}
</Button>
))}
</ButtonGroup>
</span>
</div>
<Ratio aspectRatio="4x3">
<Bar data={periodData as ChartData<"bar">} options={createOptions(ChartType.Bar, xAxisLabel, "Solves", false, false, false, isDark)} />
</Ratio>
</Card>
</Col>
);
}
charts.push(buildChartHtml(<div style={chartDataGridWrapStyle}><DataGrid style={chartDataGridStyle} rows={c.recordRows as RecordRow[]} columns={RECORD_COLS} /></div>, "Current Records", "This chart shows your current records for Single, Ao5, Ao12, Ao100, and Ao1000"));

if (hasOll) {
Expand All @@ -269,7 +314,19 @@ export class ChartPanel extends React.Component<ChartPanelProps, ChartPanelState

if (p.steps.length === Const.MethodSteps[p.methodName].length) {
charts.push(buildChartHtml(<Line data={c.goodBad as ChartData<"line">} options={createOptions(ChartType.Line, "Solve Number", "Percentage", p.useLogScale, true, false, isDark)} />, "Percentage of 'Good' and 'Bad' Solves", "This chart shows your running average of solves considered 'good' and 'bad'. This can be configured in the filter panel. Just set the good and bad values to times you feel are correct"));
charts.push(buildChartHtml(<Line data={c.recordHistory as ChartData<"line">} options={createOptions(ChartType.Line, "Date", "Time (s)", p.useLogScale, true, true, isDark)} />, "History of Records", "This chart shows your history of PBs. Note that this will only show solves that meet the criteria in your filters, so don't be alarmed if you don't see your PB here. As a note, Ao12 removes the best and worst solves of the 12. Ao100 removes the best and worst 5. Ao1000 removes the best and worst 50."));
{
const rh = c.recordHistory as { datasets: unknown[]; xAxisMin?: Date; xAxisMax?: Date };
const rhOptions = createOptions(ChartType.Line, "Date", "Time (s)", p.useLogScale, true, true, isDark);
if (p.allDays) {
// 'timeseries' evenly spaces data points so gaps disappear; 'time' renders
// a continuous timeline. Also pin min/max so the axis spans all solve dates.
(rhOptions as any).scales.x.type = 'time';
const DAY_MS = 86_400_000;
if (rh.xAxisMin) (rhOptions as any).scales.x.min = rh.xAxisMin.valueOf() - 3 * DAY_MS;
if (rh.xAxisMax) (rhOptions as any).scales.x.max = rh.xAxisMax.valueOf() + 3 * DAY_MS;
}
charts.push(buildChartHtml(<Line data={rh as unknown as ChartData<"line">} options={rhOptions} />, "History of Records", "This chart shows your history of PBs. Note that this will only show solves that meet the criteria in your filters, so don't be alarmed if you don't see your PB here. As a note, Ao12 removes the best and worst solves of the 12. Ao100 removes the best and worst 5. Ao1000 removes the best and worst 50."));
}
}

return (
Expand Down
30 changes: 22 additions & 8 deletions src/Components/FilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { MultiSelect } from "react-multi-select-component";
import { CrossColor, FilterPanelProps, FilterPanelState, Filters, getStep, MethodName, Option, Solve, SolveCleanliness, SolveLuckiness, Step, StepName } from "../Helpers/Types";
import { ChartPanel } from "./ChartPanel";
import { calculateMovingAverage, calculateMovingStdDev } from "../Helpers/MathHelpers";
import { FormControl, Card, Row, Offcanvas, Col, Button, Tooltip, OverlayTrigger, Alert, Container, CardText, Spinner } from 'react-bootstrap';
import { FormControl, Card, Row, Offcanvas, Col, Button, Tooltip, OverlayTrigger, Alert, Container, Spinner } from 'react-bootstrap';
import { Const } from "../Helpers/Constants";
import { CalculateAllSessionOptions, CalculateBenchmarkTimes, CalculateWindowSize } from "../Helpers/CubeHelpers";
import ReactSwitch from "react-switch";
Expand Down Expand Up @@ -80,14 +80,15 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt
goodTime: 15,
method: { label: MethodName.CFOP, value: MethodName.CFOP },
useLogScale: false,
use4SegmentTiming: true
use4SegmentTiming: true,
allDays: false
}

static passesFilters(solve: Solve, filters: Filters) {
if (solve.isCorrupt) {
return false;
}
if (solve.method != filters.method) {
if (solve.method !== filters.method) {
return false;
}
if (filters.sources.indexOf(solve.source) < 0) {
Expand Down Expand Up @@ -115,11 +116,11 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt

// TODO: check case logic properly
const pllStep = getStep(solve, StepName.PLL);
if (solve.method == MethodName.CFOP && pllStep?.case !== undefined && filters.pllCases.indexOf(pllStep.case) < 0) {
if (solve.method === MethodName.CFOP && pllStep?.case !== undefined && filters.pllCases.indexOf(pllStep.case) < 0) {
return false;
}
const ollStep = getStep(solve, StepName.OLL);
if (solve.method == MethodName.CFOP && ollStep?.case !== undefined && filters.ollCases.indexOf(ollStep.case) < 0) {
if (solve.method === MethodName.CFOP && ollStep?.case !== undefined && filters.ollCases.indexOf(ollStep.case) < 0) {
return false;
}

Expand Down Expand Up @@ -158,7 +159,7 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt

// For each step, check if it is 3 standard deviations more than the average
static markAllMistakes(allSolves: Solve[], windowSize: number): Solve[] {
if (allSolves.length == 0) {
if (allSolves.length === 0) {
return [];
}

Expand Down Expand Up @@ -302,7 +303,8 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt
badTime: prevState.badTime,
goodTime: prevState.goodTime,
useLogScale: prevState.useLogScale,
use4SegmentTiming: prevState.use4SegmentTiming
use4SegmentTiming: prevState.use4SegmentTiming,
allDays: prevState.allDays
};
}

Expand Down Expand Up @@ -334,7 +336,8 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt
badTime: prevState.badTime,
goodTime: prevState.goodTime,
useLogScale: prevState.useLogScale,
use4SegmentTiming: prevState.use4SegmentTiming
use4SegmentTiming: prevState.use4SegmentTiming,
allDays: prevState.allDays
}

// Update anything that needs it
Expand Down Expand Up @@ -567,6 +570,10 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt
this.setState({ use4SegmentTiming: checked });
}

setAllDays(checked: boolean) {
this.setState({ allDays: checked });
}

setCleanliness(selectedList: any[]) {
this.setState({
solveCleanliness: selectedList,
Expand Down Expand Up @@ -853,6 +860,12 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt
"Show recognition, pre-AUF, execution, and post-AUF as separate segments in timing charts. When off, shows only recognition and execution."
)}

{this.createFilterHtml(
<ReactSwitch id="allDays" checked={this.state.allDays} onChange={this.setAllDays.bind(this)} />,
"All Days",
"When on, date-based charts show a continuous timeline spanning all solve dates. Days/weeks/months without solves are shown as gaps or zero bars."
)}

{this.createFilterHtml(
<div className="row align-items-center g-2">
<div className="col-auto d-flex align-items-center gap-2">
Expand Down Expand Up @@ -922,6 +935,7 @@ export class FilterPanel extends React.Component<FilterPanelProps, FilterPanelSt
steps={this.state.filters.steps}
useLogScale={this.state.useLogScale}
use4SegmentTiming={this.state.use4SegmentTiming}
allDays={this.state.allDays}
/>
</Col>
</Row>
Expand Down
2 changes: 1 addition & 1 deletion src/Helpers/CsvParser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ function parseCubeastCsv(stringVal: string, splitter: string): Solve[] {
"date": (obj, value) => { obj.date = moment.utc(value, 'YYYY-MM-DD hh:mm:ss').toDate(); },
"solution_rotation": (obj, value) => {
obj.crossColor = Const.crossMappings.get(value) ?? CrossColor.Unknown;
if (obj.crossColor == CrossColor.Unknown) {
if (obj.crossColor === CrossColor.Unknown) {
//console.log("Unknown solution rotation: ", value);
//obj.isCorrupt = true;
};
Expand Down
2 changes: 0 additions & 2 deletions src/Helpers/MathHelpers.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Deque } from "@datastructures-js/deque";
import { Const } from "./Constants";
import { Records } from "./Types";
var Set = require("sorted-set");

export function sumArray(data: number[]): number {
return data.reduce((acc, curr) => acc + curr, 0);
Expand Down
7 changes: 5 additions & 2 deletions src/Helpers/Types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ export interface FilterPanelState {
goodTime: number,
method: Option,
useLogScale: boolean,
use4SegmentTiming: boolean
use4SegmentTiming: boolean,
allDays: boolean
}

export interface FileInputProps {
Expand All @@ -204,12 +205,14 @@ export interface ChartPanelProps {
methodName: MethodName,
steps: StepName[],
useLogScale: boolean,
use4SegmentTiming: boolean
use4SegmentTiming: boolean,
allDays: boolean
}

export interface ChartPanelState {
chartData: Record<string, unknown> | null;
isComputing: boolean;
solvesPerPeriod: 'day' | 'week' | 'month';
}

export interface StreakRow {
Expand Down
Loading