mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #272 from acelaya-forks/feature/line-chart-improvements
Some improvements on LineChartCard
This commit is contained in:
commit
daf9e7cf64
3 changed files with 86 additions and 12 deletions
|
@ -14,7 +14,10 @@ import { reverse } from 'ramda';
|
|||
import moment from 'moment';
|
||||
import { VisitType } from '../types';
|
||||
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 = {
|
||||
title: PropTypes.string,
|
||||
|
@ -22,12 +25,19 @@ const propTypes = {
|
|||
highlightedVisits: PropTypes.arrayOf(VisitType),
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{ value: 'monthly', menuText: 'Month' },
|
||||
{ value: 'weekly', menuText: 'Week' },
|
||||
{ value: 'daily', menuText: 'Day' },
|
||||
{ value: 'hourly', menuText: 'Hour' },
|
||||
];
|
||||
const STEPS_MAP = {
|
||||
monthly: 'Month',
|
||||
weekly: 'Week',
|
||||
daily: 'Day',
|
||||
hourly: 'Hour',
|
||||
};
|
||||
|
||||
const STEP_TO_DATE_UNIT_MAP = {
|
||||
hourly: 'hour',
|
||||
daily: 'day',
|
||||
weekly: 'week',
|
||||
monthly: 'month',
|
||||
};
|
||||
|
||||
const STEP_TO_DATE_FORMAT = {
|
||||
hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'),
|
||||
|
@ -49,6 +59,31 @@ const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const generateLabels = (step, visits) => {
|
||||
const unit = STEP_TO_DATE_UNIT_MAP[step];
|
||||
const formatter = STEP_TO_DATE_FORMAT[step];
|
||||
const newerDate = moment(visits[0].date);
|
||||
const oldestDate = moment(visits[visits.length - 1].date);
|
||||
const size = newerDate.diff(oldestDate, unit);
|
||||
|
||||
return [
|
||||
formatter(oldestDate),
|
||||
...rangeOf(size, () => formatter(oldestDate.add(1, unit))),
|
||||
];
|
||||
};
|
||||
|
||||
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) => ({
|
||||
label,
|
||||
data: Object.values(stats),
|
||||
|
@ -59,9 +94,13 @@ const generateDataset = (stats, label, color) => ({
|
|||
});
|
||||
|
||||
const LineChartCard = ({ title, visits, highlightedVisits }) => {
|
||||
const [ step, setStep ] = useState(steps[0].value);
|
||||
const groupedVisits = useMemo(() => groupVisitsByStep(step, reverse(visits)), [ visits, step ]);
|
||||
const labels = useMemo(() => Object.keys(groupedVisits), [ groupedVisits ]);
|
||||
const [ step, setStep ] = useState('monthly');
|
||||
const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true);
|
||||
|
||||
const [ labels, groupedVisits ] = useMemo(
|
||||
() => generateLabelsAndGroupedVisits(visits, step, skipNoVisits),
|
||||
[ visits, step, skipNoVisits ]
|
||||
);
|
||||
const groupedHighlighted = useMemo(
|
||||
() => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels),
|
||||
[ highlightedVisits, step, labels ]
|
||||
|
@ -83,6 +122,15 @@ const LineChartCard = ({ title, visits, highlightedVisits }) => {
|
|||
ticks: { beginAtZero: true, precision: 0 },
|
||||
},
|
||||
],
|
||||
xAxes: [
|
||||
{
|
||||
scaleLabel: { display: true, labelString: STEPS_MAP[step] },
|
||||
},
|
||||
],
|
||||
},
|
||||
tooltips: {
|
||||
intersect: false,
|
||||
axis: 'x',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -96,7 +144,7 @@ const LineChartCard = ({ title, visits, highlightedVisits }) => {
|
|||
Group by
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right>
|
||||
{steps.map(({ menuText, value }) => (
|
||||
{Object.entries(STEPS_MAP).map(([ value, menuText ]) => (
|
||||
<DropdownItem key={value} active={step === value} onClick={() => setStep(value)}>
|
||||
{menuText}
|
||||
</DropdownItem>
|
||||
|
@ -104,6 +152,11 @@ const LineChartCard = ({ title, visits, highlightedVisits }) => {
|
|||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
<div className="float-right mr-2">
|
||||
<Checkbox checked={skipNoVisits} onChange={toggleSkipNoVisits}>
|
||||
<small>Skip dates with no visits</small>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardBody className="line-chart-card__body">
|
||||
<Line data={data} options={options} />
|
||||
|
|
|
@ -3,8 +3,9 @@ import { shallow } from 'enzyme';
|
|||
import { CardHeader, DropdownItem } from 'reactstrap';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import LineChartCard from '../../../src/visits/helpers/LineChartCard';
|
||||
import Checkbox from '../../../src/utils/Checkbox';
|
||||
|
||||
describe('<LineCHartCard />', () => {
|
||||
describe('<LineChartCard />', () => {
|
||||
let wrapper;
|
||||
const createWrapper = (visits = [], highlightedVisits = []) => {
|
||||
wrapper = shallow(<LineChartCard title="Cool title" visits={visits} highlightedVisits={highlightedVisits} />);
|
||||
|
@ -49,6 +50,15 @@ describe('<LineCHartCard />', () => {
|
|||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue