dc
Version:
A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js
762 lines (631 loc) • 35 kB
JavaScript
/* global appendChartID, loadDateFixture, makeDate */
describe('dc.lineChart', function () {
var id, chart, data;
var dimension, group;
beforeEach(function () {
data = crossfilter(loadDateFixture());
dimension = data.dimension(function (d) { return d3.time.day.utc(d.dd); });
group = dimension.group();
id = 'line-chart';
appendChartID(id);
chart = dc.lineChart('#' + id);
chart.dimension(dimension).group(group)
.width(1100).height(200)
.x(d3.time.scale.utc().domain([makeDate(2012, 1, 1), makeDate(2012, 11, 31)]))
.transitionDuration(0);
});
describe('rendering', function () {
beforeEach(function () {
chart.render();
});
describe('with a specified dash style', function () {
beforeEach(function () {
chart.dashStyle([3, 1, 1, 1]).render();
});
it('should be dash-dot-dot-dot to match the specified style', function () {
expect(chart.selectAll('path.line').attr('stroke-dasharray')).toEqualIntList('3,1,1,1');
});
});
describe('render data markers', function () {
beforeEach(function () {
chart.dotRadius(5)
.brushOn(false)
.renderDataPoints({}).render();
});
it('should use default options', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
expect(dot.style('fill-opacity')).toBeWithinDelta(0.8);
expect(dot.style('stroke-opacity')).toBeWithinDelta(0.8);
expect(dot.attr('r')).toBe('2');
});
});
it('should use supplied options', function () {
chart.renderDataPoints({radius: 3, fillOpacity: 1, strokeOpacity: 1})
.render();
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
expect(dot.style('fill-opacity')).toBe('1');
expect(dot.style('stroke-opacity')).toBe('1');
expect(dot.attr('r')).toBe('3');
});
});
it('should change the radius on mousemove', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
dot.on('mousemove').call(this);
expect(dot.attr('r')).toBe('5');
});
});
it('should revert to original radius on mouseout', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
dot.on('mousemove').call(this);
dot.on('mouseout').call(this);
expect(dot.attr('r')).toBe('2');
});
});
describe('hiding all data markers', function () {
beforeEach(function () {
chart.renderDataPoints(false).render();
});
it('should not change the default opacity and radius', function () {
chart.selectAll('circle.dot').each(function () {
expect(d3.select(this).style('fill-opacity')).toBeWithinDelta(1e-6);
expect(d3.select(this).style('stroke-opacity')).toBeWithinDelta(1e-6);
});
});
it('should not change showing the data point on mousemove', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
dot.on('mousemove').call(this);
expect(dot.style('fill-opacity')).toBeWithinDelta(0.8);
expect(dot.style('stroke-opacity')).toBeWithinDelta(0.8);
});
});
it('should not change returning to extremely low opacity on hover out', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
dot.on('mousemove').call(this);
dot.on('mouseout').call(this);
expect(dot.style('fill-opacity')).toBeWithinDelta(1e-6);
expect(dot.style('stroke-opacity')).toBeWithinDelta(1e-6);
});
});
});
});
describe('title rendering', function () {
beforeEach(function () {
chart.renderTitle(false);
chart.render();
});
it('should not render dots and tips when boolean flag is false', function () {
expect(chart.select('.dc-tooltip._0 .dot').empty()).toBeTruthy();
expect(chart.select('.dc-tooltip._0 .dot title').empty()).toBeTruthy();
});
});
describe('title rendering with brushOn', function () {
beforeEach(function () {
chart.brushOn(true)
.xyTipsOn(true); // default, for exposition
chart.render();
});
it('should not render tips', function () {
expect(chart.select('.dc-tooltip._0 .dot').empty()).toBeTruthy();
expect(chart.select('.dc-tooltip._0 .dot title').empty()).toBeTruthy();
});
describe('with xyTipsOn always', function () {
beforeEach(function () {
chart.brushOn(true)
.xyTipsOn('always');
chart.render();
});
it('should render dots', function () {
expect(chart.select('.dc-tooltip._0 .dot').empty()).toBeFalsy();
expect(chart.select('.dc-tooltip._0 .dot title').empty()).toBeFalsy();
});
});
});
describe('label rendering off', function () {
beforeEach(function () {
chart.renderLabel(false);
chart.render();
});
it('should produce no labels', function () {
expect(chart.selectAll('.lineLabel').empty()).toBeTruthy();
});
});
describe('label rendering', function () {
beforeEach(function () {
chart.renderLabel(true);
chart.render();
});
it('should generate a label for each datum', lineLabelCount);
it('should generate labels with positions corresponding to their data', lineLabelPositions);
});
describe('data point highlights and refs off', function () {
beforeEach(function () {
chart.title(function (d) { return d.value; });
chart.brushOn(false).xyTipsOn(false).render();
});
it('should not generate per data points', function () {
expect(chart.selectAll('circle.dot').size()).toBe(0);
});
it('should not generate x and y refs', function () {
expect(chart.selectAll('path.xRef').size()).toBe(0);
expect(chart.selectAll('path.yRef').size()).toBe(0);
});
});
describe('data point highlights', function () {
beforeEach(function () {
chart.title(function (d) { return d.value; });
chart.brushOn(false).dotRadius(10).render();
});
it('should not generate a chart brush', function () {
expect(chart.selectAll('g.brush').empty()).toBeTruthy();
});
it('should be generated per data point', function () {
expect(chart.selectAll('circle.dot').size()).toBe(6);
});
it('should have configurable radius', function () {
chart.selectAll('circle.dot').each(function () {
expect(d3.select(this).attr('r')).toBe('10');
});
});
it('should be have extremely low opacity by default', function () {
chart.selectAll('circle.dot').each(function () {
expect(d3.select(this).style('fill-opacity')).toBeWithinDelta(1e-6);
expect(d3.select(this).style('stroke-opacity')).toBeWithinDelta(1e-6);
});
});
it('should become visible when hovered over', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
dot.on('mousemove').call(this);
expect(dot.style('fill-opacity')).toBeWithinDelta(0.8);
expect(dot.style('stroke-opacity')).toBeWithinDelta(0.8);
});
});
it('should return to extremely low opacity on hover out', function () {
chart.selectAll('circle.dot').each(function () {
var dot = d3.select(this);
dot.on('mousemove').call(this);
dot.on('mouseout').call(this);
expect(dot.style('fill-opacity')).toBeWithinDelta(1e-6);
expect(dot.style('stroke-opacity')).toBeWithinDelta(1e-6);
});
});
it('should render titles on to each circle', function () {
chart.selectAll('g._0 circle.dot').each(function (d) {
expect(+d3.select(this).select('title').text()).toBe(d.data.value);
});
});
describe('ref lines', function () {
it('should generate lines that are hidden by default', function () {
expect(chart.select('path.xRef').style('display')).toBe('none');
expect(chart.select('path.yRef').style('display')).toBe('none');
});
it('should have a stroke dash', function () {
expect(chart.selectAll('path.xRef').attr('stroke-dasharray')).toEqualIntList('5,5');
expect(chart.selectAll('path.yRef').attr('stroke-dasharray')).toEqualIntList('5,5');
});
describe('when dot is hovered over', function () {
describe('for vertical ref lines', function () {
var x;
beforeEach(function () {
var dot = chart.select('circle.dot');
dot.on('mousemove').call(dot[0][0]);
x = dot.attr('cx');
});
it('shows the ref line from the bottom of the graph', function () {
var path = 'M' + x + ' 160 L ' + x + ' 107';
expect(chart.select('path.xRef').attr('d')).toMatchPath(path);
expect(chart.select('path.xRef').attr('display')).not.toBe('none');
});
});
describe('for horizontal ref lines', function () {
describe('for a left y-axis chart', function () {
var x;
beforeEach(function () {
var dot = chart.select('circle.dot');
dot.on('mousemove').call(dot[0][0]);
x = dot.attr('cx');
});
it('shows the ref line on the left', function () {
var path = 'M0 107 L ' + x + ' 107';
expect(chart.select('path.yRef').attr('d')).toMatchPath(path);
expect(chart.select('path.yRef').attr('display')).not.toBe('none');
});
});
describe('for a right y-axis chart', function () {
var x;
beforeEach(function () {
chart.useRightYAxis(true).render();
var dot = chart.select('circle.dot');
dot.on('mousemove').call(dot[0][0]);
x = dot.attr('cx');
});
it('shows the ref line on the right', function () {
var path = 'M1020 107L' + x + ' 107';
expect(chart.select('path.yRef').attr('d')).toMatchPath(path); //"M1020 107L405 107");
expect(chart.select('path.yRef').attr('display')).not.toBe('none');
});
});
});
});
});
});
describe('undefined points', function () {
beforeEach(function () {
chart.defined(function (d) { return d.x.valueOf() !== makeDate(2012, 5, 10).getTime(); });
chart.brushOn(false).render();
});
it('should not show line where not defined', function () {
expect(chart.select('path.line').attr('d').indexOf('M', 1)).not.toBe(-1);
});
it('should not draw dots on undefined points', function () {
expect(chart.selectAll('.dot').size()).toBe(5);
});
});
describe('with chart area enabled', function () {
beforeEach(function () {
chart.renderArea(true).render();
});
it('should draw the chart line', function () {
expect(chart.select('path.line').attr('d')).toMatchPath('M348,107 L390,107 L397,0 L461,107 L488,53 L583,53');
});
it('should draw the chart area', function () {
expect(chart.select('path.area').attr('d')).toMatchPath('M348,107 L390,107 L397,0 L461,107 L488,53 L583,' +
'53 L583,160 L488,160 L461,160 L397,160 L390,160 L348,160Z');
});
});
describe('with an ordinal x domain', function () {
beforeEach(function () {
var stateDimension = data.dimension(function (d) { return d.state; });
var stateGroup = stateDimension.group();
chart.dimension(stateDimension)
.group(stateGroup)
.xUnits(dc.units.ordinal)
.x(d3.scale.ordinal().domain(['California', 'Colorado', 'Delaware', 'Mississippi', 'Oklahoma', 'Ontario']))
.render();
});
it('should automatically disable the brush', function () {
expect(chart.brushOn()).toBeFalsy();
});
it('should generate an ordinal path', function () {
expect(chart.select('path.line').attr('d')).toMatchPath('M85,0L255,107L425,107L595,53L765,107L935,53');
});
});
describe('with stacked data', function () {
describe('with positive data', function () {
beforeEach(function () {
var idGroup = dimension.group().reduceSum(function (d) { return d.id; });
var valueGroup = dimension.group().reduceSum(function (d) { return d.value; });
chart.dimension(dimension)
.brushOn(false)
.x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)]))
.group(idGroup, 'stack 0')
.title('stack 0', function (d) { return 'stack 0: ' + d.value; })
.stack(valueGroup, 'stack 1')
.title('stack 1', function (d) { return 'stack 1: ' + d.value; })
.stack(valueGroup, 'stack 2')
.elasticY(true)
.render();
});
it('should render the correct number of lines', function () {
expect(chart.selectAll('path.line').size()).toBe(3);
});
it('should set the path for stack 0 line', function () {
expect(chart.select('g._0 path.line').attr('d')).toMatchPath('M58 159L222 157L246 150L492 158L597 151L961 153');
});
it('should set the path for stack 1 line', function () {
expect(chart.select('g._1 path.line').attr('d')).toMatchPath('M58 134L222 119L246 75L492 133L597 120L961 109');
});
it('should set the path for stack 2 line', function () {
expect(chart.select('g._2 path.line').attr('d')).toMatchPath('M58 109L222 81L246 0L492 108L597 89L961 65');
});
it('should have its own title accessor', function () {
expect(chart.title()({value: 1})).toBe('stack 0: 1');
expect(chart.title('stack 0')({value: 2})).toBe('stack 0: 2');
expect(chart.title('stack 1')({value: 3})).toBe('stack 1: 3');
});
it('should have titles rendered for extra stacks', function () {
chart.selectAll('g._1 circle.dot').each(function (d) {
expect(d3.select(this).select('title').text()).toBe('stack 1: ' + d.data.value);
});
});
it('should default to first stack title for untitled stacks', function () {
chart.selectAll('g._2 circle.dot').each(function (d) {
expect(d3.select(this).select('title').text()).toBe('stack 0: ' + d.data.value);
});
});
describe('with chart area enabled', function () {
beforeEach(function () {
chart.renderArea(true).render();
});
it('should render the correct number of areas', function () {
expect(chart.selectAll('path.area').size()).toBe(3);
});
it('should set the area for stack 0', function () {
expect(chart.select('g._0 path.area').attr('d')).toMatchPath('M58 159L222 157L246 150L492 158L597 ' +
'151L961 153L961 160L597 160L492 160L246 160L222 160L58 160Z');
});
it('should set the area for stack 1', function () {
expect(chart.select('g._1 path.area').attr('d')).toMatchPath('M58 134L222 119L246 75L492 133L597 ' +
'120L961 109L961 153L597 151L492 158L246 150L222 157L58 159Z');
});
it('should set the area for stack 2', function () {
expect(chart.select('g._2 path.area').attr('d')).toMatchPath('M58 109L222 81L246 0L492 108L597 89L961 ' +
'65L961 109L597 120L492 133L246 75L222 119L58 134Z');
});
it('should draw tooltip dots on top of paths and areas', function () {
var list = chart.selectAll('circle.dot, path.line, path.area');
var indexOfLastLine = list[0].indexOf(list.filter('path.line')[0][2]);
var indexOfLastArea = list[0].indexOf(list.filter('path.area')[0][2]);
var indexOfDot = list[0].indexOf(list.filter('circle.dot')[0][0]);
expect(indexOfDot).toBeGreaterThan(indexOfLastArea);
expect(indexOfDot).toBeGreaterThan(indexOfLastLine);
});
it('should draw tooltip ref lines on top of paths', function () {
var list = chart.selectAll('path.yRef, path.xRef, path.line, path.area');
var indexOfLastLine = list[0].indexOf(list.filter('path.line')[0][2]);
var indexOfLastArea = list[0].indexOf(list.filter('path.area')[0][2]);
var indexOfFirstYRef = list[0].indexOf(list.filter('path.yRef')[0][0]);
var indexOfFirstXRef = list[0].indexOf(list.filter('path.xRef')[0][0]);
expect(indexOfLastLine).toBeLessThan(indexOfFirstXRef);
expect(indexOfLastLine).toBeLessThan(indexOfFirstYRef);
expect(indexOfLastArea).toBeLessThan(indexOfFirstXRef);
expect(indexOfLastArea).toBeLessThan(indexOfFirstYRef);
});
});
describe('stack hiding', function () {
describe('first stack', function () {
beforeEach(function () {
chart.hideStack('stack 0').render();
});
it('should hide the stack', function () {
expect(chart.select('g._0 path.line').attr('d')).toMatchPath('M58 133L222 120L246 80L492 133L597 127L961 113');
});
it('should show the stack', function () {
chart.showStack('stack 0').render();
expect(chart.select('g._0 path.line').attr('d')).toMatchPath('M58 159L222 157L246 150L492 158L597 151L961 153');
});
});
describe('any other stack', function () {
beforeEach(function () {
chart.title('stack 2', function (d) { return 'stack 2: ' + d.value; });
chart.hideStack('stack 1').render();
});
it('should hide the stack', function () {
expect(chart.select('g._1 path.line').attr('d')).toMatchPath('M58 112L222 83L246 0L492 108L597 85L961 64');
});
it('should show the stack', function () {
chart.showStack('stack 1').render();
expect(chart.select('g._1 path.line').attr('d')).toMatchPath('M58 134L222 119L246 75L492 133L597 120L961 109');
});
it('should color chart dots the same as line paths', function () {
var lineColor = chart.select('g._1 path.line').attr('stroke');
var circleColor = chart.select('g._1 circle.dot').attr('fill');
expect(lineColor).toEqual(circleColor);
});
it('should still show the title for a visible stack', function () {
chart.selectAll('g._1 circle.dot').each(function (d) {
expect(d3.select(this).select('title').text()).toBe('stack 2: ' + d.data.value);
});
});
});
describe('hiding all the stacks', function () {
beforeEach(function () {
chart.hideStack('stack 0')
.hideStack('stack 1')
.hideStack('stack 2')
.render();
});
it('should show a blank graph', function () {
expect(chart.selectAll('path.line').size()).toBe(0);
});
});
});
describe('label rendering', function () {
beforeEach(function () {
chart.renderLabel(true);
chart.render();
});
it('should generate a label for each datum', lineLabelCount);
it('should generate labels with positions corresponding to their data', lineLabelPositions);
});
});
describe('with mixed positive and negative data', function () {
beforeEach(function () {
var mixedGroup = dimension.group().reduceSum(function (d) { return d.nvalue; });
chart.group(mixedGroup).stack(mixedGroup).stack(mixedGroup);
chart.x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)]));
chart.margins({top: 30, right: 50, bottom: 30, left: 30})
.renderArea(true)
.yAxisPadding(5)
.elasticY(true)
.yAxis().ticks(5);
chart.render();
});
it('should generate a line and area for each stack', function () {
expect(chart.selectAll('path.line').size()).toBe(3);
expect(chart.selectAll('path.area').size()).toBe(3);
});
it('should generate negative lines and area for stack 0', function () {
expect(chart.select('g._0 path.line').attr('d')).toMatchPath('M58 81L222 81L246 92L492 79L597 52L961 67');
expect(chart.select('g._0 path.area').attr('d')).toMatchPath('M58 81L222 81L246 92L492 79L597 52L961 ' +
'67L961 73L597 73L492 73L246 73L222 73L58 73Z');
});
it('should generate negative lines and area for stack 1', function () {
expect(chart.select('g._1 path.line').attr('d')).toMatchPath('M58 88L222 88L246 111L492 84L597 31L961 61');
expect(chart.select('g._1 path.area').attr('d')).toMatchPath('M58 88L222 88L246 111L492 84L597 31L961 ' +
'61L961 67L597 52L492 79L246 92L222 81L58 81Z');
});
it('should generate y axis domain dynamically', function () {
var nthText = function (n) { return d3.select(chart.selectAll('g.axis.y .tick text')[0][n]); };
expect(nthText(0).text()).toBe('-20');
expect(nthText(1).text()).toBe('0');
expect(nthText(2).text()).toBe('20');
});
describe('label rendering', function () {
beforeEach(function () {
chart.renderLabel(true);
chart.render();
});
it('should generate a label for each datum', lineLabelCount);
it('should generate labels with positions corresponding to their data', lineLabelPositions);
});
});
describe('with negative data', function () {
beforeEach(function () {
var negativeGroup = dimension.group().reduceSum(function (d) { return -Math.abs(d.nvalue); });
chart.group(negativeGroup).stack(negativeGroup).stack(negativeGroup);
chart.x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)]));
chart.margins({top: 30, right: 50, bottom: 30, left: 30})
.elasticY(true)
.xUnits(d3.time.days.utc)
.yAxis().ticks(3);
chart.render();
});
it('should generate y axis domain dynamically', function () {
var nthText = function (n) { return d3.select(chart.selectAll('g.axis.y .tick text')[0][n]); };
expect(nthText(0).text()).toBe('-30');
expect(nthText(1).text()).toBe('-20');
expect(nthText(2).text()).toBe('-10');
expect(nthText(3).text()).toBe('0');
});
});
});
describe('legend hovering', function () {
var firstItem;
beforeEach(function () {
chart.stack(group)
.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
.renderArea(true)
.render();
firstItem = chart.select('g.dc-legend g.dc-legend-item');
firstItem.on('mouseover')(firstItem.datum());
});
describe('when a legend item is hovered over', function () {
it('should highlight corresponding lines and areas', function () {
expect(nthLine(0).classed('highlight')).toBeTruthy();
expect(nthArea(0).classed('highlight')).toBeTruthy();
});
it('should fade out non-corresponding lines and areas', function () {
expect(nthLine(1).classed('fadeout')).toBeTruthy();
expect(nthArea(1).classed('fadeout')).toBeTruthy();
});
});
describe('when a legend item is hovered out', function () {
it('should remove highlighting from corresponding lines and areas', function () {
firstItem.on('mouseout')(firstItem.datum());
expect(nthLine(0).classed('highlight')).toBeFalsy();
expect(nthArea(0).classed('highlight')).toBeFalsy();
});
it('should fade in non-corresponding lines and areas', function () {
firstItem.on('mouseout')(firstItem.datum());
expect(nthLine(1).classed('fadeout')).toBeFalsy();
expect(nthArea(1).classed('fadeout')).toBeFalsy();
});
});
function nthLine (n) {
return d3.select(chart.selectAll('path.line')[0][n]);
}
function nthArea (n) {
return d3.select(chart.selectAll('path.area')[0][n]);
}
});
describe('filtering', function () {
beforeEach(function () {
chart.filter([makeDate(2012, 5, 1), makeDate(2012, 5, 30)]).redraw();
});
it('should set the chart filter', function () {
expect(chart.filter()).toEqual([makeDate(2012, 5, 1), makeDate(2012, 5, 30)]);
});
it('should set the filter printer', function () {
expect(chart.filterPrinter()).not.toBeNull();
});
it('should not generate tooltip circles with the default brush', function () {
expect(chart.selectAll('circle.dot').empty()).toBeTruthy();
});
describe('when a brush is defined', function () {
it('should position the brush with an offset', function () {
expect(chart.select('g.brush').attr('transform')).toMatchTranslate(chart.margins().left, 10);
});
it('should create a fancy brush resize handle', function () {
chart.select('g.brush').selectAll('.resize path').each(function (d, i) {
if (i === 0) {
expect(d3.select(this).attr('d'))
.toMatchPath('M0.5,53 A6,6 0 0 1 6.5,59 V100 A6,6 0 0 1 0.5,106 ZM2.5,61 V98 M4.5,61 V98');
} else {
expect(d3.select(this).attr('d'))
.toMatchPath('M-0.5,53 A6,6 0 0 0 -6.5,59 V100 A6,6 0 0 0 -0.5,106 ZM-2.5,61 V98 M-4.5,61 V98');
}
});
});
it('should stretch the background', function () {
expect(chart.select('g.brush rect.background').attr('width')).toBe('1020');
});
it('should set the background height to the chart height', function () {
expect(chart.select('g.brush rect.background').attr('height')).toBe('160');
});
it('should set extent height to the chart height', function () {
expect(chart.select('g.brush rect.extent').attr('height')).toBe('160');
});
it('should set extent width based on filter set', function () {
expect(chart.select('g.brush rect.extent').attr('width')).toBeWithinDelta(88, 1);
});
it('should not have an area path', function () {
expect(chart.selectAll('path.area').empty()).toBeTruthy();
});
it('should set the dash style to solid', function () {
expect(chart.selectAll('path.line').attr('stroke-dasharray')).toBeNull();
});
});
});
describe('changing data', function () {
var stateDimension;
beforeEach(function () {
chart.brushOn(false)
.title(function (d) { return d.value; })
.render();
stateDimension = data.dimension(function (d) { return d.state; });
stateDimension.filter('CA');
chart.redraw();
});
it('should update dot titles', function () {
chart.selectAll('g._0 circle.dot').each(function (d) {
expect(d3.select(this).select('title').size()).toBe(1);
expect(+d3.select(this).select('title').text()).toBe(d.data.value);
});
});
afterEach(function () {
stateDimension.filter(null);
});
});
});
describe('change color', function () {
beforeEach(function () {
chart.brushOn(false)
.ordinalColors(['#ff0000'])
.colorAccessor(function () { return 0; })
.render();
});
it('updates dot colors', function () {
expect(chart.select('circle.dot')[0][0].attributes.fill.value).toMatch(/#ff0000/i);
});
});
function lineLabelCount () {
expect(chart.selectAll('text.lineLabel').size()).toBe(chart.stack().length * chart.group().all().length);
}
function lineLabelPositions () {
var LABEL_PADDING = 3;
chart.selectAll('.stack')[0].forEach(function (stack, i) {
d3.select(stack).selectAll('text.lineLabel')[0].forEach(function (lineLabel, j) {
expect(+d3.select(lineLabel).attr('x')).toBeCloseTo(chart.x()(chart.data()[i].values[j].x));
expect(+d3.select(lineLabel).attr('y') + LABEL_PADDING).toBeCloseTo(chart.y()(chart.data()[i].values[j].y +
chart.data()[i].values[j].y0));
});
});
}
});