Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Series Charts] Provide custom crosshair overlay time formatting #1429

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ No public interface changes since `6.3.0`.

## [`6.3.0`](https://github.com/elastic/eui/tree/v6.3.0)

- Added custom date string formatting for series charts crosshair overlay ([#1429](https://github.com/elastic/eui/pull/1429))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\This changelog entry is now out of date, needs to be moved back to the top

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 7968372.

- Added `onBlur` prop to `EuiComboBox` ([#1400](https://github.com/elastic/eui/pull/1400))
- Added `initialFocus` prop typedefs to `EuiModal` and `EuiPopover` ([#1410](https://github.com/elastic/eui/pull/1410))
- Updated `gisApp` icon ([#1413](https://github.com/elastic/eui/pull/1413))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import {
EuiSeriesChart,
EuiHistogramSeries,
EuiSeriesChartUtils,
} from '../../../../src/experimental';

const { SCALE } = EuiSeriesChartUtils;
let timeseriesX = Date.now();
const HOUR = 1000 * 60 * 60;

const histogramData = new Array(1000).fill(0).map(() => {
const x0 = timeseriesX;
timeseriesX += HOUR;
const x = timeseriesX;
const y = Math.floor(Math.random() * 100);
return { x0, x, y };
});

export const FormatCrosshairTimesExample = () => (
<div>
<EuiSeriesChart height={200} xType={SCALE.TIME}>
<EuiHistogramSeries yDomain={[0, 100]} name="Chart Name" xCrosshairFormat="YYYY-MM-DD hh:mmZ" data={histogramData} />
</EuiSeriesChart>
</div>
);
22 changes: 22 additions & 0 deletions src-docs/src/views/series_chart_histogram/histogram_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import StackedVerticalRectSeriesExample from './stacked_vertical_rect_series';
import HorizontalRectSeriesExample from './horizontal_rect_series';
import StackedHorizontalRectSeriesExample from './stacked_horizontal_rect_series';
import TimeHistogramSeriesExample from './time_histogram_series';
import { FormatCrosshairTimesExample } from './format_crosshair_times';

import {
EuiBadge,
Expand Down Expand Up @@ -177,6 +178,27 @@ export const XYChartHistogramExample = {
],
demo: <StackedHorizontalRectSeriesExample />,
},
{
title: 'Custom crosshair time format',
text: (
<div>
<p>
Specify a custom formatting string to change the locality or format of the time string on the x crosshair.
</p>
</div>
),
source: [
{
type: GuideSectionTypes.JS,
code: require('!!raw-loader!./format_crosshair_times'),
},
{
type: GuideSectionTypes.HTML,
code: 'This component can only be used from React',
},
],
demo: <FormatCrosshairTimesExample />
},
{
title: 'Time Series Histogram version',
text: (
Expand Down
6 changes: 4 additions & 2 deletions src/components/series_chart/crosshairs/crosshair_x.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { AbstractSeries, Crosshair } from 'react-vis';
import { SCALE } from '../utils/chart_utils';
import moment from 'moment';
/**
* The Crosshair used by the XYChart as main tooltip mechanism along X axis (vertical).
*/
Expand Down Expand Up @@ -55,9 +56,9 @@ export class EuiCrosshairX extends AbstractSeries {
}

_formatXValue = (x) => {
const { xType } = this.props;
const { xType, xCrosshairFormat } = this.props;
if (xType === SCALE.TIME || xType === SCALE.TIME_UTC) {
return new Date(x).toISOString(); // TODO add a props for time formatting
return moment(x).format(xCrosshairFormat);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either check to see that xCrosshairFormat is passed through, or add a default value for the prop that matches the original implementation (otherwise xCrosshairFormat is required and this is a breaking change for charts).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also updated in 7968372 - added tests in a later commit as well.

} else {
return x;
}
Expand Down Expand Up @@ -201,5 +202,6 @@ EuiCrosshairX.propTypes = {
* The ordered array of series names
*/
seriesNames: PropTypes.arrayOf(PropTypes.string).isRequired,
xCrosshairFormat: PropTypes.string,
};
EuiCrosshairX.defaultProps = {};
6 changes: 4 additions & 2 deletions src/components/series_chart/crosshairs/crosshair_y.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { AbstractSeries, ScaleUtils } from 'react-vis';
import { SCALE } from '../utils/chart_utils';
import moment from 'moment';

/**
* Format title by detault.
Expand Down Expand Up @@ -237,9 +238,9 @@ export class EuiCrosshairY extends AbstractSeries {
});
}
_formatYValue = (y) => {
const { yType } = this.props;
const { yType, yCrosshairFormat } = this.props;
if (yType === SCALE.TIME || yType === SCALE.TIME_UTC) {
return new Date(y).toISOString(); // TODO add a props for time formatting
return moment(y).format(yCrosshairFormat);
} else {
return y;
}
Expand Down Expand Up @@ -382,5 +383,6 @@ EuiCrosshairY.propTypes = {
* The ordered array of series names
*/
seriesNames: PropTypes.arrayOf(PropTypes.string).isRequired,
yCrosshairFormat: PropTypes.string,
};
EuiCrosshairY.defaultProps = {};
53 changes: 29 additions & 24 deletions src/components/series_chart/series_chart.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import classNames from 'classnames';
import { XYPlot, AbstractSeries } from 'react-vis';
import { AbstractSeries, XYPlot } from 'react-vis';
import { makeFlexible } from './utils/flexible';
import PropTypes from 'prop-types';
import { EuiEmptyPrompt } from '../empty_prompt';
Expand All @@ -17,7 +17,7 @@ const DEFAULT_MARGINS = {
left: 40,
right: 10,
top: 10,
bottom: 40
bottom: 40,
};

/**
Expand All @@ -32,18 +32,17 @@ class XYChart extends PureComponent {
colorIterator = 0;
_xyPlotRef = React.createRef();


/**
* Checks if the plot is empty, looking at existing series and data props.
*/
_isEmptyPlot(children) {
return React.Children
.toArray(children)
.filter(this._isAbstractSeries)
.filter(child => {
return child.props.data && child.props.data.length > 0;
})
.length === 0;
return (
React.Children.toArray(children)
.filter(this._isAbstractSeries)
.filter(child => {
return child.props.data && child.props.data.length > 0;
}).length === 0
);
}

/**
Expand All @@ -55,14 +54,13 @@ class XYChart extends PureComponent {
return prototype instanceof AbstractSeries;
}


/**
* Render children adding a valid EUI visualization color if the color prop is not specified.
*/
_renderChildren(children) {
let colorIterator = 0;

return React.Children.map(children, (child, i) => {
return React.Children.map(children, (child, i) => {
// Avoid applying color props to non series children
if (!this._isAbstractSeries(child)) {
return child;
Expand All @@ -80,11 +78,11 @@ class XYChart extends PureComponent {
return React.cloneElement(child, props);
});
}
_getSeriesNames = (children) => {
return React.Children.toArray(children)
_getSeriesNames = children => {
return React.Children.toArray(children)
.filter(this._isAbstractSeries)
.map(({ props: { name } }) => (name));
}
.map(({ props: { name } }) => name);
};

render() {
const {
Expand All @@ -93,6 +91,8 @@ class XYChart extends PureComponent {
height,
margins,
xType,
xCrosshairFormat,
yCrosshairFormat,
yType,
stackBy,
statusText,
Expand All @@ -119,9 +119,7 @@ class XYChart extends PureComponent {
className="euiSeriesChartContainer__emptyPrompt"
iconType="visualizeApp"
title={<span>Chart not available</span>}
body={
<p>{ statusText }</p>
}
body={<p>{statusText}</p>}
/>
);
}
Expand All @@ -130,10 +128,7 @@ class XYChart extends PureComponent {
const seriesNames = this._getSeriesNames(children);
const classes = classNames(className, 'euiSeriesChartContainer');
return (
<div
className={classes}
{...rest}
>
<div className={classes} {...rest}>
<XYPlot
ref={this._xyPlotRef}
dontCheckIfEmpty
Expand All @@ -153,7 +148,13 @@ class XYChart extends PureComponent {
{this._renderChildren(children)}
{showDefaultAxis && <EuiDefaultAxis orientation={orientation} />}
{showCrosshair && (
<Crosshair seriesNames={seriesNames} crosshairValue={crosshairValue} onCrosshairUpdate={onCrosshairUpdate} />
<Crosshair
seriesNames={seriesNames}
crosshairValue={crosshairValue}
onCrosshairUpdate={onCrosshairUpdate}
xCrosshairFormat={xCrosshairFormat}
yCrosshairFormat={yCrosshairFormat}
/>
)}

{enableSelectionBrush && (
Expand Down Expand Up @@ -182,6 +183,10 @@ XYChart.propTypes = {
stackBy: PropTypes.string,
/** The main x axis scale type. See https://github.com/uber/react-vis/blob/master/docs/scales-and-data.md */
xType: PropTypes.oneOf([LINEAR, ORDINAL, CATEGORY, TIME, TIME_UTC, LOG, LITERAL]),
/** The formatting string for the X-axis. */
xCrosshairFormat: PropTypes.string,
/** The formatting string for the Y-axis. */
yCrosshairFormat: PropTypes.string,
/** The main y axis scale type. See https://github.com/uber/react-vis/blob/master/docs/scales-and-data.md*/
yType: PropTypes.oneOf([LINEAR, ORDINAL, CATEGORY, TIME, TIME_UTC, LOG, LITERAL]),
/** Manually specify the domain of x axis. */
Expand Down