Skip to content

Commit

Permalink
[discover] add elastic-charts bar chart to discover
Browse files Browse the repository at this point in the history
  • Loading branch information
Emma Cunningham committed Jul 1, 2019
1 parent b20a8e2 commit e6db1d0
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/legacy/core_plugins/kibana/public/discover/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

@import 'components/fetch_error/index';
@import 'components/field_chooser/index';
@import 'components/histogram/index';
@import 'directives/index';
@import 'doc_table/index';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dscHistogram__header--partial {
font-weight: $euiFontWeightRegular;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import 'ngreact';
import { uiModules } from 'ui/modules';
import { DiscoverHistogram } from './histogram';

import { wrapInI18nContext } from 'ui/i18n';

const app = uiModules.get('apps/discover', ['react']);

app.directive('discoverHistogram', reactDirective => reactDirective(wrapInI18nContext(DiscoverHistogram)));
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
import moment from 'moment';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import {
AnnotationDomainTypes,
Axis,
Chart,
HistogramBarSeries,
getAnnotationId,
getAxisId,
getSpecId,
LineAnnotation,
Position,
ScaleType,
Settings,
RectAnnotation,
TooltipValue,
} from '@elastic/charts';

import { i18n } from '@kbn/i18n';

import { getChartTheme } from 'ui/elastic_charts';

export interface DiscoverHistogramProps {
chartData: any;
visLib: any;
}

export class DiscoverHistogram extends Component<DiscoverHistogramProps> {
static propTypes = {
chartData: PropTypes.object,
visLib: PropTypes.object,
};

render() {
const { visLib, chartData } = this.props;

if (!this.props.chartData || !this.props.chartData.series[0]) {
return null;
}

const appState = visLib.API.getAppState();

const data = chartData.series[0].values;
const format = chartData.xAxisFormat.params.pattern;

/**
* Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval].
* see https://github.com/elastic/kibana/issues/27410
*/
const xInterval = chartData.ordered.interval; // We can remove this line only for versions > 8.x

const xValues = chartData.xAxisOrderedValues;
const lastXValue = xValues[xValues.length - 1];

const formatter = (val: string) => {
return moment(val).format(format);
};

const domain = chartData.ordered;
const domainStart = domain.min.valueOf();
const domainEnd = domain.max.valueOf();

const domainMin = data[0].x > domainStart ? domainStart : data[0].x;
const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue;

const xDomain = {
min: domainMin,
max: domainMax,
minInterval: xInterval,
};

// Duplicated from point_series.js
// Domain end of 'now' will be milliseconds behind current time
// Extend toTime by 1 minute to ensure those cases have a TimeMarker
const now = moment();
const isAnnotationAtEdge = domainEnd + 60000 > now && now > domainEnd;
const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now;

const currentTime = {
dataValue: lineAnnotationValue,
};

const lineAnnotationStyle = {
line: {
strokeWidth: 2,
stroke: '#c80000',
opacity: 0.3,
},
};

const partialAnnotationText = i18n.translate(
'kbn.discover.histogram.partialData.annotationText',
{
defaultMessage:
'This area may contain partial data. The selected time range does not fully cover it.',
}
);

const rectAnnotations = [
{
coordinates: {
x0: domainStart,
},
details: partialAnnotationText,
},
{
coordinates: {
x1: domainEnd,
},
details: partialAnnotationText,
},
];

const rectAnnotationStyle = {
stroke: 'rgba(0, 0, 0, 0)',
strokeWidth: 1,
opacity: 1,
fill: 'rgba(0, 0, 0, 0.1)',
};

const customTooltip = (details?: string) => (
<div style={{ minWidth: 200 }}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="alert" />
</EuiFlexItem>
<EuiFlexItem>{details}</EuiFlexItem>
</EuiFlexGroup>
</div>
);

const onBrushEnd = (min: number, max: number) => {
const brushData = {
aggConfigs: visLib.aggs,
data: chartData,
range: [min, max],
};

visLib.API.events.brush(brushData, appState);
};

const partialDataText = i18n.translate('kbn.discover.histogram.partialData.bucketTooltipText', {
defaultMessage:
'Part of this bucket may contain partial data. The selected time range does not fully cover it.',
});

const tooltipHeaderFormater = (headerData: TooltipValue): JSX.Element | string => {
const headerDataValue = headerData.value;
const formattedValue = formatter(headerDataValue);

if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) {
return (
<React.Fragment>
<EuiFlexGroup alignItems="center" className="dscHistogram__header--partial">
<EuiFlexItem grow={false}>
<EuiIcon type="iInCircle" />
</EuiFlexItem>
<EuiFlexItem>{partialDataText}</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<p>{formattedValue}</p>
</React.Fragment>
);
}

return formattedValue;
};

const tooltipProps = {
headerFormatter: tooltipHeaderFormater,
};

return (
<Chart>
<Settings
xDomain={xDomain}
onBrushEnd={onBrushEnd}
tooltip={tooltipProps}
theme={getChartTheme()}
/>
<Axis
id={getAxisId('discover-histogram-left-axis')}
position={Position.Left}
title={chartData.yAxisLabel}
/>
<Axis
id={getAxisId('discover-histogram-bottom-axis')}
position={Position.Bottom}
title={chartData.xAxisLabel}
tickFormat={formatter}
/>
<LineAnnotation
annotationId={getAnnotationId('line-annotation')}
domainType={AnnotationDomainTypes.XDomain}
dataValues={[currentTime]}
hideTooltips={true}
style={lineAnnotationStyle}
/>
<RectAnnotation
dataValues={rectAnnotations}
annotationId={getAnnotationId('rect-annotation')}
zIndex={2}
style={rectAnnotationStyle}
renderTooltip={customTooltip}
/>
<HistogramBarSeries
id={getSpecId('discover-histogram')}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={data}
timeZone={'local'}
name={chartData.yAxisLabel}
/>
</Chart>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import './directive';
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { VisualizeLoaderProvider } from 'ui/visualize/loader/visualize_loader';
import { recentlyAccessed } from 'ui/persisted_log';
import { getDocLink } from 'ui/documentation_links';
import '../components/fetch_error';
import '../components/histogram';
import { getPainlessError } from './get_painless_error';
import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share';
import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing';
Expand Down Expand Up @@ -739,6 +740,7 @@ function discoverController(
.resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource }))
.then(resp => responseHandler(tabifiedData, resp))
.then(resp => {
$scope.histogramData = resp;
visualizeHandler.render({
as: 'visualization',
value: {
Expand Down
8 changes: 8 additions & 0 deletions src/legacy/core_plugins/kibana/public/discover/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ <h1 id="kui_local_breadcrumb" class="kuiLocalBreadcrumb" ng-if="opts.savedSearch

</header>

<discover-histogram
style="display: flex; height: 200px"
ng-show="vis && rows.length !== 0"
chart-data="histogramData"
vis-lib="vis"
watch-depth="reference"
></discover-histogram>

<div id="discoverHistogram"
ng-show="vis && rows.length !== 0"
style="display: flex; height: 200px"
Expand Down
26 changes: 26 additions & 0 deletions src/legacy/ui/public/elastic_charts/get_chart_theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import chrome from 'ui/chrome';
import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';

export function getChartTheme(): Theme {
const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode');
return isDarkMode ? DARK_THEME : LIGHT_THEME;
}
20 changes: 20 additions & 0 deletions src/legacy/ui/public/elastic_charts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { getChartTheme } from './get_chart_theme';
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Chart, Axis, Position, timeFormatter, getAxisId, Settings } from '@elas
import { first } from 'lodash';
import { niceTimeFormatByDay } from '@elastic/charts/dist/utils/data/formatters';
import moment from 'moment';
import { getChartTheme } from 'ui/elastic_charts';
import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types';
import {
MetricsExplorerOptions,
Expand All @@ -23,7 +24,6 @@ import { MetricsExplorerChartContextMenu } from './chart_context_menu';
import { SourceQuery } from '../../graphql/types';
import { MetricsExplorerEmptyChart } from './empty_chart';
import { MetricsExplorerNoMetrics } from './no_metrics';
import { getChartTheme } from './helpers/get_chart_theme';

interface Props {
intl: InjectedIntl;
Expand Down
Loading

0 comments on commit e6db1d0

Please sign in to comment.