Skip to content

Commit

Permalink
feat(chartArea,graphCard): issues/283 interactive legend (#299)
Browse files Browse the repository at this point in the history
* chartArea, expose chart methods for interactivity, redraw
* graphCard, pass chart methods to legend
* graphCardChartLegend, apply redux state, add button click
* graphReducer, apply legend toggle state
* reduxTypes, graphTypes, set legend type
* styling, allow button color
  • Loading branch information
cdcabrera committed Jun 16, 2020
1 parent 066bce8 commit 4c862ce
Show file tree
Hide file tree
Showing 15 changed files with 495 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -704,33 +704,64 @@ exports[`ChartArea Component should allow tick formatting: y tick format 1`] = `
</div>
`;

exports[`ChartArea Component should handle custom chart legends: hide state 1`] = `
Object {
"loremGraph": true,
}
`;

exports[`ChartArea Component should handle custom chart legends: isToggled state 1`] = `
Object {
"returnedToggleValue": true,
"state": Object {
"loremGraph": true,
},
}
`;

exports[`ChartArea Component should handle custom chart legends: renderLegend: should return a custom legend 1`] = `
Object {
"dataSets": Array [
Object {
"data": Array [
Object {
"x": 1,
"xAxisLabel": "1 x axis label",
"y": 0,
},
],
"id": "loremGraph",
"interpolation": "natural",
"isStacked": true,
},
Object {
"data": Array [
Object {
"x": 1,
"xAxisLabel": "1 x axis label",
"y": 10,
},
],
"id": "ipsumGraph",
"isThreshold": true,
},
],
"chart": Object {
"hide": [Function],
"isToggled": [Function],
"revert": [Function],
"toggle": [Function],
},
"datum": Object {
"dataSets": Array [
Object {
"data": Array [
Object {
"x": 1,
"xAxisLabel": "1 x axis label",
"y": 0,
},
],
"id": "loremGraph",
"interpolation": "natural",
"isStacked": true,
},
Object {
"data": Array [
Object {
"x": 1,
"xAxisLabel": "1 x axis label",
"y": 10,
},
],
"id": "ipsumGraph",
"isThreshold": true,
},
],
},
}
`;

exports[`ChartArea Component should handle custom chart legends: revert state 1`] = `Object {}`;

exports[`ChartArea Component should handle custom chart legends: toggle state 1`] = `
Object {
"loremGraph": false,
}
`;

Expand Down Expand Up @@ -867,21 +898,13 @@ exports[`ChartArea Component should handle custom chart tooltips: renderTooltip:
"y": -10,
}
}
labelComponent={
<VictoryPortal
groupComponent={<g />}
>
<ChartTooltip
flyoutComponent={<FlyoutComponent />}
/>
</VictoryPortal>
}
labelComponent={<FlyoutComponent />}
labels={[Function]}
portalComponent={<Portal />}
portalZIndex={99}
responsive={true}
voronoiDimension="x"
voronoiPadding={50}
voronoiPadding={60}
/>
`;

Expand Down
18 changes: 17 additions & 1 deletion src/components/chartArea/__tests__/chartArea.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ describe('ChartArea Component', () => {
});

it('should handle custom chart legends', () => {
let chartMethods = {};
const props = {
dataSets: [
{
Expand All @@ -334,11 +335,26 @@ describe('ChartArea Component', () => {
isThreshold: true
}
],
chartLegend: propsObj => propsObj.datum
chartLegend: propsObj => {
chartMethods = propsObj.chart;
return propsObj;
}
};

const component = shallow(<ChartArea {...props} />);
expect(component.instance().renderLegend()).toMatchSnapshot('renderLegend: should return a custom legend');

chartMethods.hide('loremGraph');
expect(component.instance().dataSetsToggle).toMatchSnapshot('hide state');

const returnedToggleValue = chartMethods.isToggled('loremGraph');
expect({ state: component.instance().dataSetsToggle, returnedToggleValue }).toMatchSnapshot('isToggled state');

chartMethods.toggle('loremGraph');
expect(component.instance().dataSetsToggle).toMatchSnapshot('toggle state');

chartMethods.revert();
expect(component.instance().dataSetsToggle).toMatchSnapshot('revert state');
});

it('should set initial width to zero and then resize', () => {
Expand Down
101 changes: 77 additions & 24 deletions src/components/chartArea/chartArea.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { createContainer, VictoryPortal } from 'victory';
import { createContainer } from 'victory';
import {
Chart,
ChartAxis,
ChartStack,
ChartThreshold,
ChartTooltip,
ChartThemeColor,
ChartArea as PfChartArea
} from '@patternfly/react-charts';
Expand All @@ -18,10 +17,16 @@ import { helpers } from '../../common';
*
* @augments React.Component
* @fires onResizeContainer
* @fires onHide
* @fires onRevert
* @fires onToggle
* @param id
*/
class ChartArea extends React.Component {
state = { chartWidth: 0 };

dataSetsToggle = {};

containerRef = React.createRef();

tooltipRef = React.createRef();
Expand All @@ -48,6 +53,52 @@ class ChartArea extends React.Component {
}
};

/**
* Consumer exposed, hides chart layer.
*
* @event onHide
* @param {string} id
*/
onHide = id => {
this.dataSetsToggle = { ...this.dataSetsToggle, [id]: true };
this.forceUpdate();
};

/**
* Consumer exposed, turns all chart layers back on.
*
* @event onRevert
*/
onRevert = () => {
this.dataSetsToggle = {};
this.forceUpdate();
};

/**
* Consumer exposed, turns chart layer on/off.
*
* @event onToggle
* @param {string} id
* @returns {boolean}
*/
onToggle = id => {
const updatedToggle = !this.dataSetsToggle[id];
this.dataSetsToggle = { ...this.dataSetsToggle, [id]: updatedToggle };
this.forceUpdate();

return updatedToggle;
};

/**
* Consumer exposed, determine if chart layer on/off.
* Note: Using "setState" as related to this exposed check gives the appearance of a race condition.
* Using a class property with forceUpdate to bypass.
*
* @param {string} id
* @returns {boolean}
*/
getIsToggled = id => this.dataSetsToggle[id] || false;

/**
* Apply props, set x and y axis chart increments/ticks formatting.
*
Expand Down Expand Up @@ -131,14 +182,14 @@ class ChartArea extends React.Component {
};
}

// ToDo: the domain range needs to be updated when additional datasets are added
/**
* Calculate and return the x and y domain range.
*
* @param {boolean} isXAxisTicks
* @returns {object}
*/
getChartDomain({ isXAxisTicks }) {
const { dataSetsToggle } = this;
const { domain, dataSets } = this.props;

if (Object.keys(domain).length) {
Expand All @@ -153,7 +204,7 @@ class ChartArea extends React.Component {
const stackedSets = dataSets.filter(set => set.isStacked === true);

stackedSets.forEach(dataSet => {
if (dataSet.data) {
if (!dataSetsToggle[dataSet.id] && dataSet.data) {
let dataSetMaxYStacked = 0;

dataSet.data.forEach((value, index) => {
Expand All @@ -167,7 +218,7 @@ class ChartArea extends React.Component {
});

dataSets.forEach(dataSet => {
if (dataSet.data) {
if (!dataSetsToggle[dataSet.id] && dataSet.data) {
dataSetMaxX = dataSet.data.length > dataSetMaxX ? dataSet.data.length : dataSetMaxX;

dataSet.data.forEach(value => {
Expand Down Expand Up @@ -199,6 +250,7 @@ class ChartArea extends React.Component {
* @returns {Array}
*/
getTooltipData() {
const { dataSetsToggle } = this;
const { dataSets, chartTooltip } = this.props;
let tooltipDataSet = [];

Expand All @@ -207,7 +259,7 @@ class ChartArea extends React.Component {
const itemsByKey = {};

dataSets.forEach(data => {
if (data.data && data.data[index]) {
if (!dataSetsToggle[data.id] && data.data && data.data[index]) {
itemsByKey[data.id] = {
color: data.stroke || data.fill || data.color || '',
data: _cloneDeep(data.data[index])
Expand Down Expand Up @@ -239,9 +291,10 @@ class ChartArea extends React.Component {
* @returns {Node}
*/
renderTooltip() {
const { chartTooltip } = this.props;
const { dataSetsToggle } = this;
const { chartTooltip, dataSets } = this.props;

if (!chartTooltip) {
if (!chartTooltip || Object.values(dataSetsToggle).filter(v => v === true).length === dataSets.length) {
return null;
}

Expand Down Expand Up @@ -273,7 +326,7 @@ class ChartArea extends React.Component {

if (
globalContainerBounds.right < globalTooltipBounds.right ||
obj.x + globalTooltipBounds.width * 3 > globalContainerBounds.right
obj.x + globalTooltipBounds.width > globalContainerBounds.right / 2
) {
xCoordinate = obj.x - 10 - globalTooltipBounds.width;
}
Expand All @@ -283,7 +336,7 @@ class ChartArea extends React.Component {
if (htmlContent) {
return (
<g>
<foreignObject x={xCoordinate} y={obj.y / 1.3} width="100%" height="100%">
<foreignObject x={xCoordinate} y={obj.y / 2} width="100%" height="100%">
<div ref={this.tooltipRef} style={{ display: 'inline-block' }} xmlns="http://www.w3.org/1999/xhtml">
{htmlContent}
</div>
Expand All @@ -295,20 +348,13 @@ class ChartArea extends React.Component {
return <g />;
};

// FixMe: "flyoutComponent" on ChartTooltip attribute requires very specific format.
const labelComponent = (
<VictoryPortal>
<ChartTooltip flyoutComponent={<FlyoutComponent />} />
</VictoryPortal>
);

return (
<VictoryVoronoiCursorContainer
cursorDimension="x"
labels={() => ''}
labelComponent={labelComponent}
labelComponent={<FlyoutComponent />}
voronoiDimension="x"
voronoiPadding={50}
voronoiPadding={60}
/>
);
}
Expand All @@ -325,13 +371,19 @@ class ChartArea extends React.Component {
return null;
}

const mockDatum = {
datum: { dataSets: _cloneDeep(dataSets) }
const legendProps = {
datum: { dataSets: _cloneDeep(dataSets) },
chart: {
hide: this.onHide,
revert: this.onRevert,
toggle: this.onToggle,
isToggled: this.getIsToggled
}
};

return (
(React.isValidElement(chartLegend) && React.cloneElement(chartLegend, { ...mockDatum })) ||
chartLegend({ ...mockDatum })
(React.isValidElement(chartLegend) && React.cloneElement(chartLegend, { ...legendProps })) ||
chartLegend({ ...legendProps })
);
}

Expand All @@ -342,6 +394,7 @@ class ChartArea extends React.Component {
* @returns {Array}
*/
renderChart({ stacked = false }) {
const { dataSetsToggle } = this;
const { dataSets } = this.props;
const charts = [];
const chartsStacked = [];
Expand Down Expand Up @@ -407,7 +460,7 @@ class ChartArea extends React.Component {
};

dataSets.forEach((dataSet, index) => {
if (dataSet.data && dataSet.data.length) {
if (!dataSetsToggle[dataSet.id] && dataSet.data && dataSet.data.length) {
const updatedDataSet = (dataSet.isThreshold && thresholdChart(dataSet, index)) || areaChart(dataSet, index);

if (dataSet.isStacked) {
Expand Down
Loading

0 comments on commit 4c862ce

Please sign in to comment.