diff --git a/src/ui/public/vislib/__tests__/lib/axis_title.js b/src/ui/public/vislib/__tests__/lib/axis_title.js index d75b3d1028b5a..dbcd8c6421894 100644 --- a/src/ui/public/vislib/__tests__/lib/axis_title.js +++ b/src/ui/public/vislib/__tests__/lib/axis_title.js @@ -5,11 +5,13 @@ import ngMock from 'ng_mock'; import expect from 'expect.js'; import $ from 'jquery'; import VislibLibAxisTitleProvider from 'ui/vislib/lib/axis_title'; +import VislibLibAxisConfigProvider from 'ui/vislib/lib/axis_config'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; describe('Vislib AxisTitle Class Test Suite', function () { let AxisTitle; + let AxisConfig; let Data; let PersistedState; let axisTitle; @@ -79,6 +81,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { AxisTitle = Private(VislibLibAxisTitleProvider); + AxisConfig = Private(VislibLibAxisConfigProvider); Data = Private(VislibLibDataProvider); PersistedState = Private(PersistedStatePersistedStateProvider); @@ -86,20 +89,47 @@ describe('Vislib AxisTitle Class Test Suite', function () { .attr('class', 'vis-wrapper'); el.append('div') - .attr('class', 'y-axis-title') - .style('height', '20px') - .style('width', '20px'); + .attr('class', 'axis-wrapper-bottom') + .append('div') + .attr('class', 'axis-title y-axis-title') + .style('height', '20px') + .style('width', '20px'); el.append('div') - .attr('class', 'x-axis-title') - .style('height', '20px') - .style('width', '20px'); + .attr('class', 'axis-wrapper-left') + .append('div') + .attr('class', 'axis-title x-axis-title') + .style('height', '20px') + .style('width', '20px'); dataObj = new Data(data, {}, new PersistedState()); - xTitle = dataObj.get('xAxisLabel'); - yTitle = dataObj.get('yAxisLabel'); - axisTitle = new AxisTitle($('.vis-wrapper')[0], xTitle, yTitle); + const xAxisConfig = new AxisConfig({ + position: 'bottom', + title: { + text: dataObj.get('xAxisLabel') + }, + vis: { + el: el.node(), + _attr: { + type: 'histogram' + } + } + }); + const yAxisConfig = new AxisConfig({ + position: 'left', + title: { + text: dataObj.get('yAxisLabel') + }, + vis: { + el: el.node(), + _attr: { + type: 'histogram' + } + } + }); + xTitle = new AxisTitle(xAxisConfig); + yTitle = new AxisTitle(yAxisConfig); })); afterEach(function () { @@ -108,7 +138,8 @@ describe('Vislib AxisTitle Class Test Suite', function () { describe('render Method', function () { beforeEach(function () { - axisTitle.render(); + xTitle.render();//here + yTitle.render(); }); it('should append an svg to div', function () { @@ -129,7 +160,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { describe('draw Method', function () { it('should be a function', function () { - expect(_.isFunction(axisTitle.draw())).to.be(true); + expect(_.isFunction(xTitle.draw())).to.be(true); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/layout/layout.js b/src/ui/public/vislib/__tests__/lib/layout/layout.js index 58179fbb1efdc..4dded5dc1d175 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/layout.js +++ b/src/ui/public/vislib/__tests__/lib/layout/layout.js @@ -53,16 +53,16 @@ dateHistogramArray.forEach(function (data, i) { describe('createLayout Method', function () { it('should append all the divs', function () { expect($(vis.el).find('.vis-wrapper').length).to.be(1); - expect($(vis.el).find('.y-axis-col-wrapper').length).to.be(1); + expect($(vis.el).find('.y-axis-col-wrapper').length).to.be(2); expect($(vis.el).find('.vis-col-wrapper').length).to.be(1); - expect($(vis.el).find('.y-axis-col').length).to.be(1); - expect($(vis.el).find('.y-axis-title').length).to.be(1); - expect($(vis.el).find('.y-axis-div-wrapper').length).to.be(1); - expect($(vis.el).find('.y-axis-spacer-block').length).to.be(1); + expect($(vis.el).find('.y-axis-col').length).to.be(2); + expect($(vis.el).find('.y-axis-title').length).to.be(2); + expect($(vis.el).find('.y-axis-div-wrapper').length).to.be(2); + expect($(vis.el).find('.y-axis-spacer-block').length).to.be(4); expect($(vis.el).find('.chart-wrapper').length).to.be(numberOfCharts); - expect($(vis.el).find('.x-axis-wrapper').length).to.be(1); - expect($(vis.el).find('.x-axis-div-wrapper').length).to.be(1); - expect($(vis.el).find('.x-axis-title').length).to.be(1); + expect($(vis.el).find('.x-axis-wrapper').length).to.be(2); + expect($(vis.el).find('.x-axis-div-wrapper').length).to.be(2); + expect($(vis.el).find('.x-axis-title').length).to.be(2); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/x_axis.js b/src/ui/public/vislib/__tests__/lib/x_axis.js index 5a1b2af35a3bc..ee8df8f4cc654 100644 --- a/src/ui/public/vislib/__tests__/lib/x_axis.js +++ b/src/ui/public/vislib/__tests__/lib/x_axis.js @@ -94,12 +94,16 @@ describe('Vislib xAxis Class Test Suite', function () { dataObj = new Data(data, {}, persistedState); xAxis = new Axis({ type: 'category', - el: $('.x-axis-div')[0], - xValues: dataObj.xValues(), - ordered: dataObj.get('ordered'), - xAxisFormatter: dataObj.get('xAxisFormatter'), - _attr: { - margin: { top: 0, right: 0, bottom: 0, left: 0 } + vis: { + el: $('.x-axis-div')[0], + _attr: { + margin: { top: 0, right: 0, bottom: 0, left: 0 }, + type: 'histogram' + } + }, + data: dataObj, + labels: { + xAxisFormatter: dataObj.get('xAxisFormatter') } }); })); @@ -127,7 +131,7 @@ describe('Vislib xAxis Class Test Suite', function () { }); }); - describe('getScale, getDomain, getTimeDomain, getOrdinalDomain, and getRange Methods', function () { + describe('getScale, getDomain, getTimeDomain, and getRange Methods', function () { let ordered; let timeScale; let timeDomain; @@ -137,28 +141,45 @@ describe('Vislib xAxis Class Test Suite', function () { let range; beforeEach(function () { - 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(); + xAxis.getAxis(width); + timeScale = xAxis.getScale(); + timeDomain = xAxis.axisScale.getExtents(); + range = xAxis.axisScale.getRange(width); }); it('should return a function', function () { expect(_.isFunction(timeScale)).to.be(true); - expect(_.isFunction(ordinalScale)).to.be(true); }); it('should return the correct domain', function () { - expect(_.isDate(timeDomain.domain()[0])).to.be(true); - expect(_.isDate(timeDomain.domain()[1])).to.be(true); + expect(_.isDate(timeScale.domain()[0])).to.be(true); + expect(_.isDate(timeScale.domain()[1])).to.be(true); }); it('should return the min and max dates', function () { - expect(timeDomain.domain()[0].toDateString()).to.be(new Date(1408734060000).toDateString()); - expect(timeDomain.domain()[1].toDateString()).to.be(new Date(1408734330000).toDateString()); + expect(timeScale.domain()[0].toDateString()).to.be(new Date(1408734060000).toDateString()); + expect(timeScale.domain()[1].toDateString()).to.be(new Date(1408734330000).toDateString()); + }); + + it('should return the correct range', function () { + expect(range[0]).to.be(0); + expect(range[1]).to.be(width); + }); + }); + + describe('getOrdinalDomain Method', function () { + let ordinalScale; + let ordinalDomain; + let width; + + beforeEach(function () { + width = $('.x-axis-div').width(); + xAxis.ordered = null; + xAxis.config.ordered = null; + xAxis.getAxis(width); + ordinalScale = xAxis.getScale(); + ordinalDomain = ordinalScale.domain(['this', 'should', 'be', 'an', 'array']); }); it('should return an ordinal scale', function () { @@ -169,11 +190,6 @@ describe('Vislib xAxis Class Test Suite', function () { it('should return an array of values', function () { expect(_.isArray(ordinalDomain.domain())).to.be(true); }); - - it('should return the correct range', function () { - expect(range.range()[0]).to.be(0); - expect(range.range()[1]).to.be(width); - }); }); describe('getXScale Method', function () { @@ -182,7 +198,8 @@ describe('Vislib xAxis Class Test Suite', function () { beforeEach(function () { width = $('.x-axis-div').width(); - xScale = xAxis.getXScale(width); + xAxis.getAxis(width); + xScale = xAxis.getScale(); }); it('should return a function', function () { @@ -206,19 +223,11 @@ describe('Vislib xAxis Class Test Suite', function () { beforeEach(function () { width = $('.x-axis-div').width(); - xAxis.getXAxis(width); - }); - - it('should create an xAxis function on the xAxis class', function () { - expect(_.isFunction(xAxis.xAxis)).to.be(true); - }); - - it('should create an xScale function on the xAxis class', function () { - expect(_.isFunction(xAxis.xScale)).to.be(true); + xAxis.getAxis(width); }); - it('should create an xAxisFormatter function on the xAxis class', function () { - expect(_.isFunction(xAxis.axisFormatter)).to.be(true); + it('should create an getScale function on the xAxis class', function () { + expect(_.isFunction(xAxis.getScale())).to.be(true); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/y_axis.js b/src/ui/public/vislib/__tests__/lib/y_axis.js index 19e5ab0883146..21e735ad8967e 100644 --- a/src/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/ui/public/vislib/__tests__/lib/y_axis.js @@ -5,7 +5,7 @@ import expect from 'expect.js'; import $ from 'jquery'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -import VislibLibYAxisProvider from 'ui/vislib/lib/y_axis'; +import VislibLibYAxisProvider from 'ui/vislib/lib/axis'; let YAxis; let Data; @@ -77,17 +77,25 @@ function createData(seriesData) { }, persistedState); buildYAxis = function (params) { - return new YAxis(_.merge({}, params, { - el: node, - yMin: dataObj.getYMin(), - yMax: dataObj.getYMax(), - _attr: { - margin: { top: 0, right: 0, bottom: 0, left: 0 }, + return new YAxis(_.merge({}, { + type: 'value', + scale: { + min: dataObj.getYMin(), + max: dataObj.getYMax(), defaultYMin: true, setYExtents: false, - yAxis: {} - } - })); + }, + vis: { + el: node, + _attr: { + margin: { top: 0, right: 0, bottom: 0, left: 0 }, + type: 'histogram', + mode: 'grouped', + yAxis: {} + } + }, + data: dataObj + }, params)); }; yAxis = buildYAxis(); @@ -112,7 +120,6 @@ describe('Vislib yAxis Class Test Suite', function () { describe('render Method', function () { beforeEach(function () { createData(defaultGraphData); - expect(d3.select(yAxis.el).selectAll('.y-axis-div')).to.have.length(1); yAxis.render(); }); @@ -150,7 +157,8 @@ describe('Vislib yAxis Class Test Suite', function () { describe('API', function () { beforeEach(function () { createData(defaultGraphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should return a function', function () { @@ -158,25 +166,12 @@ describe('Vislib yAxis Class Test Suite', function () { }); }); - describe('should return log values', function () { - let domain; - let extents; - - it('should return 1', function () { - yAxis._attr.scale = 'log'; - extents = [0, 400]; - domain = yAxis._getExtents(extents); - - // Log scales have a yMin value of 1 - expect(domain[0]).to.be(1); - }); - }); - describe('positive values', function () { beforeEach(function () { graphData = defaultGraphData; createData(graphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); @@ -196,7 +191,8 @@ describe('Vislib yAxis Class Test Suite', function () { [ -22, -8, -30, -4, 0, 0, -3, -22, -14, -24 ] ]; createData(graphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should have domain between min value and 0', function () { @@ -215,7 +211,8 @@ describe('Vislib yAxis Class Test Suite', function () { [ 22, 8, -30, -4, 0, 0, 3, -22, 14, 24 ] ]; createData(graphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should have domain between min and max values', function () { @@ -230,9 +227,11 @@ describe('Vislib yAxis Class Test Suite', function () { describe('validate user defined values', function () { beforeEach(function () { - yAxis._attr.mode = 'stacked'; - yAxis._attr.setYExtents = false; - yAxis._attr.yAxis = {}; + createData(defaultGraphData); + yAxis.config.set('scale.stacked', true); + yAxis.config.set('scale.setYExtents', false); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should throw a NaN error', function () { @@ -240,17 +239,18 @@ describe('Vislib yAxis Class Test Suite', function () { const max = 12; expect(function () { - yAxis._validateUserExtents(min, max); + yAxis.axisScale.validateUserExtents(min, max); }).to.throwError(); }); it('should return a decimal value', function () { - yAxis._attr.mode = 'percentage'; - yAxis._attr.setYExtents = true; + yAxis.config.set('vis._attr.mode', 'percentage'); + yAxis.config.set('scale.setYExtents', true); + yAxis.getAxis(height); domain = []; - domain[0] = yAxis._attr.yAxis.min = 20; - domain[1] = yAxis._attr.yAxis.max = 80; - const newDomain = yAxis._validateUserExtents(domain); + domain[0] = 20; + domain[1] = 80; + const newDomain = yAxis.axisScale.validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0] / 100); expect(newDomain[1]).to.be(domain[1] / 100); @@ -258,7 +258,7 @@ describe('Vislib yAxis Class Test Suite', function () { it('should return the user defined value', function () { domain = [20, 50]; - const newDomain = yAxis._validateUserExtents(domain); + const newDomain = yAxis.axisScale.validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0]); expect(newDomain[1]).to.be(domain[1]); @@ -271,7 +271,7 @@ describe('Vislib yAxis Class Test Suite', function () { const max = 12; expect(function () { - yAxis._validateAxisExtents(min, max); + yAxis.axisScale.validateAxisExtents(min, max); }).to.throwError(); }); @@ -280,7 +280,7 @@ describe('Vislib yAxis Class Test Suite', function () { const max = 10; expect(function () { - yAxis._validateAxisExtents(min, max); + yAxis.axisScale.validateAxisExtents(min, max); }).to.throwError(); }); }); @@ -291,16 +291,16 @@ describe('Vislib yAxis Class Test Suite', function () { it('should return a function', function () { fnNames.forEach(function (fnName) { - expect(yAxis._getScaleType(fnName)).to.be.a(Function); + expect(yAxis.axisScale.getD3Scale(fnName)).to.be.a(Function); }); // if no value is provided to the function, scale should default to a linear scale - expect(yAxis._getScaleType()).to.be.a(Function); + expect(yAxis.axisScale.getD3Scale()).to.be.a(Function); }); it('should throw an error if function name is undefined', function () { expect(function () { - yAxis._getScaleType('square'); + yAxis.axisScale.getD3Scale('square'); }).to.throwError(); }); }); @@ -308,18 +308,18 @@ describe('Vislib yAxis Class Test Suite', function () { describe('_logDomain method', function () { it('should throw an error', function () { expect(function () { - yAxis._logDomain(-10, -5); + yAxis.axisScale.logDomain(-10, -5); }).to.throwError(); expect(function () { - yAxis._logDomain(-10, 5); + yAxis.axisScale.logDomain(-10, 5); }).to.throwError(); expect(function () { - yAxis._logDomain(0, -5); + yAxis.axisScale.logDomain(0, -5); }).to.throwError(); }); it('should return a yMin value of 1', function () { - const yMin = yAxis._logDomain(0, 200)[0]; + const yMin = yAxis.axisScale.logDomain(0, 200)[0]; expect(yMin).to.be(1); }); }); @@ -330,35 +330,32 @@ describe('Vislib yAxis Class Test Suite', function () { let yScale; beforeEach(function () { createData(defaultGraphData); - mode = yAxis._attr.mode; yMax = yAxis.yMax; - yScale = yAxis.getYScale; }); afterEach(function () { - yAxis._attr.mode = mode; yAxis.yMax = yMax; - yAxis.getYScale = yScale; + yAxis = buildYAxis(); }); it('should use percentage format for percentages', function () { - yAxis._attr.mode = 'percentage'; - const tickFormat = yAxis.getYAxis().tickFormat(); + yAxis = buildYAxis({ + vis: { + _attr: { + mode: 'percentage' + } + } + }); + const tickFormat = yAxis.getAxis().tickFormat(); expect(tickFormat(1)).to.be('100%'); }); it('should use decimal format for small values', function () { yAxis.yMax = 1; - const tickFormat = yAxis.getYAxis().tickFormat(); + const tickFormat = yAxis.getAxis().tickFormat(); expect(tickFormat(0.8)).to.be('0.8'); }); - it('should throw an error if yScale is NaN', function () { - yAxis.getYScale = function () { return NaN; }; - expect(function () { - yAxis.getYAxis(); - }).to.throwError(); - }); }); describe('draw Method', function () { @@ -382,33 +379,4 @@ describe('Vislib yAxis Class Test Suite', function () { expect(yAxis.tickScale(20)).to.be(0); }); }); - - describe('#tickFormat()', function () { - const formatter = function () {}; - - it('returns a basic number formatter by default', function () { - const yAxis = buildYAxis(); - expect(yAxis.tickFormat()).to.not.be(formatter); - expect(yAxis.tickFormat()(1)).to.be('1'); - }); - - it('returns the yAxisFormatter when passed', function () { - const yAxis = buildYAxis({ - yAxisFormatter: formatter - }); - expect(yAxis.tickFormat()).to.be(formatter); - }); - - it('returns a percentage formatter when the vis is in percentage mode', function () { - const yAxis = buildYAxis({ - yAxisFormatter: formatter, - _attr: { - mode: 'percentage' - } - }); - - expect(yAxis.tickFormat()).to.not.be(formatter); - expect(yAxis.tickFormat()(1)).to.be('100%'); - }); - }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/area_chart.js b/src/ui/public/vislib/__tests__/visualizations/area_chart.js index f59db623810f5..07974d2a58a6c 100644 --- a/src/ui/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/area_chart.js @@ -182,16 +182,17 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; + const domain = yAxis.getScale().domain(); - expect(yAxis.domain[0]).to.not.be(undefined); - expect(yAxis.domain[1]).to.not.be(undefined); + expect(domain[0]).to.not.be(undefined); + expect(domain[1]).to.not.be(undefined); }); }); it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -223,11 +224,12 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; const yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; + const domain = yAxis.getScale().domain(); - expect(yAxis.domain[0]).to.equal(yVals[0]); - expect(yAxis.domain[1]).to.equal(yVals[1]); + expect(domain[0]).to.equal(yVals[0]); + expect(domain[1]).to.equal(yVals[1]); }); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/column_chart.js b/src/ui/public/vislib/__tests__/visualizations/column_chart.js index 3a5288e0d0990..27a14ae1e6110 100644 --- a/src/ui/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/column_chart.js @@ -150,16 +150,17 @@ dataTypesArray.forEach(function (dataType, i) { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; + const domain = yAxis.getScale().domain(); - expect(yAxis.domain[0]).to.not.be(undefined); - expect(yAxis.domain[1]).to.not.be(undefined); + expect(domain[0]).to.not.be(undefined); + expect(domain[1]).to.not.be(undefined); }); }); it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -191,12 +192,12 @@ dataTypesArray.forEach(function (dataType, i) { it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; const min = vis.handler.data.getYMin(); const max = vis.handler.data.getYMax(); - - expect(yAxis.domain[0]).to.equal(min); - expect(yAxis.domain[1]).to.equal(max); + const domain = yAxis.getScale().domain(); + expect(domain[0]).to.equal(min); + expect(domain[1]).to.equal(max); }); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/line_chart.js b/src/ui/public/vislib/__tests__/visualizations/line_chart.js index 0c4b02c99ace4..4b6c0ae6801a9 100644 --- a/src/ui/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/line_chart.js @@ -133,16 +133,16 @@ describe('Vislib Line Chart', function () { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; - - expect(yAxis.domain[0]).to.not.be(undefined); - expect(yAxis.domain[1]).to.not.be(undefined); + const yAxis = chart.handler.valueAxes[0]; + const domain = yAxis.getScale().domain(); + expect(domain[0]).to.not.be(undefined); + expect(domain[1]).to.not.be(undefined); }); }); it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -174,11 +174,11 @@ describe('Vislib Line Chart', function () { it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; const yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; - - expect(yAxis.domain[0]).to.equal(yVals[0]); - expect(yAxis.domain[1]).to.equal(yVals[1]); + const domain = yAxis.getScale().domain(); + expect(domain[0]).to.equal(yVals[0]); + expect(domain[1]).to.equal(yVals[1]); }); }); }); diff --git a/src/ui/public/vislib/lib/axis.js b/src/ui/public/vislib/lib/axis.js index f00b211de4fdf..dac38c9fc755c 100644 --- a/src/ui/public/vislib/lib/axis.js +++ b/src/ui/public/vislib/lib/axis.js @@ -5,137 +5,102 @@ import ErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; import AxisTitleProvider from 'ui/vislib/lib/axis_title'; import AxisLabelsProvider from 'ui/vislib/lib/axis_labels'; import AxisScaleProvider from 'ui/vislib/lib/axis_scale'; +import AxisConfigProvider from 'ui/vislib/lib/axis_config'; export default function AxisFactory(Private) { const ErrorHandler = Private(ErrorHandlerProvider); const AxisTitle = Private(AxisTitleProvider); const AxisLabels = Private(AxisLabelsProvider); const AxisScale = Private(AxisScaleProvider); - const defaults = { - show: true, - type: 'value', - elSelector: '.axis-wrapper-{pos} .axis-div', - position: 'left', - axisFormatter: null, // TODO: create default axis formatter - scale: 'linear', - expandLastBucket: true, //TODO: rename ... bucket has nothing to do with vis - inverted: false, - style: { - color: '#ddd', - lineWidth: '1px', - opacity: 1, - tickColor: '#ddd', - tickWidth: '1px', - tickLength: '6px' - } - }; - - const categoryDefaults = { - type: 'category', - position: 'bottom', - labels: { - rotate: 0, - rotateAnchor: 'end', - filter: true - } - }; - /** - * Appends y axis to the visualization - * - * @class Axis - * @constructor - * @param args {{el: (HTMLElement), yMax: (Number), _attr: (Object|*)}} - */ + const AxisConfig = Private(AxisConfigProvider); + class Axis extends ErrorHandler { constructor(args) { super(); - if (args.type === 'category') { - _.extend(this, defaults, categoryDefaults, args); - } else { - _.extend(this, defaults, args); + this.data = args.data; + this.config = new AxisConfig(args); + if (this.config.get('type') === 'category') { + this.values = this.data.xValues(); + this.ordered = this.data.get('ordered'); } - - this._attr = args.vis._attr; - this.elSelector = this.elSelector.replace('{pos}', this.position); - this.scale = new AxisScale(this, {scale: this.scale}); - this.axisTitle = new AxisTitle(this, this.axisTitle); - this.axisLabels = new AxisLabels(this, this.labels); + this.axisScale = new AxisScale(this.config, this.data); + this.axisTitle = new AxisTitle(this.config); + this.axisLabels = new AxisLabels(this.config, this.axisScale); } render() { - d3.select(this.vis.el).selectAll(this.elSelector).call(this.draw()); - } - - isHorizontal() { - return (this.position === 'top' || this.position === 'bottom'); + const elSelector = this.config.get('elSelector'); + const rootEl = this.config.get('rootEl'); + d3.select(rootEl).selectAll(elSelector).call(this.draw()); } getAxis(length) { - const scale = this.scale.getScale(length); + const scale = this.axisScale.getScale(length); + const position = this.config.get('position'); + const axisFormatter = this.config.get('labels.axisFormatter'); return d3.svg.axis() - .scale(scale) - .tickFormat(this.tickFormat(this.domain)) - .ticks(this.tickScale(length)) - .orient(this.position); + .scale(scale) + .tickFormat(axisFormatter) + .ticks(this.tickScale(length)) + .orient(position); } getScale() { - return this.scale.scale; + return this.axisScale.scale; } addInterval(interval) { - return this.scale.addInterval(interval); + return this.axisScale.addInterval(interval); } substractInterval(interval) { - return this.scale.substractInterval(interval); + return this.axisScale.substractInterval(interval); } tickScale(length) { const yTickScale = d3.scale.linear() - .clamp(true) - .domain([20, 40, 1000]) - .range([0, 3, 11]); + .clamp(true) + .domain([20, 40, 1000]) + .range([0, 3, 11]); return Math.ceil(yTickScale(length)); } - tickFormat() { - if (this.axisFormatter) return this.axisFormatter; - if (this.isPercentage()) return d3.format('%'); - return d3.format('n'); - } - getLength(el, n) { - if (this.isHorizontal()) { - return $(el).parent().width() / n - this._attr.margin.left - this._attr.margin.right - 50; + const margin = this.config.get('vis._attr.margin'); + if (this.config.isHorizontal()) { + return $(el).parent().width() / n - margin.left - margin.right - 50; } - return $(el).parent().height() / n - this._attr.margin.top - this._attr.margin.bottom; + return $(el).parent().height() / n - margin.top - margin.bottom; } updateXaxisHeight() { const self = this; - const selection = d3.select(this.vis.el).selectAll('.vis-wrapper'); - + const position = this.config.get('position'); + const selection = d3.select(this.config.get('rootEl')).selectAll('.vis-wrapper'); selection.each(function () { const visEl = d3.select(this); if (visEl.select('.inner-spacer-block').node() === null) { visEl.selectAll('.y-axis-spacer-block') - .append('div') - .attr('class', 'inner-spacer-block'); + .append('div') + .attr('class', 'inner-spacer-block'); } - const height = visEl.select(`.axis-wrapper-${self.position}`).style('height'); - visEl.selectAll(`.y-axis-spacer-block-${self.position} .inner-spacer-block`).style('height', height); + const height = visEl.select(`.axis-wrapper-${position}`).style('height'); + visEl.selectAll(`.y-axis-spacer-block-${position} .inner-spacer-block`).style('height', height); }); } adjustSize() { const self = this; + const config = this.config; const xAxisPadding = 15; + const style = config.get('style'); + const margin = config.get('vis._attr.margin'); + const position = config.get('position'); return function (selection) { const text = selection.selectAll('.tick text'); @@ -143,7 +108,7 @@ export default function AxisFactory(Private) { text.each(function textWidths() { lengths.push((() => { - if (self.isHorizontal()) { + if (config.isHorizontal()) { return d3.select(this.parentNode).node().getBBox().height; } else { return d3.select(this.parentNode).node().getBBox().width; @@ -152,21 +117,21 @@ export default function AxisFactory(Private) { }); const length = lengths.length > 0 ? _.max(lengths) : 0; - if (self.isHorizontal()) { + if (config.isHorizontal()) { selection.attr('height', length); self.updateXaxisHeight(); - if (self.position === 'top') { + if (position === 'top') { selection.select('g') - .attr('transform', `translate(0, ${length - parseInt(self.style.lineWidth)})`); + .attr('transform', `translate(0, ${length - parseInt(style.lineWidth)})`); selection.select('path') - .attr('transform', 'translate(1,0)'); + .attr('transform', 'translate(1,0)'); } } else { selection.attr('width', length + xAxisPadding); - if (self.position === 'left') { - const translateWidth = length + xAxisPadding - 2 - parseInt(self.style.lineWidth); + if (position === 'left') { + const translateWidth = length + xAxisPadding - 2 - parseInt(style.lineWidth); selection.select('g') - .attr('transform', `translate(${translateWidth},${self._attr.margin.top})`); + .attr('transform', `translate(${translateWidth},${margin.top})`); } } }; @@ -174,6 +139,8 @@ export default function AxisFactory(Private) { draw() { const self = this; + const config = this.config; + const style = config.get('style'); return function (selection) { const n = selection[0].length; @@ -192,32 +159,32 @@ export default function AxisFactory(Private) { const axis = self.getAxis(length); - if (self.show) { + if (config.get('show')) { const svg = div.append('svg') - .attr('width', width) - .attr('height', height); + .attr('width', width) + .attr('height', height); svg.append('g') - .attr('class', `axis ${self.id}`) - .call(axis); + .attr('class', `axis ${config.get('id')}`) + .call(axis); const container = svg.select('g.axis').node(); if (container) { svg.select('path') - .style('stroke', self.style.color) - .style('stroke-width', self.style.lineWidth) - .style('stroke-opacity', self.style.opacity); + .style('stroke', style.color) + .style('stroke-width', style.lineWidth) + .style('stroke-opacity', style.opacity); svg.selectAll('line') - .style('stroke', self.style.tickColor) - .style('stroke-width', self.style.tickWidth) - .style('stroke-opacity', self.style.opacity); + .style('stroke', style.tickColor) + .style('stroke-width', style.tickWidth) + .style('stroke-opacity', style.opacity); // TODO: update to be depenent on position ... - //.attr('x1', -parseInt(self.style.lineWidth) / 2) - //.attr('x2', -parseInt(self.style.lineWidth) / 2 - parseInt(self.style.tickLength)); + //.attr('x1', -parseInt(style.lineWidth) / 2) + //.attr('x2', -parseInt(style.lineWidth) / 2 - parseInt(style.tickLength)); - if (self.axisLabels) self.axisLabels.render(svg); - svg.call(self.adjustSize()); } + if (self.axisLabels) self.axisLabels.render(svg); + svg.call(self.adjustSize()); } }); }; diff --git a/src/ui/public/vislib/lib/axis_config.js b/src/ui/public/vislib/lib/axis_config.js new file mode 100644 index 0000000000000..858e5d7ed25f6 --- /dev/null +++ b/src/ui/public/vislib/lib/axis_config.js @@ -0,0 +1,141 @@ +import _ from 'lodash'; +import d3 from 'd3'; +export default function AxisConfigFactory() { + + const defaults = { + show: true, + type: 'value', + elSelector: '.axis-wrapper-{pos} .axis-div', + position: 'left', + scale: { + type: 'linear', + expandLastBucket: true, //TODO: rename ... bucket has nothing to do with vis + inverted: false, + setYExtents: null, + defaultYExtents: null, + min: null, + max: null + }, + style: { + color: '#ddd', + lineWidth: '1px', + opacity: 1, + tickColor: '#ddd', + tickWidth: '1px', + tickLength: '6px' + }, + labels: { + axisFormatter: d3.format('n'), + show: true, + rotate: 0, + rotateAnchor: 'center', + filter: false, + color: '#ddd', + font: '"Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif', // TODO + fontSize: '8pt', + truncate: 100 + }, + title: { + text: '', + elSelector: '.axis-wrapper-{pos} .axis-title' + } + }; + + const categoryDefaults = { + type: 'category', + position: 'bottom', + labels: { + rotate: 0, + rotateAnchor: 'end', + filter: true, + truncate: 0 + } + }; + + class AxisConfig { + constructor(config) { + const typeDefaults = config.type === 'category' ? categoryDefaults : {}; + this._values = _.defaultsDeep({}, config, typeDefaults, defaults); + + this._values.elSelector = this._values.elSelector.replace('{pos}', this._values.position); + this._values.rootEl = this._values.vis.el; + + this.data = this._values.data; + if (this._values.type === 'category') { + this.values = this.data.xValues(); + this.ordered = this.data.get('ordered'); + } + + if (this._values.type === 'value') { + const isWiggleOrSilluete = this._values.vis._attr.mode === 'wiggle' || this._values.vis._attr.mode === 'silluete'; + // if show was not explicitly set and wiggle or silluete option was checked + if (!config.show && isWiggleOrSilluete) { + this._values.show = false; + } + + // override axisFormatter (to replicate current behaviour) + if (this.isPercentage()) { + this._values.labels.axisFormatter = d3.format('%'); + } + + if (this.isLogScale()) { + this._values.labels.filter = true; + } + } + + // horizontal axis with ordinal scale should have labels rotated (so we can fit more) + // unless explicitly overriden by user + if (this.isHorizontal() && this.isOrdinal()) { + this._values.labels.filter = config.labels.filter || false; + this._values.labels.rotate = config.labels.rotate || 70; + } + }; + + get(property, defaults = null) { + if (_.has(this._values, property)) { + return _.get(this._values, property, defaults); + } else { + throw new Error(`Accessing invalid config property: ${property}`); + return defaults; + } + }; + + set(property, value) { + return _.set(this._values, property, value); + }; + + isHorizontal() { + return (this._values.position === 'top' || this._values.position === 'bottom'); + }; + + isOrdinal() { + return !!this.values && (!this.isTimeDomain()); + }; + + isTimeDomain() { + return this.ordered && this.ordered.date; + }; + + isPercentage() { + return (this._values.vis._attr.mode === 'percentage'); + }; + + isUserDefined() { + return (this._values.scale.setYExtents); + }; + + isYExtents() { + return (this._values.scale.defaultYExtents); + }; + + isLogScale() { + return this.getScaleType() === 'log'; + }; + + getScaleType() { + return this._values.scale.type; + }; + } + + return AxisConfig; +} diff --git a/src/ui/public/vislib/lib/axis_labels.js b/src/ui/public/vislib/lib/axis_labels.js index 73e72dd59e2bd..8aad9c365f54f 100644 --- a/src/ui/public/vislib/lib/axis_labels.js +++ b/src/ui/public/vislib/lib/axis_labels.js @@ -1,42 +1,11 @@ import d3 from 'd3'; import $ from 'jquery'; import _ from 'lodash'; -import ErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; export default function AxisLabelsFactory(Private) { - - const ErrorHandler = Private(ErrorHandlerProvider); - const defaults = { - show: true, - rotate: 0, - rotateAnchor: 'center', - filter: false, - color: '#ddd', - font: '"Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif', // TODO - fontSize: '8pt', - truncate: 100 - }; - - /** - * Appends axis title(s) to the visualization - * - * @class AxisLabels - * @constructor - * @param el {HTMLElement} DOM element - * @param xTitle {String} X-axis title - * @param yTitle {String} Y-axis title - */ - - class AxisLabels extends ErrorHandler { - constructor(axis, attr) { - super(); - _.extend(this, defaults, attr); - this.axis = axis; - - // horizontal axis with ordinal scale should have labels rotated (so we can fit more) - if (this.axis.isHorizontal() && this.axis.scale.isOrdinal()) { - this.filter = attr && attr.filter ? attr.filter : false; - this.rotate = attr && attr.rotate ? attr.rotate : 70; - } + class AxisLabels { + constructor(config, scale) { + this.config = config; + this.axisScale = scale; } render(selection) { @@ -44,33 +13,34 @@ export default function AxisLabelsFactory(Private) { }; rotateAxisLabels() { - const self = this; + const config = this.config; return function (selection) { const text = selection.selectAll('.tick text'); - if (self.rotate) { + if (config.get('labels.rotate')) { text .style('text-anchor', function () { - return self.rotateAnchor === 'center' ? 'center' : 'end'; + return config.get('labels.rotateAnchor') === 'center' ? 'center' : 'end'; }) .attr('dy', function () { - if (self.axis.isHorizontal()) { - if (self.axis.position === 'top') return '-0.9em'; + if (config.isHorizontal()) { + if (config.get('position') === 'top') return '-0.9em'; else return '0.3em'; } return '0'; }) .attr('dx', function () { - return self.axis.isHorizontal() ? '-0.9em' : '0'; + return config.isHorizontal() ? '-0.9em' : '0'; }) .attr('transform', function rotate(d, j) { - if (self.rotateAnchor === 'center') { + let rotateDeg = config.get('labels.rotate'); + if (config.get('labels.rotateAnchor') === 'center') { const coord = text[0][j].getBBox(); const transX = ((coord.x) + (coord.width / 2)); const transY = ((coord.y) + (coord.height / 2)); - return `rotate(${self.rotate}, ${transX}, ${transY})`; + return `rotate(${rotateDeg}, ${transX}, ${transY})`; } else { - const rotateDeg = self.axis.position === 'top' ? self.rotate : -self.rotate; + rotateDeg = config.get('position') === 'top' ? rotateDeg : -rotateDeg; return `rotate(${rotateDeg})`; } }); @@ -99,39 +69,39 @@ export default function AxisLabelsFactory(Private) { truncateLabels() { const self = this; + const config = this.config; return function (selection) { + if (!config.get('labels.truncate')) return; + selection.selectAll('.tick text') .text(function () { // TODO: add title to trancuated labels - return self.truncateLabel(this, self.truncate); + return self.truncateLabel(this, config.get('labels.truncate')); }); }; }; filterAxisLabels() { const self = this; - let startX = 0; - let maxW; - let par; - let myX; - let myWidth; - let halfWidth; + const config = this.config; + let startPos = 0; let padding = 1.1; return function (selection) { - if (!self.filter) return; + if (!config.get('labels.filter')) return; selection.selectAll('.tick text') .text(function (d) { - par = d3.select(this.parentNode).node(); - myX = self.axis.scale.scale(d); - myWidth = par.getBBox().width * padding; - halfWidth = myWidth / 2; - maxW = $(self.axis.vis.el).find(self.axis.elSelector).width(); - - if ((startX + halfWidth) < myX && maxW > (myX + halfWidth)) { - startX = myX + halfWidth; - return self.axis.axisFormatter(d); + const par = d3.select(this.parentNode).node(); + const el = $(config.get('rootEl')).find(config.get('elSelector')); + const maxSize = config.isHorizontal() ? el.width() : el.height(); + const myPos = config.isHorizontal() ? self.axisScale.scale(d) : maxSize - self.axisScale.scale(d); + const mySize = (config.isHorizontal() ? par.getBBox().width : par.getBBox().height) * padding; + const halfSize = mySize / 2; + + if ((startPos + halfSize) < myPos && maxSize > (myPos + halfSize)) { + startPos = myPos + halfSize; + return this.innerHTML; } else { d3.select(this.parentNode).remove(); } @@ -141,16 +111,17 @@ export default function AxisLabelsFactory(Private) { draw() { const self = this; + const config = this.config; return function (selection) { selection.each(function () { selection.selectAll('text') .attr('style', function () { const currentStyle = d3.select(this).attr('style'); - return `${currentStyle} font-size: ${self.fontSize};`; + return `${currentStyle} font-size: ${config.get('labels.fontSize')};`; }); //.attr('x', -3 - parseInt(self.style.lineWidth) / 2 - parseInt(self.style.tickLength)); - if (!self.show) selection.selectAll('test').attr('style', 'display: none;'); + if (!config.get('labels.show')) selection.selectAll('test').attr('style', 'display: none;'); selection.call(self.truncateLabels()); selection.call(self.rotateAxisLabels()); diff --git a/src/ui/public/vislib/lib/axis_scale.js b/src/ui/public/vislib/lib/axis_scale.js index adddca6bfca84..fbe150cde790b 100644 --- a/src/ui/public/vislib/lib/axis_scale.js +++ b/src/ui/public/vislib/lib/axis_scale.js @@ -1,99 +1,53 @@ import d3 from 'd3'; -import $ from 'jquery'; import _ from 'lodash'; import moment from 'moment'; import errors from 'ui/errors'; -import ErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; export default function AxisScaleFactory(Private) { - - const ErrorHandler = Private(ErrorHandlerProvider); - - /** - * Appends axis title(s) to the visualization - * - * @class AxisScale - * @constructor - * @param el {HTMLElement} DOM element - * @param xTitle {String} X-axis title - * @param yTitle {String} Y-axis title - */ - class AxisScale extends ErrorHandler { - constructor(axis, attr) { - super(); - this.attr = attr; - this.axis = axis; - }; - - isPercentage() { - return (this.axis._attr.mode === 'percentage'); - }; - - isUserDefined() { - return (this.axis._attr.setYExtents); - }; - - isYExtents() { - return (this.axis._attr.defaultYExtents); - }; - - isOrdinal() { - return !!this.axis.values && (!this.isTimeDomain()); - }; - - isTimeDomain() { - return this.axis.ordered && this.axis.ordered.date; - }; - - isLogScale() { - return this.attr.scale === 'log'; + class AxisScale { + constructor(config, data) { + this.config = config; + this.data = data; + + if (this.config.get('type') === 'category') { + this.values = data.xValues(); + this.ordered = data.get('ordered'); + } }; getScaleType() { - return this.attr.scale; + return this.config.getScaleType(); }; validateUserExtents(domain) { + const config = this.config; return domain.map((val) => { val = parseInt(val, 10); if (isNaN(val)) throw new Error(val + ' is not a valid number'); - if (this.axis.isPercentage() && this.axis._attr.setYExtents) return val / 100; + if (config.isPercentage() && config.isUserDefined()) return val / 100; return val; }); }; - /** - * Returns D3 time domain - * - * @method getTimeDomain - * @param scale {Function} D3 scale function - * @param data {Array} - * @returns {*} D3 scale function - */ getTimeDomain(data) { return [this.minExtent(data), this.maxExtent(data)]; }; minExtent(data) { - return this.calculateExtent(data || this.axis.values, 'min'); + return this.calculateExtent(data || this.values, 'min'); }; maxExtent(data) { - return this.calculateExtent(data || this.axis.values, 'max'); + return this.calculateExtent(data || this.values, 'max'); }; - /** - * - * @param data - * @param extent - */ calculateExtent(data, extent) { - const ordered = this.axis.ordered; + const ordered = this.ordered; const opts = [ordered[extent]]; let point = d3[extent](data); - if (this.axis.expandLastBucket && extent === 'max') { + if (this.config.get('scale.expandLastBucket') && extent === 'max') { point = this.addInterval(point); } opts.push(point); @@ -105,38 +59,16 @@ export default function AxisScaleFactory(Private) { }, [])); }; - /** - * Add the interval to a point on the x axis, - * this properly adds dates if needed. - * - * @param {number} x - a value on the x-axis - * @returns {number} - x + the ordered interval - */ addInterval(x) { return this.modByInterval(x, +1); }; - /** - * Subtract the interval to a point on the x axis, - * this properly subtracts dates if needed. - * - * @param {number} x - a value on the x-axis - * @returns {number} - x - the ordered interval - */ subtractInterval(x) { return this.modByInterval(x, -1); }; - /** - * Modify the x value by n intervals, properly - * handling dates if needed. - * - * @param {number} x - a value on the x-axis - * @param {number} n - the number of intervals - * @returns {number} - x + n intervals - */ modByInterval(x, n) { - const ordered = this.axis.ordered; + const ordered = this.ordered; if (!ordered) return x; const interval = ordered.interval; if (!interval) return x; @@ -156,23 +88,25 @@ export default function AxisScaleFactory(Private) { }; getExtents() { - if (this.isTimeDomain()) return this.getTimeDomain(this.axis.values); - if (this.isOrdinal()) return this.axis.values; + if (this.config.get('type') === 'category') { + if (this.config.isTimeDomain()) return this.getTimeDomain(this.values); + if (this.config.isOrdinal()) return this.values; + } - const min = this.axis.min || this.axis.data.getYMin(); - const max = this.axis.max || this.axis.data.getYMax(); + const min = this.config.get('scale.min') || this.data.getYMin(); + const max = this.config.get('scale.max') || this.data.getYMax(); const domain = [min, max]; - if (this.isUserDefined()) return this.validateUserExtents(domain); - if (this.isYExtents()) return domain; - if (this.isLogScale()) return this.logDomain(min, max); + if (this.config.isUserDefined()) return this.validateUserExtents(domain); + if (this.config.isYExtents()) return domain; + if (this.config.isLogScale()) return this.logDomain(min, max); return [Math.min(0, min), Math.max(0, max)]; }; getRange(length) { - if (this.axis.isHorizontal()) { - return !this.axis.inverted ? [0, length] : [length, 0]; + if (this.config.isHorizontal()) { + return !this.config.get('scale.inverted') ? [0, length] : [length, 0]; } else { - return this.axis.inverted ? [0, length] : [length, 0]; + return this.config.get('scale.inverted') ? [0, length] : [length, 0]; } }; @@ -184,58 +118,37 @@ export default function AxisScaleFactory(Private) { throw new errors.InvalidLogScaleValues(); }; - /** - * Return the domain for log scale, i.e. the extent of the log scale. - * Log scales must begin at 1 since the log(0) = -Infinity - * - * @param scale - * @param yMin - * @param yMax - * @returns {*[]} - */ logDomain(min, max) { if (min < 0 || max < 0) return this.throwLogScaleValuesError(); return [1, max]; }; - /** - * Returns the appropriate D3 scale - * - * @param fnName {String} D3 scale - * @returns {*} - */ - getScaleType(fnName) { + getD3Scale(fnName) { if (fnName === 'square root') fnName = 'sqrt'; // Rename 'square root' to 'sqrt' fnName = fnName || 'linear'; - if (this.isTimeDomain()) return d3.time.scale.utc(); // allow time scale - if (this.isOrdinal()) return d3.scale.ordinal(); + if (this.config.isTimeDomain()) return d3.time.scale.utc(); // allow time scale + if (this.config.isOrdinal()) return d3.scale.ordinal(); if (typeof d3.scale[fnName] !== 'function') return this.throwCustomError('Axis.getScaleType: ' + fnName + ' is not a function'); return d3.scale[fnName](); }; - /** - * Creates the d3 y scale function - * - * @method getscale - * @param length {Number} DOM Element height - * @returns {D3.Scale.QuantitiveScale|*} D3 scale function - */ getScale(length) { - const scale = this.getScaleType(this.attr.scale); + const config = this.config; + const scale = this.getD3Scale(config.getScaleType()); const domain = this.getExtents(); const range = this.getRange(length); this.scale = scale.domain(domain); - if (this.isOrdinal()) { + if (config.isOrdinal()) { this.scale.rangeBands(range, 0.1); } else { this.scale.range(range); } - if (!this.isUserDefined() && !this.isOrdinal() && !this.isTimeDomain()) this.scale.nice(); // round extents when not user defined + if (!config.isUserDefined() && !config.isYExtents() && !config.isOrdinal() && !config.isTimeDomain()) this.scale.nice(); // Prevents bars from going off the chart when the y extents are within the domain range - if (this.axis._attr.type === 'histogram' && this.scale.clamp) this.scale.clamp(true); + if (config.get('vis._attr.type') === 'histogram' && this.scale.clamp) this.scale.clamp(true); this.validateScale(this.scale); diff --git a/src/ui/public/vislib/lib/axis_title.js b/src/ui/public/vislib/lib/axis_title.js index 49a2ce2868b06..b5d5e9f76f878 100644 --- a/src/ui/public/vislib/lib/axis_title.js +++ b/src/ui/public/vislib/lib/axis_title.js @@ -1,51 +1,21 @@ import d3 from 'd3'; import $ from 'jquery'; import _ from 'lodash'; -import ErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; export default function AxisTitleFactory(Private) { - const ErrorHandler = Private(ErrorHandlerProvider); - const defaults = { - title: '', - elSelector: '.axis-wrapper-{pos} .axis-title' - }; - - /** - * Appends axis title(s) to the visualization - * - * @class AxisTitle - * @constructor - * @param el {HTMLElement} DOM element - * @param xTitle {String} X-axis title - * @param yTitle {String} Y-axis title - */ - class AxisTitle extends ErrorHandler { - constructor(axis, attr) { - super(); - _.extend(this, defaults, attr); - this.axis = axis; - this.elSelector = this.elSelector.replace('{pos}', axis.position); + class AxisTitle { + constructor(config) { + this.config = config; + this.elSelector = this.config.get('title.elSelector').replace('{pos}', this.config.get('position')); } - /** - * Renders both x and y axis titles - * - * @method render - * @returns {HTMLElement} DOM Element with axis titles - */ - render(selection) { - d3.select(this.axis.vis.el).selectAll(this.elSelector).call(this.draw()); + render() { + d3.select(this.config.get('rootEl')).selectAll(this.elSelector).call(this.draw()); }; - /** - * Appends an SVG with title text - * - * @method draw - * @param title {String} Axis title - * @returns {Function} Appends axis title to a D3 selection - */ draw() { const self = this; + const config = this.config; return function (selection) { selection.each(function () { @@ -54,29 +24,26 @@ export default function AxisTitleFactory(Private) { const width = $(el).width(); const height = $(el).height(); - self.validateWidthandHeight(width, height); - const svg = div.append('svg') .attr('width', width) .attr('height', height); - const bbox = svg.append('text') .attr('transform', function () { - if (self.axis.isHorizontal()) { + if (config.isHorizontal()) { return 'translate(' + width / 2 + ',11)'; } return 'translate(11,' + height / 2 + ') rotate(270)'; }) .attr('text-anchor', 'middle') - .text(self.title) + .text(config.get('title.text')) .node() .getBBox(); - if (self.axis.isHorizontal()) { + if (config.isHorizontal()) { svg.attr('height', bbox.height); } else { - svg.attr('width', bbox.width); + svg.attr('width', bbox.height); } }); }; diff --git a/src/ui/public/vislib/lib/dispatch.js b/src/ui/public/vislib/lib/dispatch.js index f0888c6e9e46e..bcfc8a7419ecf 100644 --- a/src/ui/public/vislib/lib/dispatch.js +++ b/src/ui/public/vislib/lib/dispatch.js @@ -25,8 +25,8 @@ export default function DispatchClass(Private) { * @param d {Object} Data point * @param i {Number} Index number of data point * @returns {{value: *, point: *, label: *, color: *, pointIndex: *, - * series: *, config: *, data: (Object|*), - * e: (d3.event|*), handler: (Object|*)}} Event response object + * series: *, config: *, data: (Object|*), + * e: (d3.event|*), handler: (Object|*)}} Event response object */ eventResponse(d, i) { const datum = d._input || d; @@ -166,7 +166,7 @@ export default function DispatchClass(Private) { const hasTimeField = this.handler.vis._attr.hasTimeField; return Boolean(hasTimeField && xAxis.ordered && xScale && _.isFunction(xScale.invert)); - }; + }; /** * Determine if brushing is currently enabled @@ -263,43 +263,43 @@ export default function DispatchClass(Private) { // Brush scale const brush = d3.svg.brush() - .x(xScale) - .on('brushend', function brushEnd() { + .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 - const data = d3.select(this).data()[0]; - const isTimeSeries = (data.ordered && data.ordered.date); + // Assumes data is selected at the chart level + // In this case, the number of data objects should always be 1 + const data = d3.select(this).data()[0]; + const isTimeSeries = (data.ordered && data.ordered.date); - // Allows for brushing on d3.scale.ordinal() - const selected = xScale.domain().filter(function (d) { - return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]); - }); - const range = isTimeSeries ? brush.extent() : selected; + // Allows for brushing on d3.scale.ordinal() + const selected = xScale.domain().filter(function (d) { + return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]); + }); + const range = isTimeSeries ? brush.extent() : selected; - return self.emit('brush', { - range: range, - config: attr, - e: d3.event, - data: data + return self.emit('brush', { + range: range, + config: attr, + e: d3.event, + data: data + }); }); - }); // if `addBrushing` is true, add brush canvas if (self.listenerCount('brush')) { svg.insert('g', 'g') - .attr('class', 'brush') - .call(brush) - .call(function (brushG) { - // hijack the brush start event to filter out right/middle clicks - const brushHandler = brushG.on('mousedown.brush'); - if (!brushHandler) return; // touch events in use - brushG.on('mousedown.brush', function () { - if (validBrushClick(d3.event)) brushHandler.apply(this, arguments); - }); - }) - .selectAll('rect') - .attr('height', height - margin.top - margin.bottom); + .attr('class', 'brush') + .call(brush) + .call(function (brushG) { + // hijack the brush start event to filter out right/middle clicks + const brushHandler = brushG.on('mousedown.brush'); + if (!brushHandler) return; // touch events in use + brushG.on('mousedown.brush', function () { + if (validBrushClick(d3.event)) brushHandler.apply(this, arguments); + }); + }) + .selectAll('rect') + .attr('height', height - margin.top - margin.bottom); return brush; } diff --git a/src/ui/public/vislib/lib/handler/types/point_series.js b/src/ui/public/vislib/lib/handler/types/point_series.js index d6a554160528c..5f41815fb5ee8 100644 --- a/src/ui/public/vislib/lib/handler/types/point_series.js +++ b/src/ui/public/vislib/lib/handler/types/point_series.js @@ -42,12 +42,14 @@ export default function ColumnHandler(Private) { type: 'category', vis: vis, data: data, - values: data.xValues(), - ordered: data.get('ordered'), - axisFormatter: data.get('xAxisFormatter'), - expandLastBucket: opts.expandLastBucket, - axisTitle: { - title: data.get('xAxisLabel') + labels: { + axisFormatter: data.data.xAxisFormatter || data.get('xAxisFormatter') + }, + scale: { + expandLastBucket: opts.expandLastBucket + }, + title: { + text: data.get('xAxisLabel') } }) ], @@ -58,11 +60,18 @@ export default function ColumnHandler(Private) { type: 'value', vis: vis, data: data, - min : isUserDefinedYAxis ? vis._attr.yAxis.min : 0, - max : isUserDefinedYAxis ? vis._attr.yAxis.max : 0, - axisFormatter: data.get('yAxisFormatter'), - axisTitle: { - title: data.get('yAxisLabel') + scale: { + type: vis._attr.scale, + setYExtents: vis._attr.setYExtents, + defaultYExtents: vis._attr.defaultYExtents, + min : isUserDefinedYAxis ? vis._attr.yAxis.min : undefined, + max : isUserDefinedYAxis ? vis._attr.yAxis.max : undefined, + }, + labels: { + axisFormatter: data.data.yAxisFormatter || data.get('yAxisFormatter') + }, + title: { + text: data.get('yAxisLabel') } }) ] diff --git a/src/ui/public/vislib/visualizations/line_chart.js b/src/ui/public/vislib/visualizations/line_chart.js index b62f521b8340e..248ea2459132d 100644 --- a/src/ui/public/vislib/visualizations/line_chart.js +++ b/src/ui/public/vislib/visualizations/line_chart.js @@ -268,7 +268,7 @@ export default function LineChartFactory(Private) { const margin = this._attr.margin; const elWidth = this._attr.width = $elem.width(); const elHeight = this._attr.height = $elem.height(); - const scaleType = this.handler.valueAxes[0].scale.getScaleType(); + const scaleType = this.handler.valueAxes[0].axisScale.getScaleType(); const yScale = this.handler.valueAxes[0].getScale(); const xScale = this.handler.categoryAxes[0].getScale(); const minWidth = 20;