diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js index 76b4b00d2..aae2a4062 100644 --- a/src/elements/element.interval.js +++ b/src/elements/element.interval.js @@ -76,7 +76,11 @@ export class Interval extends Element { CartesianGrammar.decorator_dynamic_size, CartesianGrammar.decorator_color, CartesianGrammar.decorator_label, - config.adjustPhase && enableDistributeEvenly && CartesianGrammar.decorator_size_distribute_evenly, + (config.adjustPhase && enableDistributeEvenly && CartesianGrammar.decorator_size_distribute_evenly), + (config.adjustPhase + && enableDistributeEvenly + && this.config.guide.prettify + && CartesianGrammar.avoidBaseScaleOverflow), config.adjustPhase && enableStack && CartesianGrammar.adjustYScale ].concat(config.transformModel || []); diff --git a/src/models/cartesian-grammar.js b/src/models/cartesian-grammar.js index 8c78fce10..3713a30f6 100644 --- a/src/models/cartesian-grammar.js +++ b/src/models/cartesian-grammar.js @@ -452,4 +452,61 @@ export class CartesianGrammar { }, [])); } + + static avoidBaseScaleOverflow(model, {dataSource}) { + if (model.scaleX.discrete) { + return {}; + } + + var plannedMaxSize; + model.scaleSize.fixup((prev) => { + plannedMaxSize = prev.maxSize; + return prev; + }); + + if (plannedMaxSize <= 10) { + return {}; + } + + var xs = dataSource + .map((row) => model.xi(row)) + .sort(((a, b) => (a - b))); + + var domain = model.scaleX.domain(); + var length = Math.abs(model.scaleX.value(domain[1]) - model.scaleX.value(domain[0])); + var koeff = ((domain[1] - domain[0]) / length); + + var lPad = Math.abs(Math.min(0, (xs[0] - plannedMaxSize / 2))); + var rPad = Math.abs(Math.min(0, (length - (xs[xs.length - 1] + plannedMaxSize / 2)))); + + var lxPad = model.flip ? rPad : lPad; + var rxPad = model.flip ? lPad : rPad; + + var lVal = domain[0] - (lxPad * koeff); + var rVal = domain[1] - (-1 * rxPad * koeff); + + model.scaleX.fixup((prev) => { + var next = {}; + if (!prev.fixed) { + next.fixed = true; + next.min = lVal; + next.max = rVal; + } else { + if (prev.min > lVal) { + next.min = lVal; + } + + if (prev.max < rVal) { + next.max = rVal; + } + } + + return next; + }); + + var linearlyScaledMaxSize = plannedMaxSize * (1 - ((lPad + rPad) / length)) - 1; + model.scaleSize.fixup(() => ({maxSize: linearlyScaledMaxSize})); + + return {}; + } } diff --git a/test/grammar-rules.test.js b/test/grammar-rules.test.js new file mode 100644 index 000000000..ea99ad492 --- /dev/null +++ b/test/grammar-rules.test.js @@ -0,0 +1,86 @@ +define(function (require) { + + var expect = require('chai').expect; + var CartesianGrammar = require('src/models/cartesian-grammar').CartesianGrammar; + var SizeScale = require('src/scales/size').SizeScale; + var LinearScale = require('src/scales/linear').LinearScale; + var OrdinalScale = require('src/scales/ordinal').OrdinalScale; + + describe('Grammar', function () { + + var data = [ + {x: 0.0, y: 0, s: 1, x_ordinal: 'A'}, + {x: 0.5, y: 50, s: 0, x_ordinal: 'B'}, + {x: 1.0, y: 100, s: 1, x_ordinal: 'C'} + ]; + + var xSrc = { + part: function () { + return data; + }, + full: function () { + return data; + } + }; + + it('should support avoidBaseScaleOverflow rule (continues scale)', function () { + var xConfig = {dim: 'x'}; + var sConfig = {dim: 's', minSize: 1, maxSize: 40}; + var model = { + scaleX: new LinearScale(xSrc, xConfig).create([0, 100]), + scaleSize: new SizeScale(xSrc, sConfig).create(), + xi: (row) => model.scaleX.value(row[model.scaleX.dim]) + }; + + CartesianGrammar.avoidBaseScaleOverflow(model, {dataSource: data}); + + model.scaleX.commit(); + model.scaleSize.commit(); + + expect(xConfig.min).to.equal(-0.2); + expect(xConfig.max).to.equal(1.2); + expect(sConfig.minSize).to.equal(1); + expect(sConfig.maxSize).to.equal(23); + }); + + it('should ignore avoidBaseScaleOverflow rule for ordinal scale', function () { + var xConfig = {dim: 'x_ordinal'}; + var xConfigOriginal = JSON.stringify(xConfig); + var sConfig = {dim: 's', minSize: 1, maxSize: 40}; + var sConfigOriginal = JSON.stringify(sConfig); + var model = { + scaleX: new OrdinalScale(xSrc, xConfig).create([0, 100]), + scaleSize: new SizeScale(xSrc, sConfig).create(), + xi: (row) => model.scaleX.value(row[model.scaleX.dim]) + }; + + CartesianGrammar.avoidBaseScaleOverflow(model, {dataSource: data}); + + model.scaleX.commit(); + model.scaleSize.commit(); + + expect(JSON.stringify(xConfig)).to.equal(xConfigOriginal); + expect(JSON.stringify(sConfig)).to.equal(sConfigOriginal); + }); + + it('should ignore avoidBaseScaleOverflow rule when max size is less than 10', function () { + var xConfig = {dim: 'x'}; + var xConfigOriginal = JSON.stringify(xConfig); + var sConfig = {dim: 's', minSize: 1, maxSize: 10}; + var sConfigOriginal = JSON.stringify(sConfig); + var model = { + scaleX: new LinearScale(xSrc, xConfig).create([0, 100]), + scaleSize: new SizeScale(xSrc, sConfig).create(), + xi: (row) => model.scaleX.value(row[model.scaleX.dim]) + }; + + CartesianGrammar.avoidBaseScaleOverflow(model, {dataSource: data}); + + model.scaleX.commit(); + model.scaleSize.commit(); + + expect(JSON.stringify(xConfig)).to.equal(xConfigOriginal); + expect(JSON.stringify(sConfig)).to.equal(sConfigOriginal); + }); + }); +}); \ No newline at end of file