Skip to content

Commit

Permalink
lib-user: Add BarChart component (#5806)
Browse files Browse the repository at this point in the history
* Add ProfileHeader component

* Add ProfileHeader tests

* Refactor from type module

* Remove from lib-user @node-loader/babel

* Add type commonjs to package.json

* Add mobile styling

* Remove duplicated DefaultStory

* Refactor stories with decorator

* Fix props

* Refactors for dev server

* Add ProjectCard component

* Add ProjectCard story

* Update package.json for storybook build

* Add ProjectCard tests

* Add test:ci script

* Refactor storybook setup

* Refactor story decorators

* Update stories, reorganize components

* Refactor ProjectCard per design review

* Add tabIndex to StyledSpacedText

* Remove tabIndex from StyledSpacedText

* Add BarChart component

* Add getDateRangeLabel helper

* Add BarChart stories

* Add BarChart tests

* Refactors for responsiveness

* Add dateRanges map, related refactors

* Refactors for x-axis label granularity

* Fix BarChart test per refactored x-axis granularity

* Add aria-label to BarChart
  • Loading branch information
mcbouslog authored Jan 12, 2024
1 parent a552e55 commit 569ae43
Show file tree
Hide file tree
Showing 10 changed files with 656 additions and 1 deletion.
111 changes: 111 additions & 0 deletions packages/lib-user/src/components/shared/BarChart/BarChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { DataChart, Text } from 'grommet'
import { arrayOf, number, shape, string } from 'prop-types'
import withResponsiveContext from '@zooniverse/react-components/helpers/withResponsiveContext'

import dateRanges from './helpers/dateRanges'
import getDateRangeLabel from './helpers/getDateRangeLabel'

function BarChart ({
data = [],
dateRange = dateRanges.Last7Days,
screenSize = 'small',
type = 'count'
}) {
const dateRangeLabel = getDateRangeLabel(dateRange)
const typeLabel = type === 'count' ? 'Classifications' : 'Time'
const readableDateRange = dateRange
.replace(/([A-Z])/g, ' $1')
.replace(/([0-9]+)/g, ' $1')
.trim()

// set chart options based on screen size and data length
const chartOptions = {
color: 'brand',
property: type,
type: 'bar'
}
if (screenSize !== 'small' && data.length < 9) {
chartOptions.thickness = 'xlarge'
}
if (screenSize === 'small') {
if (data.length < 12) {
chartOptions.thickness = 'small'
} else if (data.length > 11 && data.length < 19) {
chartOptions.thickness = 'xsmall'
} else {
chartOptions.thickness = 'hair'
}
}

// set x axis granularity based on data length
let xAxisGranularity = 'fine'
if (data.length > 12) {
xAxisGranularity = 'medium'
}

return (
<DataChart
a11yTitle={`Bar chart of ${typeLabel} by ${dateRangeLabel.countLabel} for ${readableDateRange}`}
axis={{
x: { granularity: xAxisGranularity, property: 'period' },
y: { granularity: 'fine', property: type },
}}
chart={chartOptions}
data={data}
detail
guide={{
y: {
granularity: 'fine'
}
}}
series={[
{
property: 'period',
label: dateRangeLabel.countLabel,
render: (period) => {
const date = new Date(period)
return (
<Text data-testid='periodLabel' textAlign='center'>
{date.toLocaleDateString('en-US', dateRangeLabel.tLDS)}
</Text>
)
},
},
{
property: type,
label: typeLabel,
render: ((number) => {
if (type === 'session_time') {
const time = number / dateRangeLabel.time
return (
<Text data-testid='timeLabel'>
{`${time.toFixed(0)} ${dateRangeLabel.timeLabel}`}
</Text>
)
} else {
return (
<Text data-testid='countLabel'>
{new Number(number).toLocaleString()}
</Text>
)
}
}),
},
]}
size='fill'
/>
)
}

BarChart.propTypes = {
data: arrayOf(shape({
period: string,
count: number,
session_time: number
})),
dateRange: string,
screenSize: string,
type: string
}

export default withResponsiveContext(BarChart)
101 changes: 101 additions & 0 deletions packages/lib-user/src/components/shared/BarChart/BarChart.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const last7days = [
{
"period": "2023-05-27T00:00:00Z",
"count": 2468,
"session_time": 3234
},
{
"period": "2023-05-28T00:00:00Z",
"count": 1234,
"session_time": 1678
},
{
"period": "2023-05-29T00:00:00Z",
"count": 1456,
"session_time": 2432
},
{
"period": "2023-05-30T00:00:00Z",
"count": 1000,
"session_time": 2333
},
{
"period": "2023-05-31T00:00:00Z",
"count": 235,
"session_time": 1346
},
{
"period": "2023-06-01T00:00:00Z",
"count": 467,
"session_time": 1234
},
{
"period": "2023-06-02T00:00:00Z",
"count": 1876,
"session_time": 3234
}
]

const last30days = Array.from(
{ length: 30 },
(_, i) => ({
period: new Date(2023, 4, 7 + i).toISOString(),
count: Math.floor(Math.random() * 3000),
session_time: Math.floor(Math.random() * 5400),
})
)

const thisMonth = Array.from(
{ length: 19 },
(_, i) => ({
period: new Date(2023, 6, 1 + i).toISOString(),
count: Math.floor(Math.random() * 3000),
session_time: Math.floor(Math.random() * 5400),
})
)

const last3months = Array.from(
{ length: 12 },
(_, i) => ({
period: new Date(2023, 4, 1 + (i * 7)).toISOString(),
count: Math.floor(Math.random() * 21000),
session_time: Math.floor(Math.random() * 37800),
})
)

const thisYear = Array.from(
{ length: 9 },
(_, i) => ({
period: new Date(2023, i, 1).toISOString(),
count: Math.floor(Math.random() * 84000),
session_time: Math.floor(Math.random() * 151200),
})
)

const last12months = Array.from(
{ length: 12 },
(_, i) => ({
period: new Date(2022, 9 + i, 1).toISOString(),
count: Math.floor(Math.random() * 84000),
session_time: Math.floor(Math.random() * 151200),
})
)

const allTime = Array.from(
{ length: 9 },
(_, i) => ({
period: new Date(2015 + i, 0, 1).toISOString(),
count: Math.floor(Math.random() * 200000),
session_time: Math.floor(Math.random() * 302400),
})
)

export {
last7days,
last30days,
thisMonth,
last3months,
thisYear,
last12months,
allTime
}
77 changes: 77 additions & 0 deletions packages/lib-user/src/components/shared/BarChart/BarChart.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { render, screen } from '@testing-library/react'
import { composeStory } from '@storybook/react'

import Meta, { Default, Last30DaysHours, Last3Months, Last12MonthsHours } from './BarChart.stories.js'

describe('components > shared > BarChart', function () {

describe('with Last7Days', function () {
const DefaultStory = composeStory(Default, Meta)

it('should have the expected aria-label', function () {
render(<DefaultStory />)

expect(screen.getByLabelText('Bar chart of Classifications by Day for Last 7 Days')).to.be.ok()
})

it('should show the expected number of count labels', function () {
render(<DefaultStory />)

expect(screen.getAllByTestId('countLabel')).to.have.lengthOf(5)
})

it('should show the expected number of period labels', function () {
render(<DefaultStory />)

expect(screen.getAllByTestId('periodLabel')).to.have.lengthOf(7)
})
})

describe('with Last30Days and Hours', function () {
const Last30DaysHoursStory = composeStory(Last30DaysHours, Meta)

it('should show the expected number of time labels', function () {
render(<Last30DaysHoursStory />)

expect(screen.getAllByTestId('timeLabel')).to.have.lengthOf(5)
})

it('should show the expected number of period labels', function () {
render(<Last30DaysHoursStory />)

expect(screen.getAllByTestId('periodLabel')).to.have.lengthOf(2)
})
})

describe('with Last3Months', function () {
const Last3MonthsStory = composeStory(Last3Months, Meta)

it('should show the expected number of count labels', function () {
render(<Last3MonthsStory />)

expect(screen.getAllByTestId('countLabel')).to.have.lengthOf(5)
})

it('should show the expected number of period labels', function () {
render(<Last3MonthsStory />)

expect(screen.getAllByTestId('periodLabel')).to.have.lengthOf(12)
})
})

describe('with Last12Months and Hours', function () {
const Last12MonthsHoursStory = composeStory(Last12MonthsHours, Meta)

it('should show the expected number of time labels', function () {
render(<Last12MonthsHoursStory />)

expect(screen.getAllByTestId('timeLabel')).to.have.lengthOf(5)
})

it('should show the expected number of period labels', function () {
render(<Last12MonthsHoursStory />)

expect(screen.getAllByTestId('periodLabel')).to.have.lengthOf(12)
})
})
})
Loading

0 comments on commit 569ae43

Please sign in to comment.