diff --git a/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap b/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap
index affad1166..95f0d582c 100644
--- a/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap
+++ b/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap
@@ -704,33 +704,64 @@ exports[`ChartArea Component should allow tick formatting: y tick format 1`] = `
`;
+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,
}
`;
@@ -867,21 +898,13 @@ exports[`ChartArea Component should handle custom chart tooltips: renderTooltip:
"y": -10,
}
}
- labelComponent={
- }
- >
- }
- />
-
- }
+ labelComponent={}
labels={[Function]}
portalComponent={}
portalZIndex={99}
responsive={true}
voronoiDimension="x"
- voronoiPadding={50}
+ voronoiPadding={60}
/>
`;
diff --git a/src/components/chartArea/__tests__/chartArea.test.js b/src/components/chartArea/__tests__/chartArea.test.js
index 8ffb720eb..657647285 100644
--- a/src/components/chartArea/__tests__/chartArea.test.js
+++ b/src/components/chartArea/__tests__/chartArea.test.js
@@ -308,6 +308,7 @@ describe('ChartArea Component', () => {
});
it('should handle custom chart legends', () => {
+ let chartMethods = {};
const props = {
dataSets: [
{
@@ -334,11 +335,26 @@ describe('ChartArea Component', () => {
isThreshold: true
}
],
- chartLegend: propsObj => propsObj.datum
+ chartLegend: propsObj => {
+ chartMethods = propsObj.chart;
+ return propsObj;
+ }
};
const component = shallow();
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', () => {
diff --git a/src/components/chartArea/chartArea.js b/src/components/chartArea/chartArea.js
index a445b66fc..20a8d46b4 100644
--- a/src/components/chartArea/chartArea.js
+++ b/src/components/chartArea/chartArea.js
@@ -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';
@@ -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();
@@ -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.
*
@@ -131,7 +182,6 @@ 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.
*
@@ -139,6 +189,7 @@ class ChartArea extends React.Component {
* @returns {object}
*/
getChartDomain({ isXAxisTicks }) {
+ const { dataSetsToggle } = this;
const { domain, dataSets } = this.props;
if (Object.keys(domain).length) {
@@ -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) => {
@@ -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 => {
@@ -199,6 +250,7 @@ class ChartArea extends React.Component {
* @returns {Array}
*/
getTooltipData() {
+ const { dataSetsToggle } = this;
const { dataSets, chartTooltip } = this.props;
let tooltipDataSet = [];
@@ -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])
@@ -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;
}
@@ -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;
}
@@ -283,7 +336,7 @@ class ChartArea extends React.Component {
if (htmlContent) {
return (
-
+
{htmlContent}
@@ -295,20 +348,13 @@ class ChartArea extends React.Component {
return ;
};
- // FixMe: "flyoutComponent" on ChartTooltip attribute requires very specific format.
- const labelComponent = (
-
- } />
-
- );
-
return (
''}
- labelComponent={labelComponent}
+ labelComponent={}
voronoiDimension="x"
- voronoiPadding={50}
+ voronoiPadding={60}
/>
);
}
@@ -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 })
);
}
@@ -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 = [];
@@ -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) {
diff --git a/src/components/graphCard/__tests__/__snapshots__/graphCardChartLegend.test.js.snap b/src/components/graphCard/__tests__/__snapshots__/graphCardChartLegend.test.js.snap
index a7b8dc3c0..8510fa639 100644
--- a/src/components/graphCard/__tests__/__snapshots__/graphCardChartLegend.test.js.snap
+++ b/src/components/graphCard/__tests__/__snapshots__/graphCardChartLegend.test.js.snap
@@ -1,5 +1,82 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`GraphCardChartLegend Component should handle a click event: click event post 1`] = `
+
+ }
+ isDisabled={false}
+ key="curiosity-button-loremIpsum"
+ onClick={[Function]}
+ onKeyPress={[Function]}
+ tabIndex={0}
+ variant="link"
+>
+ t(curiosity-graph.loremIpsumLabel, [object Object])
+
+`;
+
+exports[`GraphCardChartLegend Component should handle a click event: click event pre 1`] = `
+
+ }
+ isDisabled={false}
+ key="curiosity-button-loremIpsum"
+ onClick={[Function]}
+ onKeyPress={[Function]}
+ tabIndex={0}
+ variant="link"
+>
+ t(curiosity-graph.loremIpsumLabel, [object Object])
+
+`;
+
+exports[`GraphCardChartLegend Component should handle a click event: click event update 1`] = `
+
+ }
+ isDisabled={false}
+ key="curiosity-button-loremIpsum"
+ onClick={[Function]}
+ onKeyPress={[Function]}
+ tabIndex={0}
+ variant="link"
+>
+ t(curiosity-graph.loremIpsumLabel, [object Object])
+
+`;
+
exports[`GraphCardChartLegend Component should handle variations in data when returning legend items: legend item, MISSING tooltip content 1`] = `
}
isDisabled={false}
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -69,13 +148,15 @@ exports[`GraphCardChartLegend Component should handle variations in data when re
className="legend-icon"
style={
Object {
- "backgroundColor": "#ipsum",
+ "backgroundColor": "#000000",
"visibility": "visible",
}
}
/>
}
isDisabled={false}
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -97,6 +178,8 @@ exports[`GraphCardChartLegend Component should handle variations in data when re
/>
}
isDisabled={true}
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -159,6 +242,8 @@ exports[`GraphCardChartLegend Component should render a basic component: basic 1
}
isDisabled={false}
key="curiosity-button-loremIpsum"
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -223,6 +308,8 @@ exports[`GraphCardChartLegend Component should render basic data: data 1`] = `
}
isDisabled={false}
key="curiosity-button-loremIpsum"
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -278,6 +365,8 @@ exports[`GraphCardChartLegend Component should render basic data: data 1`] = `
}
isDisabled={true}
key="curiosity-button-ametConsectetur"
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -337,6 +426,8 @@ exports[`GraphCardChartLegend Component should render basic data: data 1`] = `
}
isDisabled={false}
key="curiosity-button-dolorSit"
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
@@ -396,6 +487,8 @@ exports[`GraphCardChartLegend Component should render basic data: data 1`] = `
}
isDisabled={false}
key="curiosity-button-nonCursus"
+ onClick={[Function]}
+ onKeyPress={[Function]}
tabIndex={0}
variant="link"
>
diff --git a/src/components/graphCard/__tests__/graphCardChartLegend.test.js b/src/components/graphCard/__tests__/graphCardChartLegend.test.js
index 0d55c1fcf..fca15cd1f 100644
--- a/src/components/graphCard/__tests__/graphCardChartLegend.test.js
+++ b/src/components/graphCard/__tests__/graphCardChartLegend.test.js
@@ -1,5 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
+import { Button } from '@patternfly/react-core';
import { GraphCardChartLegend } from '../graphCardChartLegend';
describe('GraphCardChartLegend Component', () => {
@@ -57,11 +58,76 @@ describe('GraphCardChartLegend Component', () => {
expect(component).toMatchSnapshot('data');
});
+ it('should handle a click event', () => {
+ const props = {
+ datum: {
+ dataSets: [
+ {
+ stroke: '#000000',
+ id: 'loremIpsum',
+ isThreshold: false,
+ data: [{ y: 0, hasData: true }]
+ },
+ {
+ stroke: '#ff0000',
+ id: 'dolorSit',
+ isThreshold: true,
+ data: [{ y: 0, isInfinite: false }]
+ }
+ ]
+ },
+ legend: {
+ 'test-dolorSit': true
+ },
+ product: 'test',
+ viewId: 'test'
+ };
+
+ const component = shallow();
+ expect(component.find(Button).first()).toMatchSnapshot('click event pre');
+
+ component.find(Button).first().simulate('click');
+ // emulate a Redux state update.
+ component.setProps({
+ legend: { ...props.legend, ...{ 'test-loremIpsum': true } }
+ });
+ expect(component.find(Button).first()).toMatchSnapshot('click event update');
+
+ component.find(Button).first().simulate('keyPress');
+ // emulate a Redux state update.
+ component.setProps({
+ legend: { ...props.legend, ...{ 'test-loremIpsum': false } }
+ });
+ expect(component.find(Button).first()).toMatchSnapshot('click event post');
+ });
+
it('should handle variations in data when returning legend items', () => {
+ const props = {
+ datum: {
+ dataSets: [
+ {
+ stroke: '#000000',
+ id: 'loremIpsum',
+ isThreshold: false,
+ data: [{ y: 0, hasData: true }]
+ },
+ {
+ stroke: '#ff0000',
+ id: 'dolorSit',
+ isThreshold: true,
+ data: [{ y: 0, isInfinite: false }]
+ }
+ ]
+ },
+ product: 'test'
+ };
+
+ const component = shallow();
+
expect(
- GraphCardChartLegend.renderLegendItem({
- chartId: 'lorem',
- color: '#ipsum',
+ component.instance().renderLegendItem({
+ chartId: 'loremIpsum',
+ color: '#000000',
isDisabled: false,
isThreshold: false,
labelContent: 'lorem ispum',
@@ -70,9 +136,9 @@ describe('GraphCardChartLegend Component', () => {
).toMatchSnapshot('legend item, WITH tooltip content');
expect(
- GraphCardChartLegend.renderLegendItem({
- chartId: 'lorem',
- color: '#ipsum',
+ component.instance().renderLegendItem({
+ chartId: 'loremIpsum',
+ color: '#000000',
isDisabled: false,
isThreshold: false,
labelContent: 'lorem ispum',
@@ -81,9 +147,9 @@ describe('GraphCardChartLegend Component', () => {
).toMatchSnapshot('legend item, MISSING tooltip content');
expect(
- GraphCardChartLegend.renderLegendItem({
- chartId: 'lorem',
- color: '#ipsum',
+ component.instance().renderLegendItem({
+ chartId: 'loremIpsum',
+ color: '#000000',
isDisabled: true,
isThreshold: false,
labelContent: 'lorem ispum',
diff --git a/src/components/graphCard/graphCard.js b/src/components/graphCard/graphCard.js
index 9f25bba93..b0dd7c97c 100644
--- a/src/components/graphCard/graphCard.js
+++ b/src/components/graphCard/graphCard.js
@@ -83,7 +83,7 @@ class GraphCard extends React.Component {
* @returns {Node}
*/
renderChart() {
- const { filterGraphData, graphData, graphQuery, selectOptionsType, productShortLabel } = this.props;
+ const { filterGraphData, graphData, graphQuery, selectOptionsType, productShortLabel, viewId } = this.props;
const graphGranularity = graphQuery && graphQuery[rhsmApiTypes.RHSM_API_QUERY_GRANULARITY];
const { selected } = graphCardTypes.getGranularityOptions(selectOptionsType);
const updatedGranularity = graphGranularity || selected;
@@ -141,7 +141,9 @@ class GraphCard extends React.Component {
key={helpers.generateId()}
{...chartAreaProps}
dataSets={filteredGraphData(graphData)}
- chartLegend={({ datum }) => }
+ chartLegend={({ chart, datum }) => (
+
+ )}
chartTooltip={({ datum }) => (
)}
diff --git a/src/components/graphCard/graphCardChartLegend.js b/src/components/graphCard/graphCardChartLegend.js
index 9a1ab89da..1d8fbb5ba 100644
--- a/src/components/graphCard/graphCardChartLegend.js
+++ b/src/components/graphCard/graphCardChartLegend.js
@@ -1,19 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
import { Button, Tooltip, TooltipPosition } from '@patternfly/react-core';
import { EyeSlashIcon } from '@patternfly/react-icons';
+import { connectTranslate, store, reduxTypes } from '../../redux';
import { helpers } from '../../common';
/**
* A custom chart legend.
*
* @augments React.Component
+ * @fires onClick
*/
class GraphCardChartLegend extends React.Component {
- static renderLegendItem({ chartId, color, isDisabled, isThreshold, labelContent, tooltipContent }) {
+ componentDidMount() {
+ const { chart, datum, legend, viewId } = this.props;
+ datum.dataSets.forEach(({ id }) => {
+ const checkIsToggled = legend[`${viewId}-${id}`] || chart.isToggled(id);
+
+ if (checkIsToggled) {
+ chart.hide(id);
+ }
+ });
+ }
+
+ /**
+ * Toggle legend item and chart.
+ *
+ * @event onClick
+ * @param {string} id
+ */
+ onClick = id => {
+ const { chart, viewId } = this.props;
+ const updatedToggle = chart.toggle(id);
+
+ store.dispatch({
+ type: reduxTypes.graph.SET_GRAPH_LEGEND,
+ legend: {
+ [`${viewId}-${id}`]: updatedToggle
+ }
+ });
+ };
+
+ /**
+ * Return a legend item.
+ *
+ * @param {object} options
+ * @property {string} chartId
+ * @property {string} color
+ * @property {boolean} isDisabled
+ * @property {boolean} isThreshold
+ * @property {string} labelContent
+ * @property {string} tooltipContent
+ * @returns {Node}
+ */
+ renderLegendItem({ chartId, color, isDisabled, isThreshold, labelContent, tooltipContent }) {
+ const { chart, legend, viewId } = this.props;
+ const checkIsToggled = legend[`${viewId}-${chartId}`] || chart.isToggled(chartId);
+
const button = (