Some improvements on LineChartCard

This commit is contained in:
Alejandro Celaya 2020-05-30 17:39:08 +02:00
parent 9340512980
commit 1b6028ae6d
3 changed files with 88 additions and 12 deletions

View file

@ -14,7 +14,10 @@ import { reverse } from 'ramda';
import moment from 'moment'; import moment from 'moment';
import { VisitType } from '../types'; import { VisitType } from '../types';
import { fillTheGaps } from '../../utils/helpers/visits'; import { fillTheGaps } from '../../utils/helpers/visits';
import './LineCHartCard.scss'; import './LineChartCard.scss';
import { useToggle } from '../../utils/helpers/hooks';
import { rangeOf } from '../../utils/utils';
import Checkbox from '../../utils/Checkbox';
const propTypes = { const propTypes = {
title: PropTypes.string, title: PropTypes.string,
@ -22,12 +25,19 @@ const propTypes = {
highlightedVisits: PropTypes.arrayOf(VisitType), highlightedVisits: PropTypes.arrayOf(VisitType),
}; };
const steps = [ const STEPS_MAP = {
{ value: 'monthly', menuText: 'Month' }, monthly: 'Month',
{ value: 'weekly', menuText: 'Week' }, weekly: 'Week',
{ value: 'daily', menuText: 'Day' }, daily: 'Day',
{ value: 'hourly', menuText: 'Hour' }, hourly: 'Hour',
]; };
const STEP_TO_DIFF_UNIT_MAP = {
hourly: 'hour',
daily: 'day',
weekly: 'week',
monthly: 'month',
};
const STEP_TO_DATE_FORMAT = { const STEP_TO_DATE_FORMAT = {
hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'), hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'),
@ -49,6 +59,33 @@ const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => {
return acc; return acc;
}, {}); }, {});
const generateLabels = (step, visits) => {
const newerDate = moment(visits[0].date);
const oldestDate = moment(visits[visits.length - 1].date);
const size = newerDate.diff(oldestDate, STEP_TO_DIFF_UNIT_MAP[step]);
return [
STEP_TO_DATE_FORMAT[step](oldestDate),
...rangeOf(size, () => {
const date = oldestDate.add(1, STEP_TO_DIFF_UNIT_MAP[step]);
return STEP_TO_DATE_FORMAT[step](date);
}),
];
};
const generateLabelsAndGroupedVisits = (visits, step, skipNoElements) => {
const groupedVisits = groupVisitsByStep(step, reverse(visits));
if (skipNoElements) {
return [ Object.keys(groupedVisits), groupedVisits ];
}
const labels = generateLabels(step, visits);
return [ labels, fillTheGaps(groupedVisits, labels) ];
};
const generateDataset = (stats, label, color) => ({ const generateDataset = (stats, label, color) => ({
label, label,
data: Object.values(stats), data: Object.values(stats),
@ -59,9 +96,13 @@ const generateDataset = (stats, label, color) => ({
}); });
const LineChartCard = ({ title, visits, highlightedVisits }) => { const LineChartCard = ({ title, visits, highlightedVisits }) => {
const [ step, setStep ] = useState(steps[0].value); const [ step, setStep ] = useState('monthly');
const groupedVisits = useMemo(() => groupVisitsByStep(step, reverse(visits)), [ visits, step ]); const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true);
const labels = useMemo(() => Object.keys(groupedVisits), [ groupedVisits ]);
const [ labels, groupedVisits ] = useMemo(
() => generateLabelsAndGroupedVisits(visits, step, skipNoVisits),
[ visits, step, skipNoVisits ]
);
const groupedHighlighted = useMemo( const groupedHighlighted = useMemo(
() => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels), () => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels),
[ highlightedVisits, step, labels ] [ highlightedVisits, step, labels ]
@ -83,6 +124,15 @@ const LineChartCard = ({ title, visits, highlightedVisits }) => {
ticks: { beginAtZero: true, precision: 0 }, ticks: { beginAtZero: true, precision: 0 },
}, },
], ],
xAxes: [
{
scaleLabel: { display: true, labelString: STEPS_MAP[step] },
},
],
},
tooltips: {
intersect: false,
axis: 'x',
}, },
}; };
@ -96,7 +146,7 @@ const LineChartCard = ({ title, visits, highlightedVisits }) => {
Group by Group by
</DropdownToggle> </DropdownToggle>
<DropdownMenu right> <DropdownMenu right>
{steps.map(({ menuText, value }) => ( {Object.entries(STEPS_MAP).map(([ value, menuText ]) => (
<DropdownItem key={value} active={step === value} onClick={() => setStep(value)}> <DropdownItem key={value} active={step === value} onClick={() => setStep(value)}>
{menuText} {menuText}
</DropdownItem> </DropdownItem>
@ -104,6 +154,11 @@ const LineChartCard = ({ title, visits, highlightedVisits }) => {
</DropdownMenu> </DropdownMenu>
</UncontrolledDropdown> </UncontrolledDropdown>
</div> </div>
<div className="float-right mr-2">
<Checkbox checked={skipNoVisits} onChange={toggleSkipNoVisits}>
<small>Skip dates with no visits</small>
</Checkbox>
</div>
</CardHeader> </CardHeader>
<CardBody className="line-chart-card__body"> <CardBody className="line-chart-card__body">
<Line data={data} options={options} /> <Line data={data} options={options} />

View file

@ -3,8 +3,9 @@ import { shallow } from 'enzyme';
import { CardHeader, DropdownItem } from 'reactstrap'; import { CardHeader, DropdownItem } from 'reactstrap';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import LineChartCard from '../../../src/visits/helpers/LineChartCard'; import LineChartCard from '../../../src/visits/helpers/LineChartCard';
import Checkbox from '../../../src/utils/Checkbox';
describe('<LineCHartCard />', () => { describe('<LineChartCard />', () => {
let wrapper; let wrapper;
const createWrapper = (visits = [], highlightedVisits = []) => { const createWrapper = (visits = [], highlightedVisits = []) => {
wrapper = shallow(<LineChartCard title="Cool title" visits={visits} highlightedVisits={highlightedVisits} />); wrapper = shallow(<LineChartCard title="Cool title" visits={visits} highlightedVisits={highlightedVisits} />);
@ -49,6 +50,15 @@ describe('<LineCHartCard />', () => {
ticks: { beginAtZero: true, precision: 0 }, ticks: { beginAtZero: true, precision: 0 },
}, },
], ],
xAxes: [
{
scaleLabel: { display: true, labelString: 'Month' },
},
],
},
tooltips: {
intersect: false,
axis: 'x',
}, },
}); });
}); });
@ -63,4 +73,15 @@ describe('<LineCHartCard />', () => {
expect(datasets).toHaveLength(expectedLines); expect(datasets).toHaveLength(expectedLines);
}); });
it('includes stats for visits with no dates if selected', () => {
const wrapper = createWrapper([
{ date: '2016-04-01' },
{ date: '2016-01-01' },
]);
expect(wrapper.find(Line).prop('data').labels).toHaveLength(2);
wrapper.find(Checkbox).simulate('change');
expect(wrapper.find(Line).prop('data').labels).toHaveLength(4);
});
}); });