Ensured info for selected visit in visits table gets highlighted in bar charts

This commit is contained in:
Alejandro Celaya 2020-04-04 20:16:20 +02:00
parent bd4255108d
commit f5cc1abe75
5 changed files with 56 additions and 15 deletions

View file

@ -2,7 +2,7 @@ import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap';
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
import PropTypes from 'prop-types';
import React from 'react';
import { keys, values } from 'ramda';
import { keys, values, zipObj } from 'ramda';
import './GraphCard.scss';
const propTypes = {
@ -11,9 +11,10 @@ const propTypes = {
isBarChart: PropTypes.bool,
stats: PropTypes.object,
max: PropTypes.number,
highlightedStats: PropTypes.object,
};
const generateGraphData = (title, isBarChart, labels, data) => ({
const generateGraphData = (title, isBarChart, labels, data, highlightedData) => ({
labels,
datasets: [
{
@ -31,23 +32,41 @@ const generateGraphData = (title, isBarChart, labels, data) => ({
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
borderWidth: 2,
},
],
highlightedData && {
title,
label: 'Selected',
data: highlightedData,
backgroundColor: 'rgba(247, 127, 40, 0.4)',
borderColor: '#F77F28',
borderWidth: 2,
},
].filter(Boolean),
});
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
const renderGraph = (title, isBarChart, stats, max) => {
const renderGraph = (title, isBarChart, stats, max, highlightedStats) => {
const Component = isBarChart ? HorizontalBar : Doughnut;
const labels = keys(stats).map(dropLabelIfHidden);
const data = values(stats);
const data = values(!highlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => {
if (acc[highlightedKey]) {
acc[highlightedKey] -= 1;
}
return acc;
}, stats));
const highlightedData = highlightedStats && values({ ...zipObj(labels, labels.map(() => 0)), ...highlightedStats });
const options = {
legend: isBarChart ? { display: false } : { position: 'right' },
scales: isBarChart && {
xAxes: [
{
ticks: { beginAtZero: true, max },
stacked: true,
},
],
yAxes: [{ stacked: true }],
},
tooltips: {
intersect: !isBarChart,
@ -56,17 +75,17 @@ const renderGraph = (title, isBarChart, stats, max) => {
filter: ({ yLabel }) => !isBarChart || yLabel !== '',
},
};
const graphData = generateGraphData(title, isBarChart, labels, data);
const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData);
const height = isBarChart && labels.length > 20 ? labels.length * 8 : null;
// Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
return <Component key={height} data={graphData} options={options} height={height} />;
};
const GraphCard = ({ title, footer, isBarChart, stats, max }) => (
const GraphCard = ({ title, footer, isBarChart, stats, max, highlightedStats }) => (
<Card className="mt-4">
<CardHeader className="graph-card__header">{typeof title === 'function' ? title() : title}</CardHeader>
<CardBody>{renderGraph(title, isBarChart, stats, max)}</CardBody>
<CardBody>{renderGraph(title, isBarChart, stats, max, highlightedStats)}</CardBody>
{footer && <CardFooter className="graph-card__footer--sticky">{footer}</CardFooter>}
</Card>
);

View file

@ -15,6 +15,8 @@ import GraphCard from './GraphCard';
import { shortUrlDetailType } from './reducers/shortUrlDetail';
import VisitsTable from './VisitsTable';
const highlightedVisitToStats = (highlightedVisit, prop) => highlightedVisit && { [highlightedVisit[prop]]: 1 };
const ShortUrlVisits = (
{ processStatsFromVisits },
OpenMapModalBtn
@ -40,6 +42,7 @@ const ShortUrlVisits = (
showTable: false,
tableIsSticky: false,
isMobileDevice: false,
highlightedVisit: undefined,
};
loadVisits = (loadDetail = false) => {
@ -114,9 +117,10 @@ const ShortUrlVisits = (
</div>
<div className="col-xl-4">
<SortableBarGraph
title="Referrers"
stats={referrers}
withPagination={false}
title="Referrers"
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'referer')}
sortingItems={{
name: 'Referrer name',
amount: 'Visits amount',
@ -125,8 +129,9 @@ const ShortUrlVisits = (
</div>
<div className="col-lg-6">
<SortableBarGraph
stats={countries}
title="Countries"
stats={countries}
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'country')}
sortingItems={{
name: 'Country name',
amount: 'Visits amount',
@ -135,8 +140,9 @@ const ShortUrlVisits = (
</div>
<div className="col-lg-6">
<SortableBarGraph
stats={cities}
title="Cities"
stats={cities}
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'city')}
extraHeaderContent={(activeCities) =>
mapLocations.length > 0 &&
<OpenMapModalBtn modalTitle="Cities" locations={mapLocations} activeCities={activeCities} />
@ -188,7 +194,11 @@ const ShortUrlVisits = (
onEntered={() => this.setState({ tableIsSticky: true })}
onExiting={() => this.setState({ tableIsSticky: false })}
>
<VisitsTable visits={visits} isSticky={this.state.tableIsSticky} />
<VisitsTable
visits={visits}
isSticky={this.state.tableIsSticky}
onVisitSelected={(highlightedVisit) => this.setState({ highlightedVisit })}
/>
</Collapse>
)}

View file

@ -15,6 +15,7 @@ const pickValueFromPair = ([ , value ]) => value;
export default class SortableBarGraph extends React.Component {
static propTypes = {
stats: PropTypes.object.isRequired,
highlightedStats: PropTypes.object,
title: PropTypes.string.isRequired,
sortingItems: PropTypes.object.isRequired,
extraHeaderContent: PropTypes.func,
@ -73,7 +74,7 @@ export default class SortableBarGraph extends React.Component {
}
render() {
const { stats, sortingItems, title, extraHeaderContent, withPagination = true } = this.props;
const { stats, sortingItems, title, extraHeaderContent, highlightedStats, withPagination = true } = this.props;
const { currentPageStats, pagination, max } = this.determineStats(stats, sortingItems);
const activeCities = keys(currentPageStats);
const computeTitle = () => (
@ -107,6 +108,15 @@ export default class SortableBarGraph extends React.Component {
</React.Fragment>
);
return <GraphCard isBarChart title={computeTitle} stats={currentPageStats} footer={pagination} max={max} />;
return (
<GraphCard
isBarChart
title={computeTitle}
stats={currentPageStats}
footer={pagination}
max={max}
highlightedStats={highlightedStats}
/>
);
}
}

View file

@ -98,7 +98,7 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w
'visits-table__sticky': isSticky,
})}
>
<FontAwesomeIcon icon={checkIcon} />
<FontAwesomeIcon icon={checkIcon} className={classNames({ 'text-primary': selectedVisit !== undefined })} />
</th>
<th className={headerCellsClass} onClick={orderByColumn('date')}>
Date

View file

@ -59,8 +59,10 @@ describe('<GraphCard />', () => {
xAxes: [
{
ticks: { beginAtZero: true },
stacked: true,
},
],
yAxes: [{ stacked: true }],
});
});
});