-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lib-user: Add BarChart component (#5806)
* 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
Showing
10 changed files
with
656 additions
and
1 deletion.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
packages/lib-user/src/components/shared/BarChart/BarChart.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
101
packages/lib-user/src/components/shared/BarChart/BarChart.mock.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
77
packages/lib-user/src/components/shared/BarChart/BarChart.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.