adding error handlers, debugging, tests

This commit is contained in:
Shelby Sturgis 2014-08-29 19:36:35 +03:00
parent 57cb421c2b
commit ff01e84f39
12 changed files with 221 additions and 192 deletions

View file

@ -90,7 +90,7 @@ div.y-axis-label {
}
.legend-ul {
list-style-type: none;
margin: 0 0 0 10px]]];
margin: 0 0 0 10px;
padding: 0;
visibility: visible;
display: flex;

View file

@ -3,8 +3,6 @@ define(function (require) {
var $ = require('jquery');
var _ = require('lodash');
var Chart = Private(require('components/vislib/modules/_chart'));
function AxisTitle(el, xTitle, yTitle) {
if (!(this instanceof AxisTitle)) {
return new AxisTitle(el, xTitle, yTitle);
@ -21,16 +19,15 @@ define(function (require) {
};
AxisTitle.prototype.draw = function (title) {
var self = this;
var div;
var width;
var height;
return function (selection) {
selection.each(function () {
div = d3.select(this);
width = $(this).width();
height = $(this).height();
var div = d3.select(this);
var width = $(this).width();
var height = $(this).height();
if (_.isNaN(height) || height <= 0 || _.isNaN(width) || width <= 0) {
throw new Error('The height and/or width of this container is too small for this chart. Height: ' + height + ', width: ' + width);
}
div.append('svg')
.attr('width', width)

View file

@ -52,6 +52,10 @@ define(function (require) {
var width = $(this).width();
var height = $(this).height();
if (_.isNaN(height) || height <= 0 || _.isNaN(width) || width <= 0) {
throw new Error('The height and/or width of this container is too small for this chart. Height: ' + height + ', width: ' + width);
}
div.append('svg')
.attr('width', width)
.attr('height', height)

View file

@ -21,10 +21,7 @@ define(function (require) {
xValue: function (d, i) { return d.x; },
yValue: function (d, i) { return d.y; },
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout'),
stack: d3.layout.stack()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.offset(this.offset)
stack: this._attr.stack.offset(this.offset)
});
}
@ -104,34 +101,15 @@ define(function (require) {
return function (selection) {
selection.each(function (data) {
if (!yScale) {
throw new Error('yScale is ' + yScale);
}
if (!xScale) {
throw new Error('xScale is ' + xScale);
}
layers = self.stackData(data);
self.validateHeightAndWidth($elem, elWidth, elHeight);
// Get the width and height
width = elWidth - margin.left - margin.right;
height = elHeight - margin.top - margin.bottom;
if (_.isNaN(width) || _.isNaN(height)) {
throw new Error('width: ' + width + '; height:' + height);
}
if (height <= margin.top + margin.bottom) {
throw new Error('The container is too small for this chart.');
}
/* Error Handler that prevents a chart from being rendered when
there are too many data points for the width of the container. */
if (width / layers.length <= 4) {
throw new Error('The container is too small for this chart.');
if (_.isNaN(width) || width < 20 || _.isNaN(height) || height < 20) {
throw new Error('The height and/or width of the chart container(s) is too small. Height: ' + height + ', width: ' + width);
}
// Create the canvas for the visualization
@ -185,23 +163,22 @@ define(function (require) {
barWidth = xScale(data.ordered.min + data.ordered.interval) - xScale(data.ordered.min);
barSpacing = barWidth * 0.25;
if (barWidth <= 1) {
throw new Error('This container is too small for this chart.');
}
return barWidth - barSpacing;
}
if (xScale.rangeBand() <= 1) {
throw new Error('This container is too small for this chart.');
}
return xScale.rangeBand();
})
.attr('y', function (d) {
var y = yScale(d.y0 + d.y);
if (!_.isNaN(y) || y < 0) {
return y;
}
throw new Error('The container is too small for this chart.');
return yScale(d.y0 + d.y);
})
.attr('height', function (d) {
var height = yScale(d.y0) - yScale(d.y0 + d.y);
if (!_.isNaN(height) || height < 0) {
return height;
}
throw new Error('The container is too small for this chart.');
return yScale(d.y0) - yScale(d.y0 + d.y);
});
bars

View file

@ -15,7 +15,7 @@ define(function (require) {
this.data = data;
}
Data.prototype.getChartData = function () {
Data.prototype.chartData = function () {
if (!this.data.series) {
var arr = this.data.rows ? this.data.rows : this.data.columns;
return _.pluck(arr);
@ -24,84 +24,21 @@ define(function (require) {
};
Data.prototype.get = function (thing) {
var data = this.getChartData();
var data = this.chartData();
// returns the first thing in the array
return _.pluck(data, thing)[0];
};
Data.prototype.flatten = function () {
if (!this.data.series) {
var arr = this.data.rows ? this.data.rows : this.data.columns;
var series = _.chain(arr).pluck('series').pluck().value();
var values = [];
var data = this.chartData();
var series = _.chain(data).pluck('series').pluck().value();
var values = [];
_(series).forEach(function (d) {
values.push(_.chain(d).flatten().pluck('values').value());
});
return values;
}
return [_.chain(this.data.series).flatten().pluck('values').value()];
};
// should be moved from here to yAxis or columnChart class
Data.prototype.stack = function (series) {
var stack = d3.layout.stack()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
})
.offset('zero');
return stack(series);
};
// should be moved to yAxis class
Data.prototype.isStacked = function () {
if (!this.data.series) {
// for loop to
var dataArr = this.data.rows ? this.data.rows : this.data.columns;
for (var i = 0; i < dataArr.length; i++) {
if (dataArr[i].series.length > 1) {
return true;
}
}
return false;
}
return this.data.series.length > 1 ? true : false;
};
// should be moved to yAxis class
Data.prototype.getYMaxValue = function () {
var flattenedData = this.flatten();
var self = this;
var arr = [];
_.forEach(flattenedData, function (series) {
arr.push(self.getYStackMax(series));
_(series).forEach(function (d) {
values.push(_.chain(d).flatten().pluck('values').value());
});
return _.max(arr);
};
// should be moved to yAxis class
Data.prototype.getYStackMax = function (series) {
var self = this;
if (this.isStacked()) {
series = this.stack(series);
}
return d3.max(series, function (data) {
return d3.max(data, function (d) {
if (self.isStacked()) {
return d.y0 + d.y;
}
return d.y;
});
});
return values;
};
Data.prototype.injectZeros = function () {

View file

@ -22,8 +22,11 @@ define(function (require) {
this.data = new Data(vis.data);
this.ChartClass = vis.ChartClass;
this._attr = _.defaults(vis._attr || {}, {
'margin' : { top: 10, right: 3, bottom: 5, left: 3 }
'margin' : { top: 10, right: 3, bottom: 5, left: 3 },
destroyFlag: false
});
// Visualization Classes
this.layout = new Layout(this.el, this.data.injectZeros());
if (this._attr.addLegend) {
@ -44,7 +47,8 @@ define(function (require) {
});
this.yAxis = new YAxis({
el: this.el,
yMax: this.data.getYMaxValue(),
chartData: this.data.chartData(),
dataArray: this.data.flatten(),
_attr: this._attr
});
this.axisTitle = new AxisTitle(this.el, this.data.get('xAxisLabel'), this.data.get('yAxisLabel'));
@ -52,10 +56,10 @@ define(function (require) {
this.layout,
this.legend,
this.tooltip,
this.chartTitle,
this.xAxis,
this.yAxis,
this.axisTitle
this.axisTitle,
this.chartTitle
];
}
@ -95,7 +99,25 @@ define(function (require) {
});
};
Handler.prototype.error = function () {};
Handler.prototype.removeAll = function (elem) {
return d3.select(elem).selectAll('*').remove();
};
Handler.prototype.error = function (message) {
// Removes the legend container
this.removeAll(this.el);
return d3.select(this.el)
.append('div')
.attr('class', 'error-wrapper')
.append('div')
.attr('class', 'chart error')
.append('p')
.style('line-height', function () {
return $(this.el).height() + 'px';
})
.text(message);
};
return Handler;
};

View file

@ -78,6 +78,10 @@ define(function (require) {
this.xAxisFormatter = this.xAxisFormatter;
this.xScale = this.getXScale(this.ordered, width);
if (!this.xScale || _.isNaN(this.xScale)) {
throw new Error('xScale is ' + this.xScale);
}
this.xAxis = d3.svg.axis()
.scale(this.xScale)
.tickFormat(this.xAxisFormatter)
@ -108,6 +112,10 @@ define(function (require) {
width = $(this).width() - margin.left - margin.right;
height = $(this).height();
if (_.isNaN(height) || height <= 0 || _.isNaN(width) || width <= 0) {
throw new Error('The height and/or width of this container is too small for this chart. Height: ' + height + ', width: ' + width);
}
// Return access to xAxis variable on the object
self.getXAxis(width);

View file

@ -5,15 +5,64 @@ define(function (require) {
function YAxis(args) {
this.el = args.el;
this.yMax = args.yMax;
this._attr = args._attr;
this.chartData = args.chartData;
this.dataArray = args.dataArray;
this._attr = _.defaults(args._attr || {}, {
stack: d3.layout.stack()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
});
}
YAxis.prototype.render = function () {
d3.select(this.el).selectAll('.y-axis-div').call(this.draw());
};
// should be moved to yAxis class
YAxis.prototype.isStacked = function () {
var data = this.chartData;
for (var i = 0; i < data.length; i++) {
if (data[i].series.length > 1) {
return true;
}
}
return false;
};
// should be moved to yAxis class
YAxis.prototype.getYMaxValue = function () {
var self = this;
var arr = [];
_.forEach(this.dataArray, function (series) {
arr.push(self.getYStackMax(series));
});
return _.max(arr);
};
// should be moved to yAxis class
YAxis.prototype.getYStackMax = function (series) {
var self = this;
if (this.isStacked()) {
series = this._attr.stack(series);
}
return d3.max(series, function (data) {
return d3.max(data, function (d) {
if (self.isStacked()) {
return d.y0 + d.y;
}
return d.y;
});
});
};
YAxis.prototype.getYScale = function (height) {
this.yMax = this.getYMaxValue();
this.yScale = d3.scale.linear()
.domain([0, this.yMax])
.range([height, 0])
@ -25,6 +74,10 @@ define(function (require) {
YAxis.prototype.getYAxis = function (height) {
var yScale = this.getYScale(height);
if (!yScale || _.isNaN(yScale)) {
throw new Error('yScale is ' + yScale);
}
this.yAxis = d3.svg.axis()
.scale(yScale)
.tickFormat(d3.format('s'))
@ -59,9 +112,8 @@ define(function (require) {
width = $(this).width();
height = $(this).height() - margin.top - margin.bottom;
// Return access to the yAxis
if (_.isNaN(height) || height <= 0) {
throw new Error('The container is too small for this chart.');
if (_.isNaN(height) || height <= 0 || _.isNaN(width) || width <= 0) {
throw new Error('The height and/or width of this container is too small for this chart. Height: ' + height + ', width: ' + width);
}
var yAxis = self.getYAxis(height);
@ -96,7 +148,6 @@ define(function (require) {
length += tickspace;
// set widths of svg, x-axis-div and x-axis-div-wrapper to fit ticklabels
console.log(svg);
svg.attr('width', length + 6);
d3.selectAll('.y.axis').attr('transform', 'translate(' + (length + 2) + ',' + margin.top + ')');
};

View file

@ -1,7 +1,6 @@
define(function (require) {
return function ChartBaseClass(d3, Private) {
var _ = require('lodash');
var $ = require('jquery');
function Chart(vis, el, chartData) {
if (!(this instanceof Chart)) {
@ -18,46 +17,6 @@ define(function (require) {
return d3.select(this.chartEl).call(this.draw());
};
Chart.prototype.off = function (event) {
return this._attr.dispatch.on(event, null);
};
Chart.prototype.validateHeightAndWidth = function ($el, width, height) {
if (width <= 0 || height <= 0) {
throw new Error($el.attr('class') + ' height is ' + height + ' and width is ' + width);
}
return;
};
Chart.prototype.removeAll = function (elem) {
return d3.select(elem).selectAll('*').remove();
};
Chart.prototype.error = function (elem) {
// Removes the legend container
this.removeAll(elem);
return d3.select(elem)
.append('div')
.attr('class', 'error-wrapper')
.append('div')
.attr('class', 'chart error')
.append('p')
.style('line-height', function () {
return $(elem).height() + 'px';
})
.text('The container is too small for this chart.');
};
Chart.prototype.set = function (name, val) {
this._attr[name] = val;
this.render();
};
Chart.prototype.get = function (name) {
return this._attr[name];
};
return Chart;
};
});

View file

@ -5,25 +5,25 @@ define(function (require) {
var Handler = Private(require('components/vislib/modules/Handler'));
var Events = Private(require('factories/events'));
// VisLib Visualization Types
var chartTypes = {
histogram : Private(require('components/vislib/modules/ColumnChart'))
};
var chartTypes = Private(require('components/vislib/visTypes'));
_(Vis).inherits(Events);
function Vis($el, config) {
if (!(this instanceof Vis)) {
return new Vis($el, config);
}
Vis.Super.apply(this, arguments);
this.el = $el.get ? $el.get(0) : $el;
this.ChartClass = chartTypes[config.type];
this._attr = _.defaults(config || {}, {});
}
Vis.prototype.render = function (data) {
if (this._attr.destroyFlag) {
throw new Error('You tried to render a chart that was destroyed');
}
if (!data) {
throw new Error('No valid data!');
}
@ -34,21 +34,15 @@ define(function (require) {
try {
this.handler.render();
} catch (error) {
console.error(error.message);
this.handler.error(error.message);
}
this.checkSize();
};
Vis.prototype.resize = function () {
if (!this.data) {
throw new Error('No valid data');
}
//this.render(this.data.data);
this.render(this.data);
};
Vis.prototype.checkSize = _.debounce(function () {
if (arguments.length) { return; }
// enable auto-resize
var size = $('.chart').width() + ':' + $('.chart').height();
@ -59,23 +53,29 @@ define(function (require) {
setTimeout(this.checkSize(), 250);
}, 250);
Vis.prototype.resize = function () {
if (!this.data) {
throw new Error('No valid data');
}
this.render(this.data);
};
Vis.prototype.destroy = function () {
this._attr.destroyFlag = true;
this.checkSize(false);
// Removing chart and all elements associated with it
d3.select(this.el).selectAll('*').remove();
// Cleaning up event listeners
this.off('click');
this.off('hover');
this.off('brush');
d3.select(window)
.on('resize', null);
this.off('click', null);
this.off('hover', null);
this.off('brush', null);
};
Vis.prototype.set = function (name, val) {
this._attr[name] = val;
this.render();
this.render(this.data);
};
Vis.prototype.get = function (name) {

View file

@ -0,0 +1,8 @@
define(function (require) {
return function VisTypeFactory(Private) {
// VisLib Visualization Types
return {
histogram: Private(require('components/vislib/modules/ColumnChart'))
};
};
});

View file

@ -21,6 +21,50 @@ define(function (require) {
min: 1408734082458
},
series: [
{
values: [
{
x: 1408734060000,
y: 8
},
{
x: 1408734090000,
y: 23
},
{
x: 1408734120000,
y: 30
},
{
x: 1408734150000,
y: 28
},
{
x: 1408734180000,
y: 36
},
{
x: 1408734210000,
y: 30
},
{
x: 1408734240000,
y: 26
},
{
x: 1408734270000,
y: 22
},
{
x: 1408734300000,
y: 29
},
{
x: 1408734330000,
y: 24
}
]
},
{
values: [
{
@ -88,8 +132,11 @@ define(function (require) {
dataObj = new Data(data);
yAxis = new YAxis({
el: $('.y-axis-wrapper')[0],
yMax: dataObj.getYMaxValue(),
_attr: { margin: { top: 0, right: 0, bottom: 0, left: 0 } }
chartData: dataObj.chartData(),
dataArray: dataObj.flatten(),
_attr: {
margin: { top: 0, right: 0, bottom: 0, left: 0 }
}
});
});
});
@ -141,6 +188,25 @@ define(function (require) {
});
});
describe('isStacked Method', function () {
it('should return false', function () {
expect(yAxis.isStacked()).to.be(true);
});
});
// describe('getYStackMax Method', function () {
// it('should return the max value of a series array', function () {
// expect(typeof yAxis._attr.stack).to.be('function');
// expect(yAxis.getYStackMax(data.series[0].values)).to.be(36);
// });
// });
//
// describe('getYMaxValue Method', function () {
// it('should return the data max y value', function () {
// expect(yAxis.getYMaxValue()).to.be(36);
// });
// });
describe('draw Method', function () {
it('should be a function', function () {
expect(_.isFunction(yAxis.draw())).to.be(true);