Permalink
Browse files

Merge pull request #10 from tenXer/feature/axis-min-max

Feature: optional axis min and max values
  • Loading branch information...
2 parents 1df707b + 39e42e5 commit d434dd6684cfb2c164e75477f73a26b5fb958d17 @jmullan jmullan committed Dec 21, 2012
Showing with 180 additions and 66 deletions.
  1. +47 −11 docs/docs.json
  2. +11 −0 lib/chart.js
  3. +50 −37 lib/scales.js
  4. +17 −1 test/chart.test.js
  5. +55 −17 test/scales.test.js
View
@@ -28,47 +28,67 @@
"desc": "Callback behavior for a user clicking a data point."
},
"axisPaddingTop": {
- "type": "int",
+ "type": "Number",
"default": 0,
"desc": "Amount of space between the top of the chart and the top value on the y-scale."
},
"axisPaddingRight": {
- "type": "int",
+ "type": "Number",
"default": 0,
"desc": "Amount of space between the right side of the chart and the highest value on the x-scale."
},
"axisPaddingBottom": {
- "type": "int",
+ "type": "Number",
"default": 5,
"desc": "Amount of space between the bottom of the chart and the lowest value on the y-scale."
},
"axisPaddingLeft": {
- "type": "int",
+ "type": "Number",
"default": 20,
"desc": "Amount of space between the left side of the chart and the lowest value on the x-scale."
},
+ "xMin": {
+ "type": "Number",
+ "default": "null",
+ "desc": "Minimum allowed value on the xScale. If <code>null</code>, uses the data's min value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales. May be overrided using the <a href=\"#method-setData\">setData</a> method with the <a href=\"#data-xMin\">xMin</a> data format key."
+ },
+ "xMax": {
+ "type": "Number",
+ "default": "null",
+ "desc": "Maximum allowed value on the xScale. If <code>null</code>, uses the data's max value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales. May be overrided using the <a href=\"#method-setData\">setData</a> method with the <a href=\"#data-xMax\">xMax</a> data format key."
+ },
+ "yMin": {
+ "type": "Number",
+ "default": "null",
+ "desc": "Minimum allowed value on the yScale. If <code>null</code>, uses the data's min value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales. May be overrided using the <a href=\"#method-setData\">setData</a> method with the <a href=\"#data-yMin\">yMin</a> data format key."
+ },
+ "yMax": {
+ "type": "Number",
+ "default": "null",
+ "desc": "Maximum allowed value on the yScale. If <code>null</code>, uses the data's max value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales. May be overrided using the <a href=\"#method-setData\">setData</a> method with the <a href=\"#data-yMax\">yMax</a> data format key."
+ },
"paddingTop": {
- "type": "int",
+ "type": "Number",
"default": 0,
"desc": "Amount of space from the top edge of the svg element to the beginning of the <var>axisPaddingTop</var>."
},
"paddingRight": {
- "type": "int",
+ "type": "Number",
"default": 0,
"desc": "Amount of space from the right edge of the svg element to the beginning of the <var>axisPaddingRight</var>."
},
"paddingBottom": {
- "type": "int",
+ "type": "Number",
"default": 20,
"desc": "Allows space for the x-axis scale. Controls the amount of space from the bottom edge of the svg element to the beginning of the <var>axisPaddingBottom</var>."
},
"paddingLeft": {
- "type": "int",
+ "type": "Number",
"default": 60,
"desc": "Allows space for the y-axis scale. Amount of space from the left edge of the svg element to the beginning of the <var>axisPaddingLeft</var>."
},
"tickHintX": {
- "type": "int",
+ "type": "Number",
"default": 10,
"desc": "The amount of ticks that you would <em>like</em> to have displayed on the x-axis. Note: this is merely a guide and your results will likely vary."
},
@@ -81,7 +101,7 @@
}
},
"tickHintY": {
- "type": "int",
+ "type": "Number",
"default": 10,
"desc": "The amount of ticks that you would <em>like</em> to have displayed on the y-axis. Note: this is merely a guide and your results will likely vary."
},
@@ -142,7 +162,7 @@
}
},
"timing": {
- "type": "int",
+ "type": "Number",
"default": 750,
"desc": "The amount of time, in milliseconds, to transition during draw/update."
},
@@ -199,6 +219,22 @@
"desc": "Scale type to use along the y-axis (vertical).",
"options": ["ordinal", "linear", "time", "exponential"]
},
+ "xMin": {
+ "type": "Number",
+ "desc": "Minimum allowed value on the xScale. If <code>null</code>, uses the data's min value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales."
+ },
+ "xMax": {
+ "type": "Number",
+ "desc": "Maximum allowed value on the xScale. If <code>null</code>, uses the data's max value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales."
+ },
+ "yMin": {
+ "type": "Number",
+ "desc": "Minimum allowed value on the yScale. If <code>null</code>, uses the data's min value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales."
+ },
+ "yMax": {
+ "type": "Number",
+ "desc": "Maximum allowed value on the yScale. If <code>null</code>, uses the data's max value, logically padded for aesthetics. Does not affect <code>ordinal</code> scales."
+ },
"type": {
"optional": true,
"type": "string",
View
@@ -23,6 +23,12 @@ var emptyData = [[]],
tickHintY: 10,
tickFormatY: function (y) { return y; },
+ // Min/Max Axis Values
+ xMin: null,
+ xMax: null,
+ yMin: null,
+ yMax: null,
+
// Pre-format input data
dataFormatX: function (x) { return x; },
dataFormatY: function (y) { return y; },
@@ -221,6 +227,11 @@ _.defaults(xChart.prototype, {
break;
}
+ o.xMin = data.xMin || o.xMin;
+ o.xMax = data.xMax || o.xMax;
+ o.yMin = data.yMin || o.yMin;
+ o.yMax = data.yMax || o.yMax;
+
if (self._vis) {
self._destroy(self._vis, self._mainStorage);
}
View
@@ -14,27 +14,6 @@ function _getDomain(data, axis) {
.sort(d3.ascending);
}
-function _extendDomain(domain, axis) {
- var min = domain[0],
- max = domain[1],
- diff,
- e;
-
- if (min === max) {
- e = Math.max(Math.round(min / 10), 4);
- min -= e;
- max += e;
- }
-
- diff = max - min;
- min = (min) ? min - (diff / 10) : min;
- min = (domain[0] > 0) ? Math.max(min, 0) : min;
- max = (max) ? max + (diff / 10) : max;
- max = (domain[1] < 0) ? Math.min(max, 0) : max;
-
- return [min, max];
-}
-
function ordinal(data, axis, bounds, spacing) {
spacing = spacing || defaultSpacing;
var domain = _getDomain(data, axis);
@@ -44,21 +23,13 @@ function ordinal(data, axis, bounds, spacing) {
}
function linear(extents, bounds, axis) {
- if (axis === 'y') {
- extents = _extendDomain(extents, axis);
- }
-
return d3.scale.linear()
.domain(extents)
.nice()
.rangeRound(bounds);
}
function exponential(extents, bounds, axis) {
- if (axis === 'y') {
- extents = _extendDomain(extents, axis);
- }
-
return d3.scale.pow()
.exponent(0.65)
.domain(extents)
@@ -72,22 +43,64 @@ function time(extents, bounds) {
.range(bounds);
}
-function _getExtents(data, key) {
- var nData = _.chain(data)
- .pluck('data')
- .flatten()
- .value();
+function _extendDomain(domain, axis) {
+ var min = domain[0],
+ max = domain[1],
+ diff,
+ e;
+
+ if (min === max) {
+ e = Math.max(Math.round(min / 10), 4);
+ min -= e;
+ max += e;
+ }
- return {
+ diff = max - min;
+ min = (min) ? min - (diff / 10) : min;
+ min = (domain[0] > 0) ? Math.max(min, 0) : min;
+ max = (max) ? max + (diff / 10) : max;
+ max = (domain[1] < 0) ? Math.min(max, 0) : max;
+
+ return [min, max];
+}
+
+function _getExtents(options, data, xType, yType) {
+ var extents,
+ nData = _.chain(data)
+ .pluck('data')
+ .flatten()
+ .value();
+
+ extents = {
x: d3.extent(nData, function (d) { return d.x; }),
y: d3.extent(nData, function (d) { return d.y; })
};
+
+ _.each([xType, yType], function (type, i) {
+ var axis = (i) ? 'y' : 'x',
+ extended;
+ extents[axis] = d3.extent(nData, function (d) { return d[axis]; });
+ if (type === 'ordinal') {
+ return;
+ }
+
+ _.each([axis + 'Min', axis + 'Max'], function (minMax, i) {
+ if (type !== 'time') {
+ extended = _extendDomain(extents[axis]);
+ }
+ extents[axis][i] = (options.hasOwnProperty(minMax) &&
+ options[minMax] !== null) ? options[minMax]
+ : (type !== 'time') ? extended[i] : extents[axis][i];
+ });
+ });
+
+ return extents;
}
function xy(self, data, xType, yType) {
- var extents = _getExtents(data),
+ var o = self._options,
+ extents = _getExtents(o, data, xType, yType),
scales = {},
- o = self._options,
horiz = [o.axisPaddingLeft, self._width],
vert = [self._height, o.axisPaddingTop],
xScale,
View
@@ -61,7 +61,11 @@
tickHintX: 10,
tickHintY: 10,
timing: 750,
- interpolation: 'monotone'
+ interpolation: 'monotone',
+ xMin: null,
+ xMax: null,
+ yMin: null,
+ yMax: null
});
});
@@ -252,6 +256,18 @@
expect(c._mainData[0].data[0]).to.be.eql({ x: '1taco', y: 10 });
expect(c._mainData[0].data[1]).to.be.eql({ x: '2taco', y: 20 });
});
+
+ it('allows setting x/y Min/Max onto options', function () {
+ var c = new xChart('bar', mData, container),
+ newData = _.extend({}, mData, {
+ xMin: 1,
+ xMax: 10,
+ yMin: 2,
+ yMax: 8
+ });
+ c.setData(newData);
+ expect(c._options.xMin).to.equal(newData.xMin);
+ });
});
describe('setType(type)', function () {
View
@@ -44,17 +44,10 @@
describe('Linear', function () {
it('returns a linear scale', function () {
- var s = scales.linear([10, 50], [0, 100], 'y');
+ var s = scales.linear([0, 60], [0, 100], 'y');
_testLinear(s);
});
- it('extends the domain if only 1 value', function () {
- var s = scales.linear([10, 10], [0, 100], 'y');
- expect(s.domain()).to.be.eql([5, 15]);
- s = scales.linear([100, 100], [0, 100], 'y');
- expect(s.domain()).to.be.eql([88, 112], 'uses scaled padding');
- });
-
it('does not round values', function () {
var s = scales.linear([1.1, 2.1], [0, 100], 'x');
expect(s(1.4)).to.not.equal(s(1.1));
@@ -65,9 +58,9 @@
describe('Exponential', function () {
it('returns an exponential scale', function () {
var s = scales.exponential([0, 500], [0, 100], 'y');
- expect(s(10)).to.be.eql(7); // would be 2 on linear
- expect(s(100)).to.be.eql(31); // would be 20 on linear
- expect(s(400)).to.be.eql(77); // would be 80 on linear
+ expect(s(10)).to.be.eql(8); // would be 2 on linear
+ expect(s(100)).to.be.eql(35); // would be 20 on linear
+ expect(s(400)).to.be.eql(86); // would be 80 on linear
});
});
@@ -78,17 +71,62 @@
describe('xy', function () {
it('returns multiple types', function () {
var foo = {
- _options: {
- axisPaddingLeft: 0,
- axisPaddingRight: 0
+ _options: {
+ axisPaddingLeft: 0,
+ axisPaddingRight: 0
+ },
+ _width: 100,
+ _height: 100
},
- _width: 100,
- _height: 100
- },
s = scales.xy(foo, data, 'ordinal', 'linear');
+ window.a = s;
_testOrdinal(s.x);
_testLinear(s.y);
});
+
+ it('uses xMin, xMax, yMin, and yMax in options', function () {
+ var foo = {
+ _options: {
+ xMin: -1,
+ xMax: 20,
+ yMin: 10,
+ yMax: 40
+ },
+ _width: 100,
+ _height: 100
+ },
+ data = [
+ { data: [ { x: 1, y: 50 }, { x: 1, y: 20 } ] },
+ { data: [ { x: 2, y: 50 }, { x: 2, y: 10 } ] }
+ ],
+ sOrdinal,
+ sLinear,
+ sTime;
+
+ sOrdinal = scales.xy(foo, data, 'ordinal', 'ordinal');
+ expect(sOrdinal.x.domain()).to.eql([1, 2]);
+ expect(sOrdinal.y.domain()).to.eql([10, 20, 50]);
+
+ sLinear = scales.xy(foo, data, 'linear', 'linear');
+ expect(sLinear.x.domain()).to.eql([foo._options.xMin, foo._options.xMax]);
+ expect(sLinear.y.domain()).to.eql([foo._options.yMin, foo._options.yMax]);
+
+ foo._options = {
+ xMin: new Date(2012, 1, 1),
+ xMax: new Date(2012, 10, 1),
+ yMin: new Date(2012, 2, 1),
+ yMax: new Date(2012, 11, 1)
+ };
+ data = [
+ { data: [ { x: new Date(), y: new Date() },
+ { x: new Date(), y: new Date() } ] },
+ { data: [ { x: new Date(), y: new Date() },
+ { x: new Date(), y: new Date() } ] }
+ ];
+ sTime = scales.xy(foo, data, 'time', 'time');
+ expect(sTime.x.domain()).to.eql([foo._options.xMin, foo._options.xMax]);
+ expect(sTime.y.domain()).to.eql([foo._options.yMin, foo._options.yMax]);
+ });
});
}());

0 comments on commit d434dd6

Please sign in to comment.