diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js b/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js index 1c2c0d5d5c871..5ac2e44a0f7a3 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js +++ b/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js @@ -23,7 +23,7 @@ import ngMock from 'ng_mock'; import series from 'fixtures/vislib/mock_data/date_histogram/_series'; import terms from 'fixtures/vislib/mock_data/terms/_columns'; import $ from 'jquery'; -import { VislibVisualizationsTimeMarkerProvider } from '../../visualizations/time_marker'; +import { TimeMarker } from '../../visualizations/time_marker'; describe('Vislib Time Marker Test Suite', function () { const height = 50; @@ -49,7 +49,6 @@ describe('Vislib Time Marker Test Suite', function () { }); }; const times = []; - let TimeMarker; let defaultMarker; let customMarker; let selection; @@ -59,8 +58,7 @@ describe('Vislib Time Marker Test Suite', function () { let domain; beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); + beforeEach(ngMock.inject(function () { minDomain = getExtent(series.series, d3.min); maxDomain = getExtent(series.series, d3.max); domain = [minDomain, maxDomain]; diff --git a/src/legacy/ui/public/vislib/visualizations/_chart.js b/src/legacy/ui/public/vislib/visualizations/_chart.js index b01f6d2177ec4..a65dfbae7d3c0 100644 --- a/src/legacy/ui/public/vislib/visualizations/_chart.js +++ b/src/legacy/ui/public/vislib/visualizations/_chart.js @@ -21,7 +21,7 @@ import d3 from 'd3'; import _ from 'lodash'; import { dataLabel } from '../lib/_data_label'; import { VislibLibDispatchProvider } from '../lib/dispatch'; -import { TooltipProvider } from '../../vis/components/tooltip'; +import { Tooltip } from '../../vis/components/tooltip'; import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; import { HierarchicalTooltipFormatterProvider } from '../../agg_response/hierarchical/_hierarchical_tooltip_formatter'; import { PointSeriesTooltipFormatter } from '../../agg_response/point_series/_tooltip_formatter'; @@ -29,7 +29,6 @@ import { PointSeriesTooltipFormatter } from '../../agg_response/point_series/_to export function VislibVisualizationsChartProvider(Private) { const Dispatch = Private(VislibLibDispatchProvider); - const Tooltip = Private(TooltipProvider); /** * The Base Class for all visualizations. * diff --git a/src/legacy/ui/public/vislib/visualizations/gauge_chart.js b/src/legacy/ui/public/vislib/visualizations/gauge_chart.js index 09d827429427d..90642259cf348 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauge_chart.js +++ b/src/legacy/ui/public/vislib/visualizations/gauge_chart.js @@ -19,11 +19,10 @@ import d3 from 'd3'; import { VislibVisualizationsChartProvider } from './_chart'; -import { GaugeTypesProvider } from './gauges/gauge_types'; +import { gaugeTypes } from './gauges/gauge_types'; export function GaugeChartProvider(Private) { const Chart = Private(VislibVisualizationsChartProvider); - const gaugeTypes = Private(GaugeTypesProvider); class GaugeChart extends Chart { constructor(handler, chartEl, chartData) { diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js b/src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js index 3c9b0e77f66da..450a514011466 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js +++ b/src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js @@ -17,11 +17,8 @@ * under the License. */ -import { MeterGaugeProvider } from './meter'; +import { MeterGauge } from './meter'; -export function GaugeTypesProvider(Private) { - - return { - meter: Private(MeterGaugeProvider) - }; -} +export const gaugeTypes = { + meter: MeterGauge +}; diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js index f407dbc96367e..834d579b2d93f 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js +++ b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js @@ -21,326 +21,321 @@ import d3 from 'd3'; import _ from 'lodash'; import { getHeatmapColors } from '../../components/color/heatmap_color'; -export function MeterGaugeProvider() { - - - const defaultConfig = { - showTooltip: true, - percentageMode: true, - maxAngle: 2 * Math.PI * 1.3, - minAngle: 2 * Math.PI * 0.7, - innerSpace: 5, - extents: [0, 10000], - scale: { - show: true, - color: '#666', - width: 2, - ticks: 10, - tickLength: 8, - }, - labels: { - show: true, - color: '#666' - }, - style: { - bgWidth: 0.5, - width: 0.9 - } - }; - - class MeterGauge { - constructor(gaugeChart) { - this.gaugeChart = gaugeChart; - this.gaugeConfig = gaugeChart.gaugeConfig; - this.gaugeConfig = _.defaultsDeep(this.gaugeConfig, defaultConfig); - - this.gaugeChart.handler.visConfig.set('legend', { - labels: this.getLabels(), - colors: this.getColors() - }); - - const colors = this.gaugeChart.handler.visConfig.get('legend.colors', null); - if (colors) { - this.gaugeChart.handler.vis.uiState.setSilent('vis.defaultColors', null); - this.gaugeChart.handler.vis.uiState.setSilent('vis.defaultColors', colors); - } - this.colorFunc = this.gaugeChart.handler.data.getColorFunc(); +const defaultConfig = { + showTooltip: true, + percentageMode: true, + maxAngle: 2 * Math.PI * 1.3, + minAngle: 2 * Math.PI * 0.7, + innerSpace: 5, + extents: [0, 10000], + scale: { + show: true, + color: '#666', + width: 2, + ticks: 10, + tickLength: 8, + }, + labels: { + show: true, + color: '#666' + }, + style: { + bgWidth: 0.5, + width: 0.9 + } +}; + +export class MeterGauge { + constructor(gaugeChart) { + this.gaugeChart = gaugeChart; + this.gaugeConfig = gaugeChart.gaugeConfig; + this.gaugeConfig = _.defaultsDeep(this.gaugeConfig, defaultConfig); + + this.gaugeChart.handler.visConfig.set('legend', { + labels: this.getLabels(), + colors: this.getColors() + }); + + const colors = this.gaugeChart.handler.visConfig.get('legend.colors', null); + if (colors) { + this.gaugeChart.handler.vis.uiState.setSilent('vis.defaultColors', null); + this.gaugeChart.handler.vis.uiState.setSilent('vis.defaultColors', colors); } - getLabels() { - const isPercentageMode = this.gaugeConfig.percentageMode; - const colorsRange = this.gaugeConfig.colorsRange; - const max = _.last(colorsRange).to; - const labels = []; - colorsRange.forEach(range => { - const from = isPercentageMode ? Math.round(100 * range.from / max) : range.from; - const to = isPercentageMode ? Math.round(100 * range.to / max) : range.to; - labels.push(`${from} - ${to}`); - }); + this.colorFunc = this.gaugeChart.handler.data.getColorFunc(); + } - return labels; - } + getLabels() { + const isPercentageMode = this.gaugeConfig.percentageMode; + const colorsRange = this.gaugeConfig.colorsRange; + const max = _.last(colorsRange).to; + const labels = []; + colorsRange.forEach(range => { + const from = isPercentageMode ? Math.round(100 * range.from / max) : range.from; + const to = isPercentageMode ? Math.round(100 * range.to / max) : range.to; + labels.push(`${from} - ${to}`); + }); + + return labels; + } - getColors() { - const invertColors = this.gaugeConfig.invertColors; - const colorSchema = this.gaugeConfig.colorSchema; - const colorsRange = this.gaugeConfig.colorsRange; - const labels = this.getLabels(); - const colors = {}; - for (let i = 0; i < labels.length; i += 1) { - const divider = Math.max(colorsRange.length - 1, 1); - const val = invertColors ? 1 - i / divider : i / divider; - colors[labels[i]] = getHeatmapColors(val, colorSchema); - } - return colors; + getColors() { + const invertColors = this.gaugeConfig.invertColors; + const colorSchema = this.gaugeConfig.colorSchema; + const colorsRange = this.gaugeConfig.colorsRange; + const labels = this.getLabels(); + const colors = {}; + for (let i = 0; i < labels.length; i += 1) { + const divider = Math.max(colorsRange.length - 1, 1); + const val = invertColors ? 1 - i / divider : i / divider; + colors[labels[i]] = getHeatmapColors(val, colorSchema); } + return colors; + } - getBucket(val) { - let bucket = _.findIndex(this.gaugeConfig.colorsRange, range => { - return range.from <= val && range.to > val; - }); - - if (bucket === -1) { - if (val < this.gaugeConfig.colorsRange[0].from) bucket = 0; - else bucket = this.gaugeConfig.colorsRange.length - 1; - } - - return bucket; - } + getBucket(val) { + let bucket = _.findIndex(this.gaugeConfig.colorsRange, range => { + return range.from <= val && range.to > val; + }); - getLabel(val) { - const bucket = this.getBucket(val); - const labels = this.gaugeChart.handler.visConfig.get('legend.labels'); - return labels[bucket]; + if (bucket === -1) { + if (val < this.gaugeConfig.colorsRange[0].from) bucket = 0; + else bucket = this.gaugeConfig.colorsRange.length - 1; } - getColorBucket(val) { - const bucket = this.getBucket(val); - const labels = this.gaugeChart.handler.visConfig.get('legend.labels'); - return this.colorFunc(labels[bucket]); - } + return bucket; + } - drawScale(svg, radius, angle) { - const scaleWidth = this.gaugeConfig.scale.width; - const tickLength = this.gaugeConfig.scale.tickLength; - const scaleTicks = this.gaugeConfig.scale.ticks; + getLabel(val) { + const bucket = this.getBucket(val); + const labels = this.gaugeChart.handler.visConfig.get('legend.labels'); + return labels[bucket]; + } - const scale = svg.append('g'); + getColorBucket(val) { + const bucket = this.getBucket(val); + const labels = this.gaugeChart.handler.visConfig.get('legend.labels'); + return this.colorFunc(labels[bucket]); + } - this.gaugeConfig.colorsRange.forEach(range => { - const color = this.getColorBucket(range.from); + drawScale(svg, radius, angle) { + const scaleWidth = this.gaugeConfig.scale.width; + const tickLength = this.gaugeConfig.scale.tickLength; + const scaleTicks = this.gaugeConfig.scale.ticks; - const scaleArc = d3.svg.arc() - .startAngle(angle(range.from)) - .endAngle(angle(range.to)) - .innerRadius(radius) - .outerRadius(radius + scaleWidth); + const scale = svg.append('g'); - scale - .append('path') - .attr('d', scaleArc) - .style('stroke', color) - .style('fill', color); - }); + this.gaugeConfig.colorsRange.forEach(range => { + const color = this.getColorBucket(range.from); + const scaleArc = d3.svg.arc() + .startAngle(angle(range.from)) + .endAngle(angle(range.to)) + .innerRadius(radius) + .outerRadius(radius + scaleWidth); - const extents = angle.domain(); - for (let i = 0; i <= scaleTicks; i++) { - const val = i * (extents[1] - extents[0]) / scaleTicks; - const tickAngle = angle(val) - Math.PI / 2; - const x0 = Math.cos(tickAngle) * radius; - const x1 = Math.cos(tickAngle) * (radius - tickLength); - const y0 = Math.sin(tickAngle) * radius; - const y1 = Math.sin(tickAngle) * (radius - tickLength); - const color = this.getColorBucket(val); - scale.append('line') - .attr('x1', x0).attr('x2', x1) - .attr('y1', y0).attr('y2', y1) - .style('stroke-width', scaleWidth) - .style('stroke', color); - } - - return scale; + scale + .append('path') + .attr('d', scaleArc) + .style('stroke', color) + .style('fill', color); + }); + + + const extents = angle.domain(); + for (let i = 0; i <= scaleTicks; i++) { + const val = i * (extents[1] - extents[0]) / scaleTicks; + const tickAngle = angle(val) - Math.PI / 2; + const x0 = Math.cos(tickAngle) * radius; + const x1 = Math.cos(tickAngle) * (radius - tickLength); + const y0 = Math.sin(tickAngle) * radius; + const y1 = Math.sin(tickAngle) * (radius - tickLength); + const color = this.getColorBucket(val); + scale.append('line') + .attr('x1', x0).attr('x2', x1) + .attr('y1', y0).attr('y2', y1) + .style('stroke-width', scaleWidth) + .style('stroke', color); } - drawGauge(svg, data, width, height) { - const self = this; - const marginFactor = 0.95; - const tooltip = this.gaugeChart.tooltip; - const isTooltip = this.gaugeChart.handler.visConfig.get('addTooltip'); - const isDisplayWarning = this.gaugeChart.handler.visConfig.get('isDisplayWarning', false); - const maxAngle = this.gaugeConfig.maxAngle; - const minAngle = this.gaugeConfig.minAngle; - const angleFactor = this.gaugeConfig.gaugeType === 'Arc' ? 0.75 : 1; - const maxRadius = (Math.min(width, height / angleFactor) / 2) * marginFactor; - - const extendRange = this.gaugeConfig.extendRange; - const maxY = _.max(data.values, 'y').y; - const min = this.gaugeConfig.colorsRange[0].from; - const max = _.last(this.gaugeConfig.colorsRange).to; - const angle = d3.scale.linear() - .range([minAngle, maxAngle]) - .domain([min, extendRange && max < maxY ? maxY : max]); - const radius = d3.scale.linear() - .range([0, maxRadius]) - .domain([this.gaugeConfig.innerSpace + 1, 0]); - - const totalWidth = Math.abs(radius(0) - radius(1)); - const bgPadding = totalWidth * (1 - this.gaugeConfig.style.bgWidth) / 2; - const gaugePadding = totalWidth * (1 - this.gaugeConfig.style.width) / 2; - - /** - * Function to calculate the free space in the center of the gauge. This takes into account - * whether ticks are enabled or not. - * - * This is calculated using the inner diameter (radius(1) * 2) of the gauge. - * If ticks/scale are enabled we need to still subtract the tick length * 2 to make space for a tick - * on every side. If ticks/scale are disabled, the radius(1) function actually leaves space for the scale, - * so we add that free space (which is expressed via the paddings, we just use the larger of those) to the diameter. - */ - const getInnerFreeSpace = () => (radius(1) * 2) - - (this.gaugeConfig.scale.show - ? this.gaugeConfig.scale.tickLength * 2 - : -Math.max(bgPadding, gaugePadding) * 2 - ); - - const arc = d3.svg.arc() - .startAngle(minAngle) - .endAngle(function (d) { - return Math.max(0, Math.min(maxAngle, angle(Math.max(min, d.y)))); - }) - .innerRadius(function (d, i, j) { - return Math.max(0, radius(j + 1) + gaugePadding); - }) - .outerRadius(function (d, i, j) { - return Math.max(0, radius(j) - gaugePadding); - }); + return scale; + } - const bgArc = d3.svg.arc() - .startAngle(minAngle) - .endAngle(maxAngle) - .innerRadius(function (d, i, j) { - return Math.max(0, radius(j + 1) + bgPadding); - }) - .outerRadius(function (d, i, j) { - return Math.max(0, radius(j) - bgPadding); - }); + drawGauge(svg, data, width, height) { + const self = this; + const marginFactor = 0.95; + const tooltip = this.gaugeChart.tooltip; + const isTooltip = this.gaugeChart.handler.visConfig.get('addTooltip'); + const isDisplayWarning = this.gaugeChart.handler.visConfig.get('isDisplayWarning', false); + const maxAngle = this.gaugeConfig.maxAngle; + const minAngle = this.gaugeConfig.minAngle; + const angleFactor = this.gaugeConfig.gaugeType === 'Arc' ? 0.75 : 1; + const maxRadius = (Math.min(width, height / angleFactor) / 2) * marginFactor; + + const extendRange = this.gaugeConfig.extendRange; + const maxY = _.max(data.values, 'y').y; + const min = this.gaugeConfig.colorsRange[0].from; + const max = _.last(this.gaugeConfig.colorsRange).to; + const angle = d3.scale.linear() + .range([minAngle, maxAngle]) + .domain([min, extendRange && max < maxY ? maxY : max]); + const radius = d3.scale.linear() + .range([0, maxRadius]) + .domain([this.gaugeConfig.innerSpace + 1, 0]); + + const totalWidth = Math.abs(radius(0) - radius(1)); + const bgPadding = totalWidth * (1 - this.gaugeConfig.style.bgWidth) / 2; + const gaugePadding = totalWidth * (1 - this.gaugeConfig.style.width) / 2; + + /** + * Function to calculate the free space in the center of the gauge. This takes into account + * whether ticks are enabled or not. + * + * This is calculated using the inner diameter (radius(1) * 2) of the gauge. + * If ticks/scale are enabled we need to still subtract the tick length * 2 to make space for a tick + * on every side. If ticks/scale are disabled, the radius(1) function actually leaves space for the scale, + * so we add that free space (which is expressed via the paddings, we just use the larger of those) to the diameter. + */ + const getInnerFreeSpace = () => (radius(1) * 2) - + (this.gaugeConfig.scale.show + ? this.gaugeConfig.scale.tickLength * 2 + : -Math.max(bgPadding, gaugePadding) * 2 + ); + + const arc = d3.svg.arc() + .startAngle(minAngle) + .endAngle(function (d) { + return Math.max(0, Math.min(maxAngle, angle(Math.max(min, d.y)))); + }) + .innerRadius(function (d, i, j) { + return Math.max(0, radius(j + 1) + gaugePadding); + }) + .outerRadius(function (d, i, j) { + return Math.max(0, radius(j) - gaugePadding); + }); - const gaugeHolders = svg - .selectAll('path') - .data([data]) - .enter() - .append('g') - .attr('data-label', (d) => this.getLabel(d.values[0].y)); + const bgArc = d3.svg.arc() + .startAngle(minAngle) + .endAngle(maxAngle) + .innerRadius(function (d, i, j) { + return Math.max(0, radius(j + 1) + bgPadding); + }) + .outerRadius(function (d, i, j) { + return Math.max(0, radius(j) - bgPadding); + }); + const gaugeHolders = svg + .selectAll('path') + .data([data]) + .enter() + .append('g') + .attr('data-label', (d) => this.getLabel(d.values[0].y)); - const gauges = gaugeHolders - .selectAll('g') - .data(d => d.values) - .enter(); + const gauges = gaugeHolders + .selectAll('g') + .data(d => d.values) + .enter(); - gauges - .append('path') - .attr('d', bgArc) - .style('fill', this.gaugeConfig.style.bgFill); - const series = gauges - .append('path') - .attr('d', arc) - .style('fill', function (d) { - return self.getColorBucket(Math.max(min, d.y)); - }); + gauges + .append('path') + .attr('d', bgArc) + .style('fill', this.gaugeConfig.style.bgFill); - const smallContainer = svg.node().getBBox().height < 70; - let hiddenLabels = smallContainer; + const series = gauges + .append('path') + .attr('d', arc) + .style('fill', function (d) { + return self.getColorBucket(Math.max(min, d.y)); + }); - // If the value label is hidden we later want to hide also all other labels - // since they don't make sense as long as the actual value is hidden. - let valueLabelHidden = false; + const smallContainer = svg.node().getBBox().height < 70; + let hiddenLabels = smallContainer; + + // If the value label is hidden we later want to hide also all other labels + // since they don't make sense as long as the actual value is hidden. + let valueLabelHidden = false; + + gauges + .append('text') + .attr('class', 'chart-label') + .attr('y', -5) + .text(d => { + if (this.gaugeConfig.percentageMode) { + const percentage = Math.round(100 * (d.y - min) / (max - min)); + return `${percentage}%`; + } + return data.yAxisFormatter(d.y); + }) + .attr('style', 'dominant-baseline: central;') + .style('text-anchor', 'middle') + .style('font-size', '2em') + .style('display', function () { + const textLength = this.getBBox().width; + // The text is too long if it's larger than the inner free space minus a couple of random pixels for padding. + const textTooLong = textLength >= getInnerFreeSpace() - 6; + if (textTooLong) { + hiddenLabels = true; + valueLabelHidden = true; + } + return textTooLong ? 'none' : 'initial'; + }); - gauges + if (this.gaugeConfig.labels.show) { + svg .append('text') .attr('class', 'chart-label') - .attr('y', -5) - .text(d => { - if (this.gaugeConfig.percentageMode) { - const percentage = Math.round(100 * (d.y - min) / (max - min)); - return `${percentage}%`; - } - return data.yAxisFormatter(d.y); - }) - .attr('style', 'dominant-baseline: central;') - .style('text-anchor', 'middle') - .style('font-size', '2em') + .text(data.label) + .attr('y', -30) + .attr('style', 'dominant-baseline: central; text-anchor: middle;') .style('display', function () { const textLength = this.getBBox().width; - // The text is too long if it's larger than the inner free space minus a couple of random pixels for padding. - const textTooLong = textLength >= getInnerFreeSpace() - 6; + const textTooLong = textLength > maxRadius; if (textTooLong) { hiddenLabels = true; - valueLabelHidden = true; } - return textTooLong ? 'none' : 'initial'; + return smallContainer || textTooLong ? 'none' : 'initial'; }); - if (this.gaugeConfig.labels.show) { - svg - .append('text') - .attr('class', 'chart-label') - .text(data.label) - .attr('y', -30) - .attr('style', 'dominant-baseline: central; text-anchor: middle;') - .style('display', function () { - const textLength = this.getBBox().width; - const textTooLong = textLength > maxRadius; - if (textTooLong) { - hiddenLabels = true; - } - return smallContainer || textTooLong ? 'none' : 'initial'; - }); - - svg - .append('text') - .attr('class', 'chart-label') - .text(this.gaugeConfig.style.subText) - .attr('y', 20) - .attr('style', 'dominant-baseline: central; text-anchor: middle;') - .style('display', function () { - const textLength = this.getBBox().width; - const textTooLong = textLength > maxRadius; - if (textTooLong) { - hiddenLabels = true; - } - return valueLabelHidden || smallContainer || textTooLong ? 'none' : 'initial'; - }); - } - - if (this.gaugeConfig.scale.show) { - this.drawScale(svg, radius(1), angle); - } - - if (isTooltip) { - series.each(function () { - const gauge = d3.select(this); - gauge.call(tooltip.render()); + svg + .append('text') + .attr('class', 'chart-label') + .text(this.gaugeConfig.style.subText) + .attr('y', 20) + .attr('style', 'dominant-baseline: central; text-anchor: middle;') + .style('display', function () { + const textLength = this.getBBox().width; + const textTooLong = textLength > maxRadius; + if (textTooLong) { + hiddenLabels = true; + } + return valueLabelHidden || smallContainer || textTooLong ? 'none' : 'initial'; }); - } - - if (hiddenLabels && isDisplayWarning) { - this.gaugeChart.handler.alerts.show('Some labels were hidden due to size constraints'); - } + } - //center the visualization - const transformX = width / 2; - const transformY = height / 2 > maxRadius ? height / 2 : maxRadius; + if (this.gaugeConfig.scale.show) { + this.drawScale(svg, radius(1), angle); + } - svg.attr('transform', `translate(${transformX}, ${transformY})`); + if (isTooltip) { + series.each(function () { + const gauge = d3.select(this); + gauge.call(tooltip.render()); + }); + } - return series; + if (hiddenLabels && isDisplayWarning) { + this.gaugeChart.handler.alerts.show('Some labels were hidden due to size constraints'); } - } - return MeterGauge; + //center the visualization + const transformX = width / 2; + const transformY = height / 2 > maxRadius ? height / 2 : maxRadius; + + svg.attr('transform', `translate(${transformX}, ${transformY})`); + + return series; + } } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series.js b/src/legacy/ui/public/vislib/visualizations/point_series.js index 8883be5ae9775..c2da64d94d804 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series.js @@ -20,17 +20,15 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import { TooltipProvider } from '../../vis/components/tooltip'; +import { Tooltip } from '../../vis/components/tooltip'; import { VislibVisualizationsChartProvider } from './_chart'; -import { VislibVisualizationsTimeMarkerProvider } from './time_marker'; -import { VislibVisualizationsSeriesTypesProvider } from './point_series/series_types'; +import { TimeMarker } from './time_marker'; +import { seriesTypes } from './point_series/series_types'; export function VislibVisualizationsPointSeriesProvider(Private) { const Chart = Private(VislibVisualizationsChartProvider); - const Tooltip = Private(TooltipProvider); - const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); - const seriTypes = Private(VislibVisualizationsSeriesTypesProvider); + const seriTypes = seriesTypes; const touchdownTmpl = _.template(require('../partials/touchdown.tmpl.html')); /** * Line Chart Visualization diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js b/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js index a1cd8a403b0e9..dd02a542f35cc 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js @@ -19,70 +19,65 @@ import _ from 'lodash'; -export function VislibVisualizationsPointSeriesProvider() { - - class PointSeries { - constructor(handler, seriesEl, seriesData, seriesConfig) { - this.handler = handler; - this.baseChart = handler.pointSeries; - this.chartEl = seriesEl; - this.chartData = seriesData; - this.seriesConfig = seriesConfig; - } +export class PointSeries { + constructor(handler, seriesEl, seriesData, seriesConfig) { + this.handler = handler; + this.baseChart = handler.pointSeries; + this.chartEl = seriesEl; + this.chartData = seriesData; + this.seriesConfig = seriesConfig; + } - getGroupedCount() { - const stacks = []; - return this.baseChart.chartConfig.series.reduce((sum, series) => { - const valueAxis = series.valueAxis || this.baseChart.handler.valueAxes[0].id; - const isStacked = series.mode === 'stacked'; - const isHistogram = series.type === 'histogram'; - if (!isHistogram) return sum; - if (isStacked && stacks.includes(valueAxis)) return sum; - if (isStacked) stacks.push(valueAxis); - return sum + 1; - }, 0); - } + getGroupedCount() { + const stacks = []; + return this.baseChart.chartConfig.series.reduce((sum, series) => { + const valueAxis = series.valueAxis || this.baseChart.handler.valueAxes[0].id; + const isStacked = series.mode === 'stacked'; + const isHistogram = series.type === 'histogram'; + if (!isHistogram) return sum; + if (isStacked && stacks.includes(valueAxis)) return sum; + if (isStacked) stacks.push(valueAxis); + return sum + 1; + }, 0); + } - getGroupedNum(data) { - let i = 0; - const stacks = []; - for (const seri of this.baseChart.chartConfig.series) { - const valueAxis = seri.valueAxis || this.baseChart.handler.valueAxes[0].id; - const isStacked = seri.mode === 'stacked'; - if (!isStacked) { - if (seri.data === data) return i; - i++; - } else { - if (!(valueAxis in stacks)) stacks[valueAxis] = i++; - if (seri.data === data) return stacks[valueAxis]; - } + getGroupedNum(data) { + let i = 0; + const stacks = []; + for (const seri of this.baseChart.chartConfig.series) { + const valueAxis = seri.valueAxis || this.baseChart.handler.valueAxes[0].id; + const isStacked = seri.mode === 'stacked'; + if (!isStacked) { + if (seri.data === data) return i; + i++; + } else { + if (!(valueAxis in stacks)) stacks[valueAxis] = i++; + if (seri.data === data) return stacks[valueAxis]; } - return 0; } + return 0; + } - getValueAxis() { - return _.find(this.handler.valueAxes, axis => { - return axis.axisConfig.get('id') === this.seriesConfig.valueAxis; - }) || this.handler.valueAxes[0]; - } + getValueAxis() { + return _.find(this.handler.valueAxes, axis => { + return axis.axisConfig.get('id') === this.seriesConfig.valueAxis; + }) || this.handler.valueAxes[0]; + } - getCategoryAxis() { - return _.find(this.handler.categoryAxes, axis => { - return axis.axisConfig.get('id') === this.seriesConfig.categoryAxis; - }) || this.handler.categoryAxes[0]; - } + getCategoryAxis() { + return _.find(this.handler.categoryAxes, axis => { + return axis.axisConfig.get('id') === this.seriesConfig.categoryAxis; + }) || this.handler.categoryAxes[0]; + } - addCircleEvents(element) { - const events = this.events; - if (this.handler.visConfig.get('enableHover')) { - const hover = events.addHoverEvent(); - const mouseout = events.addMouseoutEvent(); - element.call(hover).call(mouseout); - } - const click = events.addClickEvent(); - return element.call(click); + addCircleEvents(element) { + const events = this.events; + if (this.handler.visConfig.get('enableHover')) { + const hover = events.addHoverEvent(); + const mouseout = events.addMouseoutEvent(); + element.call(hover).call(mouseout); } + const click = events.addClickEvent(); + return element.call(click); } - - return PointSeries; } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js b/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js index 24e9a507197c0..9bcc35ef70c37 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js @@ -20,250 +20,244 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import { VislibVisualizationsPointSeriesProvider } from './_point_series'; +import { PointSeries } from './_point_series'; + + +const defaults = { + mode: 'normal', + showCircles: true, + radiusRatio: 9, + showLines: true, + interpolate: 'linear', + color: undefined, + fillColor: undefined, +}; +/** + * Area chart visualization + * + * @class AreaChart + * @constructor + * @extends Chart + * @param handler {Object} Reference to the Handler Class Constructor + * @param el {HTMLElement} HTML element to which the chart will be appended + * @param chartData {Object} Elasticsearch query results for this specific + * chart + */ +export class AreaChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + + this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); + this.isOverlapping = (this.seriesConfig.mode !== 'stacked'); + if (this.isOverlapping) { + + // Default opacity should return to 0.6 on mouseout + const defaultOpacity = 0.6; + this.seriesConfig.defaultOpacity = defaultOpacity; + handler.highlight = function (element) { + const label = this.getAttribute('data-label'); + if (!label) return; + + const highlightOpacity = 0.8; + const highlightElements = $('[data-label]', element.parentNode).filter( + function (els, el) { + return `${$(el).data('label')}` === label; + }); + $('[data-label]', element.parentNode).not(highlightElements).css('opacity', defaultOpacity / 2); // half of the default opacity + highlightElements.css('opacity', highlightOpacity); + }; + handler.unHighlight = function (element) { + $('[data-label]', element).css('opacity', defaultOpacity); -export function VislibVisualizationsAreaChartProvider(Private) { + //The legend should keep max opacity + $('[data-label]', $(element).siblings()).css('opacity', 1); + }; + } - const PointSeries = Private(VislibVisualizationsPointSeriesProvider); + } - const defaults = { - mode: 'normal', - showCircles: true, - radiusRatio: 9, - showLines: true, - interpolate: 'linear', - color: undefined, - fillColor: undefined, - }; - /** - * Area chart visualization - * - * @class AreaChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific - * chart - */ - class AreaChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); - - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); - this.isOverlapping = (this.seriesConfig.mode !== 'stacked'); - if (this.isOverlapping) { - - // Default opacity should return to 0.6 on mouseout - const defaultOpacity = 0.6; - this.seriesConfig.defaultOpacity = defaultOpacity; - handler.highlight = function (element) { - const label = this.getAttribute('data-label'); - if (!label) return; - - const highlightOpacity = 0.8; - const highlightElements = $('[data-label]', element.parentNode).filter( - function (els, el) { - return `${$(el).data('label')}` === label; - }); - $('[data-label]', element.parentNode).not(highlightElements).css('opacity', defaultOpacity / 2); // half of the default opacity - highlightElements.css('opacity', highlightOpacity); - }; - handler.unHighlight = function (element) { - $('[data-label]', element).css('opacity', defaultOpacity); - - //The legend should keep max opacity - $('[data-label]', $(element).siblings()).css('opacity', 1); - }; + addPath(svg, data) { + const ordered = this.handler.data.get('ordered'); + const isTimeSeries = (ordered && ordered.date); + const isOverlapping = this.isOverlapping; + const color = this.handler.data.getColorFunc(); + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.getValueAxis().getScale(); + const interpolate = this.seriesConfig.interpolate; + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + + // Data layers + const layer = svg.append('g') + .attr('class', function (d, i) { + return 'series series-' + i; + }); + + // Append path + const path = layer.append('path') + .attr('data-label', data.label) + .style('fill', () => color(data.label)) + .style('stroke', () => color(data.label)) + .classed('visAreaChart__overlapArea', function () { + return isOverlapping; + }) + .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); + + function x(d) { + if (isTimeSeries) { + return xScale(d.x); } - + return xScale(d.x) + xScale.rangeBand() / 2; } - addPath(svg, data) { - const ordered = this.handler.data.get('ordered'); - const isTimeSeries = (ordered && ordered.date); - const isOverlapping = this.isOverlapping; - const color = this.handler.data.getColorFunc(); - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const interpolate = this.seriesConfig.interpolate; - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - - // Data layers - const layer = svg.append('g') - .attr('class', function (d, i) { - return 'series series-' + i; - }); + function y1(d) { + const y0 = d.y0 || 0; + const y = d.y || 0; + return yScale(y0 + y); + } - // Append path - const path = layer.append('path') - .attr('data-label', data.label) - .style('fill', () => color(data.label)) - .style('stroke', () => color(data.label)) - .classed('visAreaChart__overlapArea', function () { - return isOverlapping; - }) - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - function x(d) { - if (isTimeSeries) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } + function y0(d) { + const y0 = d.y0 || 0; + return yScale(y0); + } - function y1(d) { - const y0 = d.y0 || 0; - const y = d.y || 0; - return yScale(y0 + y); + function getArea() { + if (isHorizontal) { + return d3.svg.area() + .x(x) + .y0(y0) + .y1(y1); + } else { + return d3.svg.area() + .y(x) + .x0(y0) + .x1(y1); } + } - function y0(d) { - const y0 = d.y0 || 0; - return yScale(y0); - } + // update + path + .attr('d', function () { + const area = getArea() + .defined(function (d) { + return !_.isNull(d.y); + }) + .interpolate(interpolate); + return area(data.values); + }) + .style('stroke-width', '1px'); + + return path; + } - function getArea() { - if (isHorizontal) { - return d3.svg.area() - .x(x) - .y0(y0) - .y1(y1); - } else { - return d3.svg.area() - .y(x) - .x0(y0) - .x1(y1); - } + /** + * Adds SVG circles to area chart + * + * @method addCircles + * @param svg {HTMLElement} SVG to which circles are appended + * @param data {Array} Chart data array + * @returns {D3.UpdateSelection} SVG with circles added + */ + addCircles(svg, data) { + const color = this.handler.data.getColorFunc(); + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.getValueAxis().getScale(); + const ordered = this.handler.data.get('ordered'); + const circleRadius = 12; + const circleStrokeWidth = 0; + const tooltip = this.baseChart.tooltip; + const isTooltip = this.handler.visConfig.get('tooltip.show'); + const isOverlapping = this.isOverlapping; + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + + const layer = svg.append('g') + .attr('class', 'points area') + .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); + + // append the circles + const circles = layer.selectAll('circles') + .data(function appendData() { + return data.values.filter(function isZeroOrNull(d) { + return d.y !== 0 && !_.isNull(d.y); + }); + }); + + // exit + circles.exit().remove(); + + // enter + circles + .enter() + .append('circle') + .attr('data-label', data.label) + .attr('stroke', () => { + return color(data.label); + }) + .attr('fill', 'transparent') + .attr('stroke-width', circleStrokeWidth); + + function cx(d) { + if (ordered && ordered.date) { + return xScale(d.x); } - - // update - path - .attr('d', function () { - const area = getArea() - .defined(function (d) { - return !_.isNull(d.y); - }) - .interpolate(interpolate); - return area(data.values); - }) - .style('stroke-width', '1px'); - - return path; + return xScale(d.x) + xScale.rangeBand() / 2; } - /** - * Adds SVG circles to area chart - * - * @method addCircles - * @param svg {HTMLElement} SVG to which circles are appended - * @param data {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with circles added - */ - addCircles(svg, data) { - const color = this.handler.data.getColorFunc(); - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const ordered = this.handler.data.get('ordered'); - const circleRadius = 12; - const circleStrokeWidth = 0; - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - const isOverlapping = this.isOverlapping; - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - - const layer = svg.append('g') - .attr('class', 'points area') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - // append the circles - const circles = layer.selectAll('circles') - .data(function appendData() { - return data.values.filter(function isZeroOrNull(d) { - return d.y !== 0 && !_.isNull(d.y); - }); - }); - - // exit - circles.exit().remove(); - - // enter - circles - .enter() - .append('circle') - .attr('data-label', data.label) - .attr('stroke', () => { - return color(data.label); - }) - .attr('fill', 'transparent') - .attr('stroke-width', circleStrokeWidth); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; + function cy(d) { + const y = d.y || 0; + if (isOverlapping) { + return yScale(y); } + return yScale(d.y0 + y); + } - function cy(d) { - const y = d.y || 0; - if (isOverlapping) { - return yScale(y); - } - return yScale(d.y0 + y); - } + // update + circles + .attr('cx', isHorizontal ? cx : cy) + .attr('cy', isHorizontal ? cy : cx) + .attr('r', circleRadius); - // update - circles - .attr('cx', isHorizontal ? cx : cy) - .attr('cy', isHorizontal ? cy : cx) - .attr('r', circleRadius); + // Add tooltip + if (isTooltip) { + circles.call(tooltip.render()); + } - // Add tooltip - if (isTooltip) { - circles.call(tooltip.render()); - } + return circles; + } - return circles; + addPathEvents(path) { + const events = this.events; + if (this.handler.visConfig.get('enableHover')) { + const hover = events.addHoverEvent(); + const mouseout = events.addMouseoutEvent(); + path.call(hover).call(mouseout); } + } - addPathEvents(path) { - const events = this.events; - if (this.handler.visConfig.get('enableHover')) { - const hover = events.addHoverEvent(); - const mouseout = events.addMouseoutEvent(); - path.call(hover).call(mouseout); - } - } + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the area chart + */ + draw() { + const self = this; - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the area chart - */ - draw() { - const self = this; - - return function (selection) { - selection.each(function () { - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); - - const path = self.addPath(svg, self.chartData); - self.addPathEvents(path); - const circles = self.addCircles(svg, self.chartData); - self.addCircleEvents(circles); - - self.events.emit('rendered', { - chart: self.chartData - }); + return function (selection) { + selection.each(function () { + const svg = self.chartEl.append('g'); + svg.data([self.chartData]); - return svg; + const path = self.addPath(svg, self.chartData); + self.addPathEvents(path); + const circles = self.addCircles(svg, self.chartData); + self.addCircleEvents(circles); + + self.events.emit('rendered', { + chart: self.chartData }); - }; - } - } - return AreaChart; + return svg; + }); + }; + } } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js b/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js index 7d43d4b47e924..6e0dd2851df84 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js @@ -18,263 +18,257 @@ */ import _ from 'lodash'; -import { VislibVisualizationsPointSeriesProvider } from './_point_series'; +import { PointSeries } from './_point_series'; -export function VislibVisualizationsColumnChartProvider(Private) { - const PointSeries = Private(VislibVisualizationsPointSeriesProvider); +const defaults = { + mode: 'normal', + showTooltip: true, + color: undefined, + fillColor: undefined, +}; - const defaults = { - mode: 'normal', - showTooltip: true, - color: undefined, - fillColor: undefined, - }; +/** + * Histogram intervals are not always equal widths, e.g, monthly time intervals. + * It is more visually appealing to vary bar width so that gutter width is constant. + */ +function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCount = 1) { + let datumWidth = defaultWidth; + if (nextDatum) { + datumWidth = ((scale(nextDatum.x) - scale(datum.x)) - gutterWidth) / groupCount; + // To handle data-sets with holes, do not let width be larger than default. + if (datumWidth > defaultWidth) { + datumWidth = defaultWidth; + } + } + return datumWidth; +} + +/** + * Vertical Bar Chart Visualization: renders vertical and/or stacked bars + * + * @class ColumnChart + * @constructor + * @extends Chart + * @param handler {Object} Reference to the Handler Class Constructor + * @param el {HTMLElement} HTML element to which the chart will be appended + * @param chartData {Object} Elasticsearch query results for this specific chart + */ +export class ColumnChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); + } + + addBars(svg, data) { + const self = this; + const color = this.handler.data.getColorFunc(); + const tooltip = this.baseChart.tooltip; + const isTooltip = this.handler.visConfig.get('tooltip.show'); + + const layer = svg.append('g') + .attr('class', 'series histogram') + .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); + + const bars = layer.selectAll('rect') + .data(data.values.filter(function (d) { + return !_.isNull(d.y); + })); + + bars + .exit() + .remove(); + + bars + .enter() + .append('rect') + .attr('data-label', data.label) + .attr('fill', () => color(data.label)) + .attr('stroke', () => color(data.label)); + + self.updateBars(bars); + + // Add tooltip + if (isTooltip) { + bars.call(tooltip.render()); + } + + return bars; + } /** - * Histogram intervals are not always equal widths, e.g, monthly time intervals. - * It is more visually appealing to vary bar width so that gutter width is constant. + * Determines whether bars are grouped or stacked and updates the D3 + * selection + * + * @method updateBars + * @param bars {D3.UpdateSelection} SVG with rect added + * @returns {D3.UpdateSelection} */ - function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCount = 1) { - let datumWidth = defaultWidth; - if (nextDatum) { - datumWidth = ((scale(nextDatum.x) - scale(datum.x)) - gutterWidth) / groupCount; - // To handle data-sets with holes, do not let width be larger than default. - if (datumWidth > defaultWidth) { - datumWidth = defaultWidth; - } + updateBars(bars) { + if (this.seriesConfig.mode === 'stacked') { + return this.addStackedBars(bars); } - return datumWidth; + return this.addGroupedBars(bars); + } /** - * Vertical Bar Chart Visualization: renders vertical and/or stacked bars + * Adds stacked bars to column chart visualization * - * @class ColumnChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific chart + * @method addStackedBars + * @param bars {D3.UpdateSelection} SVG with rect added + * @returns {D3.UpdateSelection} */ - class ColumnChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); + addStackedBars(bars) { + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.getValueAxis().getScale(); + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); + const yMin = yScale.domain()[0]; + const gutterSpacingPercentage = 0.15; + const groupCount = this.getGroupedCount(); + const groupNum = this.getGroupedNum(this.chartData); + let barWidth; + let gutterWidth; + + if (isTimeScale) { + const { min, interval } = this.handler.data.get('ordered'); + let intervalWidth = xScale(min + interval) - xScale(min); + intervalWidth = Math.abs(intervalWidth); + + gutterWidth = intervalWidth * gutterSpacingPercentage; + barWidth = (intervalWidth - gutterWidth) / groupCount; } - addBars(svg, data) { - const self = this; - const color = this.handler.data.getColorFunc(); - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - - const layer = svg.append('g') - .attr('class', 'series histogram') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - const bars = layer.selectAll('rect') - .data(data.values.filter(function (d) { - return !_.isNull(d.y); - })); - - bars - .exit() - .remove(); - - bars - .enter() - .append('rect') - .attr('data-label', data.label) - .attr('fill', () => color(data.label)) - .attr('stroke', () => color(data.label)); - - self.updateBars(bars); - - // Add tooltip - if (isTooltip) { - bars.call(tooltip.render()); + function x(d, i) { + if (isTimeScale) { + return xScale(d.x) + datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount) * groupNum; } - - return bars; + return xScale(d.x) + xScale.rangeBand() / groupCount * groupNum; } - /** - * Determines whether bars are grouped or stacked and updates the D3 - * selection - * - * @method updateBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - updateBars(bars) { - if (this.seriesConfig.mode === 'stacked') { - return this.addStackedBars(bars); + function y(d) { + if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { + return yScale(d.y0); } - return this.addGroupedBars(bars); - + return yScale(d.y0 + d.y); } - /** - * Adds stacked bars to column chart visualization - * - * @method addStackedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - addStackedBars(bars) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); - const yMin = yScale.domain()[0]; - const gutterSpacingPercentage = 0.15; - const groupCount = this.getGroupedCount(); - const groupNum = this.getGroupedNum(this.chartData); - let barWidth; - let gutterWidth; - + function widthFunc(d, i) { if (isTimeScale) { - const { min, interval } = this.handler.data.get('ordered'); - let intervalWidth = xScale(min + interval) - xScale(min); - intervalWidth = Math.abs(intervalWidth); - - gutterWidth = intervalWidth * gutterSpacingPercentage; - barWidth = (intervalWidth - gutterWidth) / groupCount; - } - - function x(d, i) { - if (isTimeScale) { - return xScale(d.x) + datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount) * groupNum; - } - return xScale(d.x) + xScale.rangeBand() / groupCount * groupNum; - } - - function y(d) { - if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { - return yScale(d.y0); - } - return yScale(d.y0 + d.y); + return datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount); } + return xScale.rangeBand() / groupCount; + } - function widthFunc(d, i) { - if (isTimeScale) { - return datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount); - } - return xScale.rangeBand() / groupCount; + function heightFunc(d) { + // for split bars or for one series, + // last series will have d.y0 = 0 + if (d.y0 === 0 && yMin > 0) { + return yScale(yMin) - yScale(d.y); } - function heightFunc(d) { - // for split bars or for one series, - // last series will have d.y0 = 0 - if (d.y0 === 0 && yMin > 0) { - return yScale(yMin) - yScale(d.y); - } + return Math.abs(yScale(d.y0) - yScale(d.y0 + d.y)); + } - return Math.abs(yScale(d.y0) - yScale(d.y0 + d.y)); - } + // update + bars + .attr('x', isHorizontal ? x : y) + .attr('width', isHorizontal ? widthFunc : heightFunc) + .attr('y', isHorizontal ? y : x) + .attr('height', isHorizontal ? heightFunc : widthFunc); - // update - bars - .attr('x', isHorizontal ? x : y) - .attr('width', isHorizontal ? widthFunc : heightFunc) - .attr('y', isHorizontal ? y : x) - .attr('height', isHorizontal ? heightFunc : widthFunc); + return bars; + } - return bars; + /** + * Adds grouped bars to column chart visualization + * + * @method addGroupedBars + * @param bars {D3.UpdateSelection} SVG with rect added + * @returns {D3.UpdateSelection} + */ + addGroupedBars(bars) { + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.getValueAxis().getScale(); + const groupCount = this.getGroupedCount(); + const groupNum = this.getGroupedNum(this.chartData); + const gutterSpacingPercentage = 0.15; + const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + const isLogScale = this.getValueAxis().axisConfig.isLogScale(); + let barWidth; + let gutterWidth; + + if (isTimeScale) { + const { min, interval } = this.handler.data.get('ordered'); + let intervalWidth = xScale(min + interval) - xScale(min); + intervalWidth = Math.abs(intervalWidth); + + gutterWidth = intervalWidth * gutterSpacingPercentage; + barWidth = (intervalWidth - gutterWidth) / groupCount; } - /** - * Adds grouped bars to column chart visualization - * - * @method addGroupedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - addGroupedBars(bars) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const groupCount = this.getGroupedCount(); - const groupNum = this.getGroupedNum(this.chartData); - const gutterSpacingPercentage = 0.15; - const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const isLogScale = this.getValueAxis().axisConfig.isLogScale(); - let barWidth; - let gutterWidth; - + function x(d, i) { if (isTimeScale) { - const { min, interval } = this.handler.data.get('ordered'); - let intervalWidth = xScale(min + interval) - xScale(min); - intervalWidth = Math.abs(intervalWidth); - - gutterWidth = intervalWidth * gutterSpacingPercentage; - barWidth = (intervalWidth - gutterWidth) / groupCount; - } - - function x(d, i) { - if (isTimeScale) { - return xScale(d.x) + datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount) * groupNum; - } - return xScale(d.x) + xScale.rangeBand() / groupCount * groupNum; + return xScale(d.x) + datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount) * groupNum; } + return xScale(d.x) + xScale.rangeBand() / groupCount * groupNum; + } - function y(d) { - if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { - return yScale(0); - } - - return yScale(d.y); + function y(d) { + if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { + return yScale(0); } - function widthFunc(d, i) { - if (isTimeScale) { - return datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount); - } - return xScale.rangeBand() / groupCount; - } + return yScale(d.y); + } - function heightFunc(d) { - const baseValue = isLogScale ? 1 : 0; - return Math.abs(yScale(baseValue) - yScale(d.y)); + function widthFunc(d, i) { + if (isTimeScale) { + return datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount); } + return xScale.rangeBand() / groupCount; + } - // update - bars - .attr('x', isHorizontal ? x : y) - .attr('width', isHorizontal ? widthFunc : heightFunc) - .attr('y', isHorizontal ? y : x) - .attr('height', isHorizontal ? heightFunc : widthFunc); - - return bars; + function heightFunc(d) { + const baseValue = isLogScale ? 1 : 0; + return Math.abs(yScale(baseValue) - yScale(d.y)); } - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the vertical bar chart - */ - draw() { - const self = this; + // update + bars + .attr('x', isHorizontal ? x : y) + .attr('width', isHorizontal ? widthFunc : heightFunc) + .attr('y', isHorizontal ? y : x) + .attr('height', isHorizontal ? heightFunc : widthFunc); + + return bars; + } - return function (selection) { - selection.each(function () { - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the vertical bar chart + */ + draw() { + const self = this; - const bars = self.addBars(svg, self.chartData); - self.addCircleEvents(bars); + return function (selection) { + selection.each(function () { + const svg = self.chartEl.append('g'); + svg.data([self.chartData]); - self.events.emit('rendered', { - chart: self.chartData - }); + const bars = self.addBars(svg, self.chartData); + self.addCircleEvents(bars); - return svg; + self.events.emit('rendered', { + chart: self.chartData }); - }; - } - } - return ColumnChart; + return svg; + }); + }; + } } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js b/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js index b765ccb31838e..c5dfd18626dbe 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js @@ -19,314 +19,308 @@ import _ from 'lodash'; import moment from 'moment'; -import { VislibVisualizationsPointSeriesProvider } from './_point_series'; +import { PointSeries } from './_point_series'; import { getHeatmapColors } from '../../components/color/heatmap_color'; import { isColorDark } from '@elastic/eui'; -export function VislibVisualizationsHeatmapChartProvider(Private) { - const PointSeries = Private(VislibVisualizationsPointSeriesProvider); +const defaults = { + color: undefined, // todo + fillColor: undefined // todo +}; +/** + * Line Chart Visualization + * + * @class HeatmapChart + * @constructor + * @extends Chart + * @param handler {Object} Reference to the Handler Class Constructor + * @param el {HTMLElement} HTML element to which the chart will be appended + * @param chartData {Object} Elasticsearch query results for this specific chart + */ +export class HeatmapChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); + + this.handler.visConfig.set('legend', { + labels: this.getHeatmapLabels(this.handler.visConfig), + colors: this.getHeatmapColors(this.handler.visConfig) + }); + + const colors = this.handler.visConfig.get('legend.colors', null); + if (colors) { + this.handler.vis.uiState.setSilent('vis.defaultColors', null); + this.handler.vis.uiState.setSilent('vis.defaultColors', colors); + } + } - const defaults = { - color: undefined, // todo - fillColor: undefined // todo - }; - /** - * Line Chart Visualization - * - * @class HeatmapChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific chart - */ - class HeatmapChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); - - this.handler.visConfig.set('legend', { - labels: this.getHeatmapLabels(this.handler.visConfig), - colors: this.getHeatmapColors(this.handler.visConfig) + getHeatmapLabels(cfg) { + const percentageMode = cfg.get('percentageMode'); + const colorsNumber = cfg.get('colorsNumber'); + const colorsRange = cfg.get('colorsRange'); + const zAxisConfig = this.getValueAxis().axisConfig; + const zAxisFormatter = zAxisConfig.get('labels.axisFormatter'); + const zScale = this.getValueAxis().getScale(); + const [min, max] = zScale.domain(); + const labels = []; + const maxColorCnt = 10; + if (cfg.get('setColorRange')) { + colorsRange.forEach(range => { + const from = isFinite(range.from) ? zAxisFormatter(range.from) : range.from; + const to = isFinite(range.to) ? zAxisFormatter(range.to) : range.to; + labels.push(`${from} - ${to}`); }); - - const colors = this.handler.visConfig.get('legend.colors', null); - if (colors) { - this.handler.vis.uiState.setSilent('vis.defaultColors', null); - this.handler.vis.uiState.setSilent('vis.defaultColors', colors); + } else { + if (max === min) { + return [ min.toString() ]; } - } - - getHeatmapLabels(cfg) { - const percentageMode = cfg.get('percentageMode'); - const colorsNumber = cfg.get('colorsNumber'); - const colorsRange = cfg.get('colorsRange'); - const zAxisConfig = this.getValueAxis().axisConfig; - const zAxisFormatter = zAxisConfig.get('labels.axisFormatter'); - const zScale = this.getValueAxis().getScale(); - const [min, max] = zScale.domain(); - const labels = []; - const maxColorCnt = 10; - if (cfg.get('setColorRange')) { - colorsRange.forEach(range => { - const from = isFinite(range.from) ? zAxisFormatter(range.from) : range.from; - const to = isFinite(range.to) ? zAxisFormatter(range.to) : range.to; - labels.push(`${from} - ${to}`); - }); - } else { - if (max === min) { - return [ min.toString() ]; - } - for (let i = 0; i < colorsNumber; i++) { - let label; - let val = i / colorsNumber; - let nextVal = (i + 1) / colorsNumber; - if (percentageMode) { - val = Math.ceil(val * 100); - nextVal = Math.ceil(nextVal * 100); - label = `${val}% - ${nextVal}%`; - } else { - val = val * (max - min) + min; - nextVal = nextVal * (max - min) + min; - if (max - min > maxColorCnt) { - const valInt = Math.ceil(val); - if (i === 0) { - val = (valInt === val ? val : valInt - 1); - } - else{ - val = valInt; - } - nextVal = Math.ceil(nextVal); + for (let i = 0; i < colorsNumber; i++) { + let label; + let val = i / colorsNumber; + let nextVal = (i + 1) / colorsNumber; + if (percentageMode) { + val = Math.ceil(val * 100); + nextVal = Math.ceil(nextVal * 100); + label = `${val}% - ${nextVal}%`; + } else { + val = val * (max - min) + min; + nextVal = nextVal * (max - min) + min; + if (max - min > maxColorCnt) { + const valInt = Math.ceil(val); + if (i === 0) { + val = (valInt === val ? val : valInt - 1); } - if (isFinite(val)) val = zAxisFormatter(val); - if (isFinite(nextVal)) nextVal = zAxisFormatter(nextVal); - label = `${val} - ${nextVal}`; + else{ + val = valInt; + } + nextVal = Math.ceil(nextVal); } - - labels.push(label); + if (isFinite(val)) val = zAxisFormatter(val); + if (isFinite(nextVal)) nextVal = zAxisFormatter(nextVal); + label = `${val} - ${nextVal}`; } - } - return labels; + labels.push(label); + } } - getHeatmapColors(cfg) { - const invertColors = cfg.get('invertColors'); - const colorSchema = cfg.get('colorSchema'); - const labels = this.getHeatmapLabels(cfg); - const colors = {}; - for (const i in labels) { - if (labels[i]) { - const val = invertColors ? 1 - i / labels.length : i / labels.length; - colors[labels[i]] = getHeatmapColors(val, colorSchema); - } + return labels; + } + + getHeatmapColors(cfg) { + const invertColors = cfg.get('invertColors'); + const colorSchema = cfg.get('colorSchema'); + const labels = this.getHeatmapLabels(cfg); + const colors = {}; + for (const i in labels) { + if (labels[i]) { + const val = invertColors ? 1 - i / labels.length : i / labels.length; + colors[labels[i]] = getHeatmapColors(val, colorSchema); } - return colors; } + return colors; + } - addSquares(svg, data) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.handler.categoryAxes[1].getScale(); - const zScale = this.getValueAxis().getScale(); - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const colorsNumber = this.handler.visConfig.get('colorsNumber'); - const setColorRange = this.handler.visConfig.get('setColorRange'); - const colorsRange = this.handler.visConfig.get('colorsRange'); - const color = this.handler.data.getColorFunc(); - const labels = this.handler.visConfig.get('legend.labels'); - const zAxisConfig = this.getValueAxis().axisConfig; - const zAxisFormatter = zAxisConfig.get('labels.axisFormatter'); - const showLabels = zAxisConfig.get('labels.show'); - const overwriteLabelColor = zAxisConfig.get('labels.overwriteColor', false); - - const layer = svg.append('g') - .attr('class', 'series'); - - const squares = layer - .selectAll('g.square') - .data(data.values); - - squares - .exit() - .remove(); - - let barWidth; - if (this.getCategoryAxis().axisConfig.isTimeDomain()) { - const { min, interval } = this.handler.data.get('ordered'); - const start = min; - const end = moment(min).add(interval).valueOf(); - - barWidth = xScale(end) - xScale(start); - if (!isHorizontal) barWidth *= -1; - } + addSquares(svg, data) { + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.handler.categoryAxes[1].getScale(); + const zScale = this.getValueAxis().getScale(); + const tooltip = this.baseChart.tooltip; + const isTooltip = this.handler.visConfig.get('tooltip.show'); + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + const colorsNumber = this.handler.visConfig.get('colorsNumber'); + const setColorRange = this.handler.visConfig.get('setColorRange'); + const colorsRange = this.handler.visConfig.get('colorsRange'); + const color = this.handler.data.getColorFunc(); + const labels = this.handler.visConfig.get('legend.labels'); + const zAxisConfig = this.getValueAxis().axisConfig; + const zAxisFormatter = zAxisConfig.get('labels.axisFormatter'); + const showLabels = zAxisConfig.get('labels.show'); + const overwriteLabelColor = zAxisConfig.get('labels.overwriteColor', false); + + const layer = svg.append('g') + .attr('class', 'series'); + + const squares = layer + .selectAll('g.square') + .data(data.values); + + squares + .exit() + .remove(); + + let barWidth; + if (this.getCategoryAxis().axisConfig.isTimeDomain()) { + const { min, interval } = this.handler.data.get('ordered'); + const start = min; + const end = moment(min).add(interval).valueOf(); + + barWidth = xScale(end) - xScale(start); + if (!isHorizontal) barWidth *= -1; + } - function x(d) { - return xScale(d.x); - } + function x(d) { + return xScale(d.x); + } - function y(d) { - return yScale(d.series); - } + function y(d) { + return yScale(d.series); + } - const [min, max] = zScale.domain(); - function getColorBucket(d) { - let val = 0; - if (setColorRange && colorsRange.length) { - const bucket = _.find(colorsRange, range => { - return range.from <= d.y && range.to > d.y; - }); - return bucket ? colorsRange.indexOf(bucket) : -1; + const [min, max] = zScale.domain(); + function getColorBucket(d) { + let val = 0; + if (setColorRange && colorsRange.length) { + const bucket = _.find(colorsRange, range => { + return range.from <= d.y && range.to > d.y; + }); + return bucket ? colorsRange.indexOf(bucket) : -1; + } else { + if (isNaN(min) || isNaN(max)) { + val = colorsNumber - 1; + } else if (min === max) { + val = 0; } else { - if (isNaN(min) || isNaN(max)) { - val = colorsNumber - 1; - } else if (min === max) { - val = 0; - } else { - val = (d.y - min) / (max - min); /* get val from 0 - 1 */ - val = Math.min(colorsNumber - 1, Math.floor(val * colorsNumber)); - } - } - if (d.y == null) { - return -1; + val = (d.y - min) / (max - min); /* get val from 0 - 1 */ + val = Math.min(colorsNumber - 1, Math.floor(val * colorsNumber)); } - return !isNaN(val) ? val : -1; } - - function label(d) { - const colorBucket = getColorBucket(d); - // colorBucket id should always GTE 0 - if (colorBucket < 0) d.hide = true; - return labels[colorBucket]; + if (d.y == null) { + return -1; } + return !isNaN(val) ? val : -1; + } - function z(d) { - if (label(d) === '') return 'transparent'; - return color(label(d)); - } + function label(d) { + const colorBucket = getColorBucket(d); + // colorBucket id should always GTE 0 + if (colorBucket < 0) d.hide = true; + return labels[colorBucket]; + } - const squareWidth = barWidth || xScale.rangeBand(); - const squareHeight = yScale.rangeBand(); - - squares - .enter() - .append('g') - .attr('class', 'square'); - - squares.append('rect') - .attr('x', x) - .attr('width', squareWidth) - .attr('y', y) - .attr('height', squareHeight) - .attr('data-label', label) - .attr('fill', z) - .attr('style', 'cursor: pointer; stroke: black; stroke-width: 0.1px') - .style('display', d => { - return d.hide ? 'none' : 'initial'; - }); + function z(d) { + if (label(d) === '') return 'transparent'; + return color(label(d)); + } + const squareWidth = barWidth || xScale.rangeBand(); + const squareHeight = yScale.rangeBand(); + + squares + .enter() + .append('g') + .attr('class', 'square'); + + squares.append('rect') + .attr('x', x) + .attr('width', squareWidth) + .attr('y', y) + .attr('height', squareHeight) + .attr('data-label', label) + .attr('fill', z) + .attr('style', 'cursor: pointer; stroke: black; stroke-width: 0.1px') + .style('display', d => { + return d.hide ? 'none' : 'initial'; + }); - // todo: verify that longest label is not longer than the barwidth - // or barwidth is not smaller than textheight (and vice versa) - // - if (showLabels) { - const rotate = zAxisConfig.get('labels.rotate'); - const rotateRad = rotate * Math.PI / 180; - const cellPadding = 5; - const maxLength = Math.min( - Math.abs(squareWidth / Math.cos(rotateRad)), - Math.abs(squareHeight / Math.sin(rotateRad)) - ) - cellPadding; - const maxHeight = Math.min( - Math.abs(squareWidth / Math.sin(rotateRad)), - Math.abs(squareHeight / Math.cos(rotateRad)) - ) - cellPadding; - - let labelColor; - if (overwriteLabelColor) { - // If overwriteLabelColor is true, use the manual specified color - labelColor = zAxisConfig.get('labels.color'); - } else { - // Otherwise provide a function that will calculate a light or dark color - labelColor = d => { - const bgColor = z(d); - const color = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); - return color && isColorDark(parseInt(color[1]), parseInt(color[2]), parseInt(color[3])) - ? '#FFF' : '#222'; - }; - } - let hiddenLabels = false; - squares.append('text') - .text(d => zAxisFormatter(d.y)) - .style('display', function (d) { - const textLength = this.getBBox().width; - const textHeight = this.getBBox().height; - const textTooLong = textLength > maxLength; - const textTooWide = textHeight > maxHeight; - if (!d.hide && (textTooLong || textTooWide)) { - hiddenLabels = true; - } - return d.hide || textTooLong || textTooWide ? 'none' : 'initial'; - }) - .style('dominant-baseline', 'central') - .style('text-anchor', 'middle') - .style('fill', labelColor) - .attr('x', function (d) { - const center = x(d) + squareWidth / 2; - return center; - }) - .attr('y', function (d) { - const center = y(d) + squareHeight / 2; - return center; - }) - .attr('transform', function (d) { - const horizontalCenter = x(d) + squareWidth / 2; - const verticalCenter = y(d) + squareHeight / 2; - return `rotate(${rotate},${horizontalCenter},${verticalCenter})`; - }); - if (hiddenLabels) { - this.baseChart.handler.alerts.show('Some labels were hidden due to size constraints'); - } + // todo: verify that longest label is not longer than the barwidth + // or barwidth is not smaller than textheight (and vice versa) + // + if (showLabels) { + const rotate = zAxisConfig.get('labels.rotate'); + const rotateRad = rotate * Math.PI / 180; + const cellPadding = 5; + const maxLength = Math.min( + Math.abs(squareWidth / Math.cos(rotateRad)), + Math.abs(squareHeight / Math.sin(rotateRad)) + ) - cellPadding; + const maxHeight = Math.min( + Math.abs(squareWidth / Math.sin(rotateRad)), + Math.abs(squareHeight / Math.cos(rotateRad)) + ) - cellPadding; + + let labelColor; + if (overwriteLabelColor) { + // If overwriteLabelColor is true, use the manual specified color + labelColor = zAxisConfig.get('labels.color'); + } else { + // Otherwise provide a function that will calculate a light or dark color + labelColor = d => { + const bgColor = z(d); + const color = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); + return color && isColorDark(parseInt(color[1]), parseInt(color[2]), parseInt(color[3])) + ? '#FFF' : '#222'; + }; } - if (isTooltip) { - squares.call(tooltip.render()); + let hiddenLabels = false; + squares.append('text') + .text(d => zAxisFormatter(d.y)) + .style('display', function (d) { + const textLength = this.getBBox().width; + const textHeight = this.getBBox().height; + const textTooLong = textLength > maxLength; + const textTooWide = textHeight > maxHeight; + if (!d.hide && (textTooLong || textTooWide)) { + hiddenLabels = true; + } + return d.hide || textTooLong || textTooWide ? 'none' : 'initial'; + }) + .style('dominant-baseline', 'central') + .style('text-anchor', 'middle') + .style('fill', labelColor) + .attr('x', function (d) { + const center = x(d) + squareWidth / 2; + return center; + }) + .attr('y', function (d) { + const center = y(d) + squareHeight / 2; + return center; + }) + .attr('transform', function (d) { + const horizontalCenter = x(d) + squareWidth / 2; + const verticalCenter = y(d) + squareHeight / 2; + return `rotate(${rotate},${horizontalCenter},${verticalCenter})`; + }); + if (hiddenLabels) { + this.baseChart.handler.alerts.show('Some labels were hidden due to size constraints'); } + } - return squares.selectAll('rect'); + if (isTooltip) { + squares.call(tooltip.render()); } - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the line chart - */ - draw() { - const self = this; + return squares.selectAll('rect'); + } - return function (selection) { - selection.each(function () { - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the line chart + */ + draw() { + const self = this; - const squares = self.addSquares(svg, self.chartData); - self.addCircleEvents(squares); + return function (selection) { + selection.each(function () { + const svg = self.chartEl.append('g'); + svg.data([self.chartData]); - self.events.emit('rendered', { - chart: self.chartData - }); + const squares = self.addSquares(svg, self.chartData); + self.addCircleEvents(squares); - return svg; + self.events.emit('rendered', { + chart: self.chartData }); - }; - } - } - return HeatmapChart; + return svg; + }); + }; + } } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js b/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js index fc2961f1059cc..09111a7ce1d61 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js @@ -19,224 +19,217 @@ import d3 from 'd3'; import _ from 'lodash'; -import { VislibVisualizationsPointSeriesProvider } from './_point_series'; - -export function VislibVisualizationsLineChartProvider(Private) { - - const PointSeries = Private(VislibVisualizationsPointSeriesProvider); - - const defaults = { - mode: 'normal', - showCircles: true, - radiusRatio: 9, - showLines: true, - interpolate: 'linear', - lineWidth: 2, - color: undefined, - fillColor: undefined - }; - /** - * Line Chart Visualization - * - * @class LineChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific chart - */ - class LineChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); - } +import { PointSeries } from './_point_series'; + +const defaults = { + mode: 'normal', + showCircles: true, + radiusRatio: 9, + showLines: true, + interpolate: 'linear', + lineWidth: 2, + color: undefined, + fillColor: undefined +}; +/** + * Line Chart Visualization + * + * @class LineChart + * @constructor + * @extends Chart + * @param handler {Object} Reference to the Handler Class Constructor + * @param el {HTMLElement} HTML element to which the chart will be appended + * @param chartData {Object} Elasticsearch query results for this specific chart + */ +export class LineChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); + } - addCircles(svg, data) { - const self = this; - const showCircles = this.seriesConfig.showCircles; - const color = this.handler.data.getColorFunc(); - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const ordered = this.handler.data.get('ordered'); - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const lineWidth = this.seriesConfig.lineWidth; - - const radii = this.baseChart.radii; - - const radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this.seriesConfig.radiusRatio, 2); - - const layer = svg.append('g') - .attr('class', 'points line') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - const circles = layer - .selectAll('circle') - .data(function appendData() { - return data.values.filter(function (d) { - return !_.isNull(d.y) && (d.y || !d.y0); - }); + addCircles(svg, data) { + const self = this; + const showCircles = this.seriesConfig.showCircles; + const color = this.handler.data.getColorFunc(); + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.getValueAxis().getScale(); + const ordered = this.handler.data.get('ordered'); + const tooltip = this.baseChart.tooltip; + const isTooltip = this.handler.visConfig.get('tooltip.show'); + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + const lineWidth = this.seriesConfig.lineWidth; + + const radii = this.baseChart.radii; + + const radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this.seriesConfig.radiusRatio, 2); + + const layer = svg.append('g') + .attr('class', 'points line') + .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); + + const circles = layer + .selectAll('circle') + .data(function appendData() { + return data.values.filter(function (d) { + return !_.isNull(d.y) && (d.y || !d.y0); }); + }); - circles - .exit() - .remove(); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } + circles + .exit() + .remove(); - function cy(d) { - const y0 = d.y0 || 0; - const y = d.y || 0; - return yScale(y0 + y); + function cx(d) { + if (ordered && ordered.date) { + return xScale(d.x); } + return xScale(d.x) + xScale.rangeBand() / 2; + } - function cColor(d) { - return color(d.series); - } + function cy(d) { + const y0 = d.y0 || 0; + const y = d.y || 0; + return yScale(y0 + y); + } - function colorCircle(d) { - const parent = d3.select(this).node().parentNode; - const lengthOfParent = d3.select(parent).data()[0].length; - const isVisible = (lengthOfParent === 1); + function cColor(d) { + return color(d.series); + } - // If only 1 point exists, show circle - if (!showCircles && !isVisible) return 'none'; - return cColor(d); - } + function colorCircle(d) { + const parent = d3.select(this).node().parentNode; + const lengthOfParent = d3.select(parent).data()[0].length; + const isVisible = (lengthOfParent === 1); - function getCircleRadiusFn(modifier) { - return function getCircleRadius(d) { - const width = self.baseChart.chartConfig.width; - const height = self.baseChart.chartConfig.height; - const circleRadius = (d.z - radii.min) / radiusStep; - const baseMagicNumber = 2; + // If only 1 point exists, show circle + if (!showCircles && !isVisible) return 'none'; + return cColor(d); + } - const base = circleRadius ? Math.sqrt(circleRadius + baseMagicNumber) + lineWidth : lineWidth; - return _.min([base, width, height]) + (modifier || 0); - }; - } + function getCircleRadiusFn(modifier) { + return function getCircleRadius(d) { + const width = self.baseChart.chartConfig.width; + const height = self.baseChart.chartConfig.height; + const circleRadius = (d.z - radii.min) / radiusStep; + const baseMagicNumber = 2; - circles - .enter() - .append('circle') - .attr('r', getCircleRadiusFn()) - .attr('fill-opacity', (this.seriesConfig.drawLinesBetweenPoints ? 1 : 0.7)) - .attr('cx', isHorizontal ? cx : cy) - .attr('cy', isHorizontal ? cy : cx) - .attr('class', 'circle-decoration') - .attr('data-label', data.label) - .attr('fill', colorCircle); - - circles - .enter() - .append('circle') - .attr('r', getCircleRadiusFn(10)) - .attr('cx', isHorizontal ? cx : cy) - .attr('cy', isHorizontal ? cy : cx) - .attr('fill', 'transparent') - .attr('class', 'circle') - .attr('data-label', data.label) - .attr('stroke', cColor) - .attr('stroke-width', 0); - - if (isTooltip) { - circles.call(tooltip.render()); - } + const base = circleRadius ? Math.sqrt(circleRadius + baseMagicNumber) + lineWidth : lineWidth; + return _.min([base, width, height]) + (modifier || 0); + }; + } - return circles; + circles + .enter() + .append('circle') + .attr('r', getCircleRadiusFn()) + .attr('fill-opacity', (this.seriesConfig.drawLinesBetweenPoints ? 1 : 0.7)) + .attr('cx', isHorizontal ? cx : cy) + .attr('cy', isHorizontal ? cy : cx) + .attr('class', 'circle-decoration') + .attr('data-label', data.label) + .attr('fill', colorCircle); + + circles + .enter() + .append('circle') + .attr('r', getCircleRadiusFn(10)) + .attr('cx', isHorizontal ? cx : cy) + .attr('cy', isHorizontal ? cy : cx) + .attr('fill', 'transparent') + .attr('class', 'circle') + .attr('data-label', data.label) + .attr('stroke', cColor) + .attr('stroke-width', 0); + + if (isTooltip) { + circles.call(tooltip.render()); } - /** - * Adds path to SVG - * - * @method addLines - * @param svg {HTMLElement} SVG to which path are appended - * @param data {Array} Array of object data points - * @returns {D3.UpdateSelection} SVG with paths added - */ - addLine(svg, data) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const color = this.handler.data.getColorFunc(); - const ordered = this.handler.data.get('ordered'); - const lineWidth = this.seriesConfig.lineWidth; - const interpolate = this.seriesConfig.interpolate; - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - - const line = svg.append('g') - .attr('class', 'pathgroup lines') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } + return circles; + } - function cy(d) { - const y = d.y || 0; - const y0 = d.y0 || 0; - return yScale(y0 + y); + /** + * Adds path to SVG + * + * @method addLines + * @param svg {HTMLElement} SVG to which path are appended + * @param data {Array} Array of object data points + * @returns {D3.UpdateSelection} SVG with paths added + */ + addLine(svg, data) { + const xScale = this.getCategoryAxis().getScale(); + const yScale = this.getValueAxis().getScale(); + const color = this.handler.data.getColorFunc(); + const ordered = this.handler.data.get('ordered'); + const lineWidth = this.seriesConfig.lineWidth; + const interpolate = this.seriesConfig.interpolate; + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + + const line = svg.append('g') + .attr('class', 'pathgroup lines') + .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); + + function cx(d) { + if (ordered && ordered.date) { + return xScale(d.x); } + return xScale(d.x) + xScale.rangeBand() / 2; + } - line.append('path') - .attr('data-label', data.label) - .attr('d', () => { - const d3Line = d3.svg.line() - .defined(function (d) { - return !_.isNull(d.y); - }) - .interpolate(interpolate) - .x(isHorizontal ? cx : cy) - .y(isHorizontal ? cy : cx); - return d3Line(data.values); - }) - .attr('fill', 'none') - .attr('stroke', () => { - return color(data.label); - }) - .attr('stroke-width', lineWidth); - - return line; + function cy(d) { + const y = d.y || 0; + const y0 = d.y0 || 0; + return yScale(y0 + y); } - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the line chart - */ - draw() { - const self = this; + line.append('path') + .attr('data-label', data.label) + .attr('d', () => { + const d3Line = d3.svg.line() + .defined(function (d) { + return !_.isNull(d.y); + }) + .interpolate(interpolate) + .x(isHorizontal ? cx : cy) + .y(isHorizontal ? cy : cx); + return d3Line(data.values); + }) + .attr('fill', 'none') + .attr('stroke', () => { + return color(data.label); + }) + .attr('stroke-width', lineWidth); + + return line; + } - return function (selection) { - selection.each(function () { + /** + * Renders d3 visualization + * + * @method draw + * @returns {Function} Creates the line chart + */ + draw() { + const self = this; - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); + return function (selection) { + selection.each(function () { - if (self.seriesConfig.drawLinesBetweenPoints) { - self.addLine(svg, self.chartData); - } - const circles = self.addCircles(svg, self.chartData); - self.addCircleEvents(circles); + const svg = self.chartEl.append('g'); + svg.data([self.chartData]); - self.events.emit('rendered', { - chart: self.chartData - }); + if (self.seriesConfig.drawLinesBetweenPoints) { + self.addLine(svg, self.chartData); + } + const circles = self.addCircles(svg, self.chartData); + self.addCircleEvents(circles); - return svg; + self.events.emit('rendered', { + chart: self.chartData }); - }; - } - } - return LineChart; + return svg; + }); + }; + } } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/series_types.js b/src/legacy/ui/public/vislib/visualizations/point_series/series_types.js index f7411ed2dd3ee..a448c8e72e6db 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/series_types.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/series_types.js @@ -17,17 +17,14 @@ * under the License. */ -import { VislibVisualizationsColumnChartProvider } from './column_chart'; -import { VislibVisualizationsLineChartProvider } from './line_chart'; -import { VislibVisualizationsAreaChartProvider } from './area_chart'; -import { VislibVisualizationsHeatmapChartProvider } from './heatmap_chart'; +import { ColumnChart } from './column_chart'; +import { LineChart } from './line_chart'; +import { AreaChart } from './area_chart'; +import { HeatmapChart } from './heatmap_chart'; -export function VislibVisualizationsSeriesTypesProvider(Private) { - - return { - histogram: Private(VislibVisualizationsColumnChartProvider), - line: Private(VislibVisualizationsLineChartProvider), - area: Private(VislibVisualizationsAreaChartProvider), - heatmap: Private(VislibVisualizationsHeatmapChartProvider) - }; -} +export const seriesTypes = { + histogram: ColumnChart, + line: LineChart, + area: AreaChart, + heatmap: HeatmapChart +}; diff --git a/src/legacy/ui/public/vislib/visualizations/time_marker.js b/src/legacy/ui/public/vislib/visualizations/time_marker.js index 347f62cadcc6b..d594969c3439b 100644 --- a/src/legacy/ui/public/vislib/visualizations/time_marker.js +++ b/src/legacy/ui/public/vislib/visualizations/time_marker.js @@ -20,72 +20,67 @@ import d3 from 'd3'; import dateMath from '@elastic/datemath'; -export function VislibVisualizationsTimeMarkerProvider() { +export class TimeMarker { + constructor(times, xScale, height) { + const currentTimeArr = [{ + 'time': new Date().getTime(), + 'class': 'time-marker', + 'color': '#c80000', + 'opacity': 0.3, + 'width': 2 + }]; - class TimeMarker { - constructor(times, xScale, height) { - const currentTimeArr = [{ - 'time': new Date().getTime(), - 'class': 'time-marker', - 'color': '#c80000', - 'opacity': 0.3, - 'width': 2 - }]; - - this.xScale = xScale; - this.height = height; - this.times = (times.length) ? times.map(function (d) { - return { - 'time': dateMath.parse(d.time), - 'class': d.class || 'time-marker', - 'color': d.color || '#c80000', - 'opacity': d.opacity || 0.3, - 'width': d.width || 2 - }; - }) : currentTimeArr; - } + this.xScale = xScale; + this.height = height; + this.times = (times.length) ? times.map(function (d) { + return { + 'time': dateMath.parse(d.time), + 'class': d.class || 'time-marker', + 'color': d.color || '#c80000', + 'opacity': d.opacity || 0.3, + 'width': d.width || 2 + }; + }) : currentTimeArr; + } - _isTimeBasedChart(selection) { - const data = selection.data(); - return data.every(function (datum) { - return (datum.ordered && datum.ordered.date); - }); - } + _isTimeBasedChart(selection) { + const data = selection.data(); + return data.every(function (datum) { + return (datum.ordered && datum.ordered.date); + }); + } - render(selection) { - const self = this; + render(selection) { + const self = this; - // return if not time based chart - if (!self._isTimeBasedChart(selection)) return; + // return if not time based chart + if (!self._isTimeBasedChart(selection)) return; - selection.each(function () { - d3.select(this).selectAll('time-marker') - .data(self.times) - .enter().append('line') - .attr('class', function (d) { - return d.class; - }) - .attr('pointer-events', 'none') - .attr('stroke', function (d) { - return d.color; - }) - .attr('stroke-width', function (d) { - return d.width; - }) - .attr('stroke-opacity', function (d) { - return d.opacity; - }) - .attr('x1', function (d) { - return self.xScale(d.time); - }) - .attr('x2', function (d) { - return self.xScale(d.time); - }) - .attr('y1', self.height) - .attr('y2', self.xScale.range()[0]); - }); - } + selection.each(function () { + d3.select(this).selectAll('time-marker') + .data(self.times) + .enter().append('line') + .attr('class', function (d) { + return d.class; + }) + .attr('pointer-events', 'none') + .attr('stroke', function (d) { + return d.color; + }) + .attr('stroke-width', function (d) { + return d.width; + }) + .attr('stroke-opacity', function (d) { + return d.opacity; + }) + .attr('x1', function (d) { + return self.xScale(d.time); + }) + .attr('x2', function (d) { + return self.xScale(d.time); + }) + .attr('y1', self.height) + .attr('y2', self.xScale.range()[0]); + }); } - - return TimeMarker; }