diff --git a/src/kibana/components/vislib/components/tooltip/tooltip.js b/src/kibana/components/vislib/components/tooltip/tooltip.js index 21776b0932ccb8..9215082ac1c31f 100644 --- a/src/kibana/components/vislib/components/tooltip/tooltip.js +++ b/src/kibana/components/vislib/components/tooltip/tooltip.js @@ -3,6 +3,8 @@ define(function (require) { var _ = require('lodash'); var $ = require('jquery'); + var allContents = []; + require('css!components/vislib/styles/main'); /** @@ -14,16 +16,20 @@ define(function (require) { * @param formatter {Function} Tooltip formatter * @param events {Constructor} Allows tooltip to return event response data */ - function Tooltip(el, formatter, events) { + function Tooltip(id, el, formatter, events) { if (!(this instanceof Tooltip)) { - return new Tooltip(el, formatter, events); + return new Tooltip(id, el, formatter, events); } + + this.id = id; // unique id for this tooltip type this.el = el; + this.order = 100; // higher ordered contents are rendered below the others this.formatter = formatter; this.events = events; this.containerClass = 'vis-wrapper'; this.tooltipClass = 'vis-tooltip'; this.tooltipSizerClass = 'vis-tooltip-sizing-clone'; + this.showCondition = _.constant(true); this.$window = $(window); this.$chart = $(el).find('.' + this.containerClass); @@ -63,6 +69,9 @@ define(function (require) { return function (selection) { var $tooltip = self.$get(); var $sizer = self.$getSizer(); + var id = self.id; + var order = self.order; + var tooltipSelection = d3.select($tooltip.get(0)); if (self.container === undefined || self.container !== d3.select(self.el).select('.' + self.containerClass)) { @@ -76,44 +85,71 @@ define(function (require) { // only clear when we leave the chart, so that // moving between points doesn't make it reposition - self.previousPlacement = null; + self.$chart.removeData('previousPlacement'); }); selection.each(function (d, i) { var element = d3.select(this); - function show() { - var placement = self.previousPlacement = self.getTooltipPlacement({ + function render(html, placement) { + allContents = _.filter(allContents, function (content) { + return content.id !== id; + }); + + if (html) allContents.push({ id: id, html: html, order: order }); + + var allHtml = _(allContents) + .sortBy('order') + .pluck('html') + .compact() + .join('\n'); + + if (allHtml) { + $tooltip.html(allHtml).css('visibility', 'visible'); + } else { + $tooltip.css({ + visibility: 'hidden', + left: '-500px', + top: '-500px' + }); + } + + if (placement) { + $tooltip.css({ + left: placement.left + 'px', + top: placement.top + 'px' + }); + } + } + + element + .on('mousemove.tip', function update() { + if (!self.showCondition.call(element, d, i)) { + return render(); + } + + var event = d3.event; + var placement = self.getTooltipPlacement({ $window: self.$window, $chart: self.$chart, $el: $tooltip, $sizer: $sizer, - event: d3.event, - prev: self.previousPlacement + event: event, + prev: self.$chart.data('previousPlacement') }); + + self.$chart.data('previousPlacement', placement); if (!placement) return; var events = self.events ? self.events.eventResponse(d, i) : d; var html = tooltipFormatter(events); - if (!html) return hide(); - - // return text and position for tooltip - return tooltipSelection - .html(html) - .style('visibility', 'visible') - .style('left', placement.left + 'px') - .style('top', placement.top + 'px'); - } + if (!html) return render(); - function hide() { - return tooltipSelection.style('visibility', 'hidden') - .style('left', '-500px') - .style('top', '-500px'); - } - - element - .on('mousemove.tip', show) - .on('mouseout.tip', hide); + render(html, placement); + }) + .on('mouseout.tip', function () { + render(); + }); }); }; }; diff --git a/src/kibana/components/vislib/lib/chart_title.js b/src/kibana/components/vislib/lib/chart_title.js index 7d1bcc9a5c3830..a628a1d7c58f4a 100644 --- a/src/kibana/components/vislib/lib/chart_title.js +++ b/src/kibana/components/vislib/lib/chart_title.js @@ -20,7 +20,7 @@ define(function (require) { } this.el = el; - this.tooltip = new Tooltip(el, function (d) { + this.tooltip = new Tooltip('chart-title', el, function (d) { return d.label; }); } diff --git a/src/kibana/components/vislib/lib/data.js b/src/kibana/components/vislib/lib/data.js index cd4f23ef01c272..c58f94f46afd3c 100644 --- a/src/kibana/components/vislib/lib/data.js +++ b/src/kibana/components/vislib/lib/data.js @@ -387,9 +387,9 @@ define(function (require) { // push the calculated y value to the initialized array (arr) _.forEach(this.flatten(), function (series) { if (self.shouldBeStacked(series) && !grouped) { - return arr.push(self.getYStackMax(series)); + return arr.push(self._getYMax(series, self._getYStack)); } - return arr.push(self.getYMax(series)); + return arr.push(self._getYMax(series, self._getY)); }); return _.max(arr); @@ -403,99 +403,33 @@ define(function (require) { * @returns {*} Array of data objects with x, y, y0 keys */ Data.prototype.stackData = function (series) { + // SHould not stack values on line chart + if (this._attr.type === 'line') return series; return this._attr.stack(series); }; /** - * Calculates the smallest y stack value among all data objects - * - * @method getYStackMin - * @param series {Array} Array of data objects - * @returns {Number} Y stack max value - */ - Data.prototype.getYStackMin = function (series) { - var isOrdered = (this.data.ordered && this.data.ordered.date); - var minDate = isOrdered ? this.data.ordered.min : undefined; - var maxDate = isOrdered ? this.data.ordered.max : undefined; - - return d3.min(this.stackData(series), function (data) { - return d3.min(data, function (d) { - if (isOrdered) { - return (d.x >= minDate && d.x <= maxDate) ? d.y0 + d.y : undefined; - } - - return d.y0 + d.y; - }); - }); - }; - - /** - * Calculates the largest y stack value among all data objects - * - * @method getYStackMax - * @param series {Array} Array of data objects - * @returns {Number} Y stack max value + * Returns the max Y axis value for a `series` array based on + * a specified callback function (calculation). */ - Data.prototype.getYStackMax = function (series) { - var isOrdered = (this.data.ordered && this.data.ordered.date); - var minDate = isOrdered ? this.data.ordered.min : undefined; - var maxDate = isOrdered ? this.data.ordered.max : undefined; - + Data.prototype._getYMax = function (series, calculation) { return d3.max(this.stackData(series), function (data) { - return d3.max(data, function (d) { - if (isOrdered) { - return (d.x >= minDate && d.x <= maxDate) ? d.y0 + d.y : undefined; - } - - return d.y0 + d.y; - }); + return d3.max(data, calculation); }); }; /** - * Calculates the Y domain min value - * - * @method getYMin - * @param series {Array} Array of data objects - * @returns {Number} Y domain min value + * Calculates the y stack value for each data object */ - Data.prototype.getYMin = function (series) { - var isOrdered = (this.data.ordered && this.data.ordered.date); - var minDate = isOrdered ? this.data.ordered.min : undefined; - var maxDate = isOrdered ? this.data.ordered.max : undefined; - - return d3.min(series, function (data) { - return d3.min(data, function (d) { - if (isOrdered) { - return (d.x >= minDate && d.x <= maxDate) ? d.y : undefined; - } - - return d.y; - }); - }); + Data.prototype._getYStack = function (d) { + return d.y0 + d.y; }; /** - * Calculates the Y domain max value - * - * @method getYMax - * @param series {Array} Array of data objects - * @returns {Number} Y domain max value + * Calculates the Y max value */ - Data.prototype.getYMax = function (series) { - var isOrdered = (this.data.ordered && this.data.ordered.date); - var minDate = isOrdered ? this.data.ordered.min : undefined; - var maxDate = isOrdered ? this.data.ordered.max : undefined; - - return d3.max(series, function (data) { - return d3.max(data, function (d) { - if (isOrdered) { - return (d.x >= minDate && d.x <= maxDate) ? d.y : undefined; - } - - return d.y; - }); - }); + Data.prototype._getY = function (d) { + return d.y; }; /** diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index b5809d2abfd816..6683c2476f712e 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -1,6 +1,8 @@ define(function (require) { - return function DispatchClass(d3) { + return function DispatchClass(d3, Private) { var _ = require('lodash'); + var $ = require('jquery'); + var Tooltip = Private(require('components/vislib/components/tooltip/tooltip')); /** * Handles event responses @@ -158,7 +160,9 @@ define(function (require) { */ Dispatch.prototype.addBrushEvent = function (svg) { if (!this.isBrushable()) return; + var xScale = this.handler.xAxis.xScale; + var yScale = this.handler.xAxis.yScale; var brush = this.createBrush(xScale, svg); function brushEnd() { @@ -206,40 +210,41 @@ define(function (require) { // Brush scale var brush = d3.svg.brush() - .x(xScale) - .on('brushend', function brushEnd() { - - // Assumes data is selected at the chart level - // In this case, the number of data objects should always be 1 - var data = d3.select(this).data()[0]; - var isTimeSeries = (data.ordered && data.ordered.date); - - // Allows for brushing on d3.scale.ordinal() - var selected = xScale.domain().filter(function (d) { - return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]); - }); - var range = isTimeSeries ? brush.extent() : selected; - - return dispatch.brush({ - range: range, - config: attr, - e: d3.event, - data: data - }); + .x(xScale) + .on('brushend', function brushEnd() { + + // Assumes data is selected at the chart level + // In this case, the number of data objects should always be 1 + var data = d3.select(this).data()[0]; + var isTimeSeries = (data.ordered && data.ordered.date); + + // Allows for brushing on d3.scale.ordinal() + var selected = xScale.domain().filter(function (d) { + return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]); }); + var range = isTimeSeries ? brush.extent() : selected; + + return dispatch.brush({ + range: range, + config: attr, + e: d3.event, + data: data + }); + }); // if `addBrushing` is true, add brush canvas if (dispatch.on('brush')) { svg.insert('g', 'g') - .attr('class', 'brush') - .call(brush) - .selectAll('rect') - .attr('height', height - margin.top - margin.bottom); + .attr('class', 'brush') + .call(brush) + .selectAll('rect') + .attr('height', height - margin.top - margin.bottom); return brush; } }; + return Dispatch; }; }); diff --git a/src/kibana/components/vislib/lib/x_axis.js b/src/kibana/components/vislib/lib/x_axis.js index c5935a5d8332bd..70e7439a9bff90 100644 --- a/src/kibana/components/vislib/lib/x_axis.js +++ b/src/kibana/components/vislib/lib/x_axis.js @@ -2,6 +2,7 @@ define(function (require) { return function XAxisFactory(d3, Private) { var $ = require('jquery'); var _ = require('lodash'); + var moment = require('moment'); var ErrorHandler = Private(require('components/vislib/lib/_error_handler')); @@ -42,10 +43,11 @@ define(function (require) { * If time, return time scale, else return d3 ordinal scale for nominal data * * @method getScale - * @param ordered {Object|*} Time information * @returns {*} D3 scale function */ - XAxis.prototype.getScale = function (ordered) { + XAxis.prototype.getScale = function () { + var ordered = this.ordered; + if (ordered && ordered.date) { return d3.time.scale.utc(); } @@ -59,12 +61,13 @@ define(function (require) { * * @method getDomain * @param scale {Function} D3 scale - * @param ordered {Object} Time information * @returns {*} D3 scale function */ - XAxis.prototype.getDomain = function (scale, ordered) { + XAxis.prototype.getDomain = function (scale) { + var ordered = this.ordered; + if (ordered && ordered.date) { - return this.getTimeDomain(scale, ordered); + return this.getTimeDomain(scale, this.xValues); } return this.getOrdinalDomain(scale, this.xValues); }; @@ -74,11 +77,37 @@ define(function (require) { * * @method getTimeDomain * @param scale {Function} D3 scale function - * @param ordered {Object} Time information + * @param data {Array} * @returns {*} D3 scale function */ - XAxis.prototype.getTimeDomain = function (scale, ordered) { - return scale.domain([ordered.min, ordered.max]); + XAxis.prototype.getTimeDomain = function (scale, data) { + return scale.domain([this._getXExtents(data, 'min'), this._getXExtents(data, 'max')]); + }; + + /** + * + * @param data + * @param extent + */ + XAxis.prototype._getXExtents = function (data, extent) { + var ordered = this.ordered; + var opts = [ordered[extent]]; + + var point = d3[extent](data); + if (extent === 'max') { + if (ordered.date) { + point = moment(point).add(ordered.interval).valueOf(); + } else { + point += ordered.interval; + } + } + opts.push(point); + + return d3[extent](opts.reduce(function (opts, v) { + if (!_.isNumber(v)) v = +v; + if (!isNaN(v)) opts.push(v); + return opts; + }, [])); }; /** @@ -99,29 +128,29 @@ define(function (require) { * * @method getRange * @param scale {Function} D3 scale function - * @param ordered {Object} Time information * @param width {Number} HTML Element width * @returns {*} D3 scale function */ - XAxis.prototype.getRange = function (scale, ordered, width) { + XAxis.prototype.getRange = function (domain, width) { + var ordered = this.ordered; + if (ordered && ordered.date) { - return scale.range([0, width]); + return domain.range([0, width]); } - return scale.rangeBands([0, width], 0.1); + return domain.rangeBands([0, width], 0.1); }; /** * Return the x axis scale * * @method getXScale - * @param ordered {Object} Time information * @param width {Number} HTML Element width * @returns {*} D3 x scale function */ - XAxis.prototype.getXScale = function (ordered, width) { - var scale = this.getScale(ordered); - var domain = this.getDomain(scale, ordered); - return this.getRange(domain, ordered, width); + XAxis.prototype.getXScale = function (width) { + var domain = this.getDomain(this.getScale()); + + return this.getRange(domain, width); }; /** @@ -131,7 +160,7 @@ define(function (require) { * @param width {Number} HTML Element width */ XAxis.prototype.getXAxis = function (width) { - this.xScale = this.getXScale(this.ordered, width); + this.xScale = this.getXScale(width); if (!this.xScale || _.isNaN(this.xScale)) { throw new Error('xScale is ' + this.xScale); diff --git a/src/kibana/components/vislib/partials/touchdown.html b/src/kibana/components/vislib/partials/touchdown.html new file mode 100644 index 00000000000000..1616f4fbc80cd4 --- /dev/null +++ b/src/kibana/components/vislib/partials/touchdown.html @@ -0,0 +1,3 @@ +

+ This area is outside of the selected time range +

\ No newline at end of file diff --git a/src/kibana/components/vislib/styles/_svg.less b/src/kibana/components/vislib/styles/_svg.less index 01fa278fe879bb..300f0f55da5c37 100644 --- a/src/kibana/components/vislib/styles/_svg.less +++ b/src/kibana/components/vislib/styles/_svg.less @@ -61,3 +61,8 @@ path { stroke: #333; } } + +.endzone { + opacity: 0.2; + pointer-events: none; +} diff --git a/src/kibana/components/vislib/styles/_tooltip.less b/src/kibana/components/vislib/styles/_tooltip.less index b6d27879c06c47..063040239beca3 100644 --- a/src/kibana/components/vislib/styles/_tooltip.less +++ b/src/kibana/components/vislib/styles/_tooltip.less @@ -1,12 +1,14 @@ @import (reference) "../../../styles/main"; +@tooltip-padding: 8px; + .vis-tooltip, .vis-tooltip-sizing-clone { visibility: hidden; line-height: 1.1; font-size: 12px; font-weight: normal; - padding: 8px; + padding: 0 @tooltip-padding @tooltip-padding; background: rgba(70, 82, 93, 0.95); color: @gray-lighter; border-radius: 4px; @@ -14,8 +16,11 @@ z-index: 120; word-wrap: break-word; max-width: 40%; + overflow: hidden; table { + margin: @tooltip-padding 0 0; + td,th { padding: 2px 4px; @@ -40,6 +45,16 @@ } } +.vis-tooltip-header { + margin: 0 (@tooltip-padding * -1); + padding: (@tooltip-padding / 2) @tooltip-padding; + text-align: center; + + &:last-child { + margin-bottom: (@tooltip-padding * -1); + } +} + .vis-tooltip-sizing-clone { visibility: hidden; position: fixed; diff --git a/src/kibana/components/vislib/visualizations/_chart.js b/src/kibana/components/vislib/visualizations/_chart.js index f60068d895cc9e..ad419953ef1824 100644 --- a/src/kibana/components/vislib/visualizations/_chart.js +++ b/src/kibana/components/vislib/visualizations/_chart.js @@ -31,8 +31,7 @@ define(function (require) { var formatter = this.handler.data.get('tooltipFormatter'); // Add tooltip - this.tooltip = new Tooltip($el, formatter, events); - + this.tooltip = new Tooltip('chart', $el, formatter, events); } this._attr = _.defaults(handler._attr || {}, {}); diff --git a/src/kibana/components/vislib/visualizations/_point_series_chart.js b/src/kibana/components/vislib/visualizations/_point_series_chart.js new file mode 100644 index 00000000000000..f35fd73270bef7 --- /dev/null +++ b/src/kibana/components/vislib/visualizations/_point_series_chart.js @@ -0,0 +1,119 @@ +define(function (require) { + return function PointSeriesChartProvider(d3, Private) { + var _ = require('lodash'); + + var Chart = Private(require('components/vislib/visualizations/_chart')); + var Tooltip = Private(require('components/vislib/components/tooltip/tooltip')); + var touchdownHtml = require('text!components/vislib/partials/touchdown.html'); + + _(PointSeriesChart).inherits(Chart); + function PointSeriesChart(handler, chartEl, chartData) { + if (!(this instanceof PointSeriesChart)) { + return new PointSeriesChart(handler, chartEl, chartData); + } + + PointSeriesChart.Super.apply(this, arguments); + } + + /** + * Stacks chart data values + * + * @method stackData + * @param data {Object} Elasticsearch query result for this chart + * @returns {Array} Stacked data objects with x, y, and y0 values + */ + PointSeriesChart.prototype.stackData = function (data) { + var self = this; + var stack = this._attr.stack; + + return stack(data.series.map(function (d) { + var label = d.label; + return d.values.map(function (e, i) { + return { + _input: e, + label: label, + x: self._attr.xValue.call(d.values, e, i), + y: self._attr.yValue.call(d.values, e, i) + }; + }); + })); + }; + + /** + * Creates rects to show buckets outside of the ordered.min and max, returns rects + * + * @param xScale {Function} D3 xScale function + * @param svg {HTMLElement} Reference to SVG + * @method createEndZones + * @returns {D3.Selection} + */ + PointSeriesChart.prototype.createEndZones = function (svg) { + var xScale = this.handler.xAxis.xScale; + var yScale = this.handler.xAxis.yScale; + var addEvent = this.addEvent; + var attr = this.handler._attr; + var height = attr.height; + var width = attr.width; + var margin = attr.margin; + var ordered = this.handler.xAxis.ordered; + var xVals = this.handler.xAxis.xValues; + var color = '#004c99'; + + if (!ordered || ordered.min == null || ordered.max == null) return; + + var leftEndzone = { + x: 0, + w: xScale(ordered.min) > 0 ? xScale(ordered.min) : 0 + }; + + var rightEndzone = { + x: xScale(ordered.max), + w: width - xScale(ordered.max) > 0 ? width - xScale(ordered.max) : 0 + }; + + // svg diagonal line pattern + this.pattern = svg.append('defs') + .append('pattern') + .attr('id', 'DiagonalLines') + .attr('patternUnits', 'userSpaceOnUse') + .attr('patternTransform', 'rotate(45)') + .attr('x', '0') + .attr('y', '0') + .attr('width', '4') + .attr('height', '4') + .append('rect') + .attr('stroke', 'none') + .attr('fill', color) + .attr('width', 2) + .attr('height', 4); + + this.endzones = svg.selectAll('.layer') + .data([leftEndzone, rightEndzone]) + .enter() + .insert('g', '.brush') + .attr('class', 'endzone') + .append('rect') + .attr('class', 'zone') + .attr('x', function (d) { + return d.x; + }) + .attr('y', 0) + .attr('height', height - margin.top - margin.bottom) + .attr('width', function (d) { + return d.w; + }) + .attr('fill', 'url(#DiagonalLines)'); + + var touchdown = _.constant(touchdownHtml); + var endzoneTT = this.endzoneTT = new Tooltip('endzones', this.handler.el, touchdown, null); + endzoneTT.order = 0; + endzoneTT.showCondition = function inEndzone() { + var x = d3.event.offsetX; + return (x < leftEndzone.w) || (x > rightEndzone.x); + }; + endzoneTT.render()(svg); + }; + + return PointSeriesChart; + }; +}); \ No newline at end of file diff --git a/src/kibana/components/vislib/visualizations/area_chart.js b/src/kibana/components/vislib/visualizations/area_chart.js index 84fa233dba7466..da46d295b6910e 100644 --- a/src/kibana/components/vislib/visualizations/area_chart.js +++ b/src/kibana/components/vislib/visualizations/area_chart.js @@ -3,7 +3,7 @@ define(function (require) { var _ = require('lodash'); var $ = require('jquery'); - var Chart = Private(require('components/vislib/visualizations/_chart')); + var PointSeriesChart = Private(require('components/vislib/visualizations/_point_series_chart')); var errors = require('errors'); require('css!components/vislib/styles/main'); @@ -18,7 +18,7 @@ define(function (require) { * @param chartData {Object} Elasticsearch query results for this specific * chart */ - _(AreaChart).inherits(Chart); + _(AreaChart).inherits(PointSeriesChart); function AreaChart(handler, chartEl, chartData) { if (!(this instanceof AreaChart)) { return new AreaChart(handler, chartEl, chartData); @@ -42,31 +42,6 @@ define(function (require) { }); } - /** - * Stacks chart data values - * TODO: refactor so that this is called from the data module - * - * @method stackData - * @param data {Object} Elasticsearch query result for this chart - * @returns {Array} Stacked data objects with x, y, and y0 values - */ - AreaChart.prototype.stackData = function (data) { - var self = this; - var stack = this._attr.stack; - - return stack(data.series.map(function (d) { - var label = d.label; - return d.values.map(function (e, i) { - return { - _input: e, - label: label, - x: self._attr.xValue.call(d.values, e, i), - y: self._attr.yValue.call(d.values, e, i) - }; - }); - })); - }; - /** * Adds SVG path to area chart * @@ -339,6 +314,7 @@ define(function (require) { // add clipPath to hide circles when they go out of bounds self.addClipPath(svg, width, height); + self.createEndZones(svg); // add path path = self.addPath(svg, layers); diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index 26d3f2ddf5e243..0e41fdec3ad820 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -2,8 +2,9 @@ define(function (require) { return function ColumnChartFactory(d3, Private) { var _ = require('lodash'); var $ = require('jquery'); + var moment = require('moment'); - var Chart = Private(require('components/vislib/visualizations/_chart')); + var PointSeriesChart = Private(require('components/vislib/visualizations/_point_series_chart')); var errors = require('errors'); require('css!components/vislib/styles/main'); @@ -17,7 +18,7 @@ define(function (require) { * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ - _(ColumnChart).inherits(Chart); + _(ColumnChart).inherits(PointSeriesChart); function ColumnChart(handler, chartEl, chartData) { if (!(this instanceof ColumnChart)) { return new ColumnChart(handler, chartEl, chartData); @@ -32,30 +33,6 @@ define(function (require) { }); } - /** - * Stacks chart data values - * - * @method stackData - * @param data {Object} Elasticsearch query result for this chart - * @returns {Array} Stacked data objects with x, y, and y0 values - */ - // TODO: refactor so that this is called from the data module - ColumnChart.prototype.stackData = function (data) { - var self = this; - - return this._attr.stack(data.series.map(function (d) { - var label = d.label; - return d.values.map(function (e, i) { - return { - _input: e, - label: label, - x: self._attr.xValue.call(d.values, e, i), - y: self._attr.yValue.call(d.values, e, i) - }; - }); - })); - }; - /** * Adds SVG rect to Vertical Bar Chart * @@ -76,7 +53,7 @@ define(function (require) { .data(layers) .enter().append('g') .attr('class', function (d, i) { - return i; + return 'series ' + i; }); bars = layer.selectAll('rect') @@ -140,22 +117,22 @@ define(function (require) { var yMin = this.handler.yAxis.yScale.domain()[0]; var self = this; + var barWidth; + if (data.ordered && data.ordered.date) { + var start = data.ordered.min; + var end = moment(data.ordered.min).add(data.ordered.interval).valueOf(); + + barWidth = xScale(end) - xScale(start); + barWidth = barWidth - Math.min(barWidth * 0.25, 15); + } + // update bars .attr('x', function (d) { return xScale(d.x); }) .attr('width', function () { - var barWidth; - var barSpacing; - - if (data.ordered && data.ordered.date) { - barWidth = xScale(data.ordered.min + data.ordered.interval) - xScale(data.ordered.min); - barSpacing = barWidth * 0.25; - - return barWidth - barSpacing; - } - return xScale.rangeBand(); + return barWidth || xScale.rangeBand(); }) .attr('y', function (d) { if (d.y < 0) { @@ -315,6 +292,7 @@ define(function (require) { .attr('transform', 'translate(0,' + margin.top + ')'); bars = self.addBars(svg, layers); + self.createEndZones(svg); // Adds event listeners self.addBarEvents(bars, svg); diff --git a/src/kibana/components/vislib/visualizations/line_chart.js b/src/kibana/components/vislib/visualizations/line_chart.js index e25f5814e59547..6dd5462d567e14 100644 --- a/src/kibana/components/vislib/visualizations/line_chart.js +++ b/src/kibana/components/vislib/visualizations/line_chart.js @@ -4,7 +4,7 @@ define(function (require) { var $ = require('jquery'); var errors = require('errors'); - var Chart = Private(require('components/vislib/visualizations/_chart')); + var PointSeriesChart = Private(require('components/vislib/visualizations/_point_series_chart')); require('css!components/vislib/styles/main'); /** @@ -17,7 +17,7 @@ define(function (require) { * @param el {HTMLElement} HTML element to which the chart will be appended * @param chartData {Object} Elasticsearch query results for this specific chart */ - _(LineChart).inherits(Chart); + _(LineChart).inherits(PointSeriesChart); function LineChart(handler, chartEl, chartData) { if (!(this instanceof LineChart)) { return new LineChart(handler, chartEl, chartData); @@ -278,6 +278,7 @@ define(function (require) { lines = self.addLines(svg, data.series); circles = self.addCircles(svg, layers); self.addCircleEvents(circles, svg); + self.createEndZones(svg); var line = svg .append('line') diff --git a/test/unit/specs/vislib/lib/data.js b/test/unit/specs/vislib/lib/data.js index 053047558aac11..de444b98afefad 100644 --- a/test/unit/specs/vislib/lib/data.js +++ b/test/unit/specs/vislib/lib/data.js @@ -327,8 +327,8 @@ define(function (require) { stackedVisData = new Data(stackedDataSeries, {}); series = visData.flatten(); stackedSeries = stackedVisData.flatten(); - maxValue = 25; - stackedMaxValue = 60; + maxValue = 41; + stackedMaxValue = 115; }); }); @@ -337,10 +337,10 @@ define(function (require) { // when calculating the Y max value since it falls outside of the range. it('should return the Y domain max value', function () { series.forEach(function (data) { - expect(visData.getYMax(data)).to.be(maxValue); + expect(visData._getYMax(data, visData._getY)).to.be(maxValue); }); stackedSeries.forEach(function (data) { - expect(stackedVisData.getYStackMax(data)).to.be(stackedMaxValue); + expect(stackedVisData._getYMax(data, visData._getYStack)).to.be(stackedMaxValue); }); }); diff --git a/test/unit/specs/vislib/lib/x_axis.js b/test/unit/specs/vislib/lib/x_axis.js index ad2a2be44c3d27..ec3cbf59556d5f 100644 --- a/test/unit/specs/vislib/lib/x_axis.js +++ b/test/unit/specs/vislib/lib/x_axis.js @@ -136,13 +136,13 @@ define(function (require) { var range; beforeEach(function () { - ordered = dataObj.get('ordered'); - timeScale = xAxis.getScale(ordered); - timeDomain = xAxis.getDomain(timeScale, ordered); - ordinalScale = xAxis.getScale(false); + timeScale = xAxis.getScale(); + timeDomain = xAxis.getDomain(timeScale); + range = xAxis.getRange(timeDomain, width); + xAxis.ordered = {}; + ordinalScale = xAxis.getScale(); ordinalDomain = ordinalScale.domain(['this', 'should', 'be', 'an', 'array']); width = $('.x-axis-div').width(); - range = xAxis.getRange(timeDomain, ordered, width); }); it('should return a function', function () { @@ -176,14 +176,12 @@ define(function (require) { }); describe('getXScale Method', function () { - var ordered; var width; var xScale; beforeEach(function () { - ordered = dataObj.get('ordered'); width = $('.x-axis-div').width(); - xScale = xAxis.getXScale(ordered, width); + xScale = xAxis.getXScale(width); }); it('should return a function', function () { diff --git a/test/unit/specs/vislib/visualizations/column_chart.js b/test/unit/specs/vislib/visualizations/column_chart.js index 96e2ae5c3b3afe..49330f3c395477 100644 --- a/test/unit/specs/vislib/visualizations/column_chart.js +++ b/test/unit/specs/vislib/visualizations/column_chart.js @@ -98,11 +98,7 @@ define(function (require) { numOfSeries = chart.chartData.series.length; numOfValues = chart.chartData.series[0].values.length; product = numOfSeries * numOfValues; - - // remove brushing el before counting rects - $(chart.chartEl).find('g.brush').remove(); - - expect($(chart.chartEl).find('rect')).to.have.length(product); + expect($(chart.chartEl).find('.series rect')).to.have.length(product); }); }); }); @@ -120,13 +116,12 @@ define(function (require) { describe('addBarEvents method', function () { function checkChart(chart) { - var rect = $(chart.chartEl).find('rect')[4]; - var d3selectedRect = d3.select(rect)[0][0]; + var rect = $(chart.chartEl).find('.series rect').get(0); // check for existance of stuff and things return { - click: !!d3selectedRect.__onclick, - mouseOver: !!d3selectedRect.__onmouseover, + click: !!rect.__onclick, + mouseOver: !!rect.__onmouseover, // D3 brushing requires that a g element is appended that // listens for mousedown events. This g element includes // listeners, however, I was not able to test for the listener