diff --git a/src/components/logbook/ChartsSection.tsx b/src/components/logbook/ChartsSection.tsx new file mode 100644 index 000000000..4e68b1c42 --- /dev/null +++ b/src/components/logbook/ChartsSection.tsx @@ -0,0 +1,17 @@ +import { TickType } from '../../js/types' +import DifficultyPyramid from './DifficultyPyramid' +import OverviewChart from './OverviewChart' + +export interface ChartsSectionProps { + tickList: TickType[] +} +const ChartsSection: React.FC = ({ tickList }) => { + return ( +
+ + +
+ ) +} + +export default ChartsSection diff --git a/src/components/logbook/DifficultyPyramid.tsx b/src/components/logbook/DifficultyPyramid.tsx new file mode 100644 index 000000000..fedb19abb --- /dev/null +++ b/src/components/logbook/DifficultyPyramid.tsx @@ -0,0 +1,91 @@ +import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, CartesianGrid } from 'recharts' +import { getScale } from '@openbeta/sandbag' + +import { TickType } from '../../js/types' +import { maxSorted } from 'simple-statistics' +/** + * Assume grades are YDS or Vscale for now since we don't store + * grade context with ticks, nor do we have a way to get score + * without knowing the grade system + */ +export const ydsScale = getScale('yds') +export const vScale = getScale('vscale') + +interface DifficultyPyramidProps { + tickList: TickType[] +} + +const DifficultyPyramid: React.FC = ({ tickList }) => { + const gradeHistogram = new Map() + + if (tickList == null || tickList.length < 1) return null + + tickList.forEach((tick) => { + const score = getScoreUSAForRouteAndBoulder(tick.grade) + if (score > 0) { + const count = gradeHistogram.get(score) + if (count == null) { + gradeHistogram.set(score, 0) + } else { + gradeHistogram.set(score, count + 1) + } + } + }) + + const sortedKeys = Array.from(gradeHistogram.keys()).sort((a, b) => a - b) + const sortedValues = Array.from(gradeHistogram.values()).sort((a, b) => a - b) + const yOffset = maxSorted(sortedValues) + + const chartData = sortedKeys.map(key => { + const value = gradeHistogram.get(key) ?? 0 + return ({ + x: key, + xBottom: key, + hackRange: [value + yOffset, -value + yOffset] + }) + }) + return ( +
+

+ Difficulty Pyramid +

+ + + + + { + if (value == null) return '' + const yds = ydsScale?.getGrade(parseInt(value)) ?? '' + const vscale = vScale?.getGrade(parseInt(value)) ?? '' + return `${yds}/${vscale}` + }} + /> + + { + const actual = parseInt(value) - yOffset + return `${actual > 0 ? actual : ''}` + }} + /> + + + + +
+ ) +} + +export default DifficultyPyramid + +const getScoreUSAForRouteAndBoulder = (grade: string): number => { + let score = ydsScale?.getScore(grade)[0] as number ?? -1 + if (score < 0) { + score = vScale?.getScore(grade)[0] as number ?? -1 + } + return score +} diff --git a/src/components/logbook/OverviewChart.tsx b/src/components/logbook/OverviewChart.tsx index fd1a2fddb..5d7b3b4f0 100644 --- a/src/components/logbook/OverviewChart.tsx +++ b/src/components/logbook/OverviewChart.tsx @@ -4,10 +4,10 @@ import { } from 'recharts' import { groupBy } from 'underscore' import { lastDayOfMonth, format } from 'date-fns' -import { getScale } from '@openbeta/sandbag' import { linearRegression, linearRegressionLine, minSorted, maxSorted, medianSorted } from 'simple-statistics' import { TickType } from '../../js/types' +import { ydsScale, vScale } from './DifficultyPyramid' export interface OverviewChartProps { tickList: TickType[] @@ -17,13 +17,7 @@ export interface OverviewChartProps { * Proof of concept chart showing climbs aggregated by a time interval */ const OverviewChart: React.FC = ({ tickList }) => { - /** - * Assume grades are YDS or Vscale for now since we don't store - * grade context with ticks, nor do we have a way to get score - * without knowing the grade system - */ - const ydsScale = getScale('yds') - const vScale = getScale('vscale') + if (tickList == null || tickList.length < 1) return null const agg = groupBy(tickList, getYearMonthFromDate) @@ -72,6 +66,9 @@ const OverviewChart: React.FC = ({ tickList }) => { return (
+

+ Climb History +

= ({ username, ticks }) => { contentContainerClass='content-default with-standard-y-margin' showFilterBar={false} > -
- -
+ +

{username}

@@ -88,8 +87,8 @@ export const getStaticProps: GetStaticProps( +const ChartsSection = dynamic( async () => - await import('../../components/logbook/OverviewChart').then( + await import('../../components/logbook/ChartsSection').then( module => module.default), { ssr: false } )