/**
* Concrete row chart implementation.
*
* Examples:
* - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
* @class rowChart
* @memberof dc
* @mixes dc.capMixin
* @mixes dc.marginMixin
* @mixes dc.colorMixin
* @mixes dc.baseMixin
* @example
* // create a row chart under #chart-container1 element using the default global chart group
* var chart1 = dc.rowChart('#chart-container1');
* // create a row chart under #chart-container2 element using chart group A
* var chart2 = dc.rowChart('#chart-container2', 'chartGroupA');
* @param {String|node|d3.selection} parent - Any valid
* {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying
* a dom block element such as a div; or a dom element or d3 selection.
* @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
* Interaction with a chart will only trigger events and redraws within the chart's group.
* @returns {dc.rowChart}
*/
dc.rowChart = function (parent, chartGroup) {
var _g;
var _labelOffsetX = 10;
var _labelOffsetY = 15;
var _hasLabelOffsetY = false;
var _dyOffset = '0.35em'; // this helps center labels https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#svg_text
var _titleLabelOffsetX = 2;
var _gap = 5;
var _fixedBarHeight = false;
var _rowCssClass = 'row';
var _titleRowCssClass = 'titlerow';
var _renderTitleLabel = false;
var _chart = dc.capMixin(dc.marginMixin(dc.colorMixin(dc.baseMixin({}))));
var _x;
var _elasticX;
var _xAxis = d3.axisBottom();
var _rowData;
_chart.rowsCap = _chart.cap;
function calculateAxisScale () {
if (!_x || _elasticX) {
var extent = d3.extent(_rowData, _chart.cappedValueAccessor);
if (extent[0] > 0) {
extent[0] = 0;
}
if (extent[1] < 0) {
extent[1] = 0;
}
_x = d3.scaleLinear().domain(extent)
.range([0, _chart.effectiveWidth()]);
}
_xAxis.scale(_x);
}
function drawAxis () {
var axisG = _g.select('g.axis');
calculateAxisScale();
if (axisG.empty()) {
axisG = _g.append('g').attr('class', 'axis');
}
axisG.attr('transform', 'translate(0, ' + _chart.effectiveHeight() + ')');
dc.transition(axisG, _chart.transitionDuration(), _chart.transitionDelay())
.call(_xAxis);
}
_chart._doRender = function () {
_chart.resetSvg();
_g = _chart.svg()
.append('g')
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
drawChart();
return _chart;
};
_chart.title(function (d) {
return _chart.cappedKeyAccessor(d) + ': ' + _chart.cappedValueAccessor(d);
});
_chart.label(_chart.cappedKeyAccessor);
/**
* Gets or sets the x scale. The x scale can be any d3
* {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}.
* @method x
* @memberof dc.rowChart
* @instance
* @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}
* @param {d3.scale} [scale]
* @returns {d3.scale|dc.rowChart}
*/
_chart.x = function (scale) {
if (!arguments.length) {
return _x;
}
_x = scale;
return _chart;
};
function drawGridLines () {
_g.selectAll('g.tick')
.select('line.grid-line')
.remove();
_g.selectAll('g.tick')
.append('line')
.attr('class', 'grid-line')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', function () {
return -_chart.effectiveHeight();
});
}
function drawChart () {
_rowData = _chart.data();
drawAxis();
drawGridLines();
var rows = _g.selectAll('g.' + _rowCssClass)
.data(_rowData);
removeElements(rows);
rows = createElements(rows)
.merge(rows);
updateElements(rows);
}
function createElements (rows) {
var rowEnter = rows.enter()
.append('g')
.attr('class', function (d, i) {
return _rowCssClass + ' _' + i;
});
rowEnter.append('rect').attr('width', 0);
createLabels(rowEnter);
return rowEnter;
}
function removeElements (rows) {
rows.exit().remove();
}
function rootValue () {
var root = _x(0);
return (root === -Infinity || root !== root) ? _x(1) : root;
}
function updateElements (rows) {
var n = _rowData.length;
var height;
if (!_fixedBarHeight) {
height = (_chart.effectiveHeight() - (n + 1) * _gap) / n;
} else {
height = _fixedBarHeight;
}
// vertically align label in center unless they override the value via property setter
if (!_hasLabelOffsetY) {
_labelOffsetY = height / 2;
}
var rect = rows.attr('transform', function (d, i) {
return 'translate(0,' + ((i + 1) * _gap + i * height) + ')';
}).select('rect')
.attr('height', height)
.attr('fill', _chart.getColor)
.on('click', onClick)
.classed('deselected', function (d) {
return (_chart.hasFilter()) ? !isSelectedRow(d) : false;
})
.classed('selected', function (d) {
return (_chart.hasFilter()) ? isSelectedRow(d) : false;
});
dc.transition(rect, _chart.transitionDuration(), _chart.transitionDelay())
.attr('width', function (d) {
return Math.abs(rootValue() - _x(_chart.cappedValueAccessor(d)));
})
.attr('transform', translateX);
createTitles(rows);
updateLabels(rows);
}
function createTitles (rows) {
if (_chart.renderTitle()) {
rows.select('title').remove();
rows.append('title').text(_chart.title());
}
}
function createLabels (rowEnter) {
if (_chart.renderLabel()) {
rowEnter.append('text')
.on('click', onClick);
}
if (_chart.renderTitleLabel()) {
rowEnter.append('text')
.attr('class', _titleRowCssClass)
.on('click', onClick);
}
}
function updateLabels (rows) {
if (_chart.renderLabel()) {
var lab = rows.select('text')
.attr('x', _labelOffsetX)
.attr('y', _labelOffsetY)
.attr('dy', _dyOffset)
.on('click', onClick)
.attr('class', function (d, i) {
return _rowCssClass + ' _' + i;
})
.text(function (d) {
return _chart.label()(d);
});
dc.transition(lab, _chart.transitionDuration(), _chart.transitionDelay())
.attr('transform', translateX);
}
if (_chart.renderTitleLabel()) {
var titlelab = rows.select('.' + _titleRowCssClass)
.attr('x', _chart.effectiveWidth() - _titleLabelOffsetX)
.attr('y', _labelOffsetY)
.attr('dy', _dyOffset)
.attr('text-anchor', 'end')
.on('click', onClick)
.attr('class', function (d, i) {
return _titleRowCssClass + ' _' + i ;
})
.text(function (d) {
return _chart.title()(d);
});
dc.transition(titlelab, _chart.transitionDuration(), _chart.transitionDelay())
.attr('transform', translateX);
}
}
/**
* Turn on/off Title label rendering (values) using SVG style of text-anchor 'end'.
* @method renderTitleLabel
* @memberof dc.rowChart
* @instance
* @param {Boolean} [renderTitleLabel=false]
* @returns {Boolean|dc.rowChart}
*/
_chart.renderTitleLabel = function (renderTitleLabel) {
if (!arguments.length) {
return _renderTitleLabel;
}
_renderTitleLabel = renderTitleLabel;
return _chart;
};
function onClick (d) {
_chart.onClick(d);
}
function translateX (d) {
var x = _x(_chart.cappedValueAccessor(d)),
x0 = rootValue(),
s = x > x0 ? x0 : x;
return 'translate(' + s + ',0)';
}
_chart._doRedraw = function () {
drawChart();
return _chart;
};
/**
* Get or sets the x axis for the row chart instance.
* See the {@link https://github.com/d3/d3-axis/blob/master/README.md d3.axis}
* documention for more information.
* @method xAxis
* @memberof dc.rowChart
* @instance
* @param {d3.axis} [xAxis]
* @example
* // customize x axis tick format
* chart.xAxis().tickFormat(function (v) {return v + '%';});
* // customize x axis tick values
* chart.xAxis().tickValues([0, 100, 200, 300]);
* // use a top-oriented axis. Note: position of the axis and grid lines will need to
* // be set manually, see https://dc-js.github.io/dc.js/examples/row-top-axis.html
* chart.xAxis(d3.axisTop())
* @returns {d3.axis|dc.rowChart}
*/
_chart.xAxis = function (xAxis) {
if (!arguments.length) {
return _xAxis;
}
_xAxis = xAxis;
return this;
};
/**
* Get or set the fixed bar height. Default is [false] which will auto-scale bars.
* For example, if you want to fix the height for a specific number of bars (useful in TopN charts)
* you could fix height as follows (where count = total number of bars in your TopN and gap is
* your vertical gap space).
* @method fixedBarHeight
* @memberof dc.rowChart
* @instance
* @example
* chart.fixedBarHeight( chartheight - (count + 1) * gap / count);
* @param {Boolean|Number} [fixedBarHeight=false]
* @returns {Boolean|Number|dc.rowChart}
*/
_chart.fixedBarHeight = function (fixedBarHeight) {
if (!arguments.length) {
return _fixedBarHeight;
}
_fixedBarHeight = fixedBarHeight;
return _chart;
};
/**
* Get or set the vertical gap space between rows on a particular row chart instance.
* @method gap
* @memberof dc.rowChart
* @instance
* @param {Number} [gap=5]
* @returns {Number|dc.rowChart}
*/
_chart.gap = function (gap) {
if (!arguments.length) {
return _gap;
}
_gap = gap;
return _chart;
};
/**
* Get or set the elasticity on x axis. If this attribute is set to true, then the x axis will rescale to auto-fit the
* data range when filtered.
* @method elasticX
* @memberof dc.rowChart
* @instance
* @param {Boolean} [elasticX]
* @returns {Boolean|dc.rowChart}
*/
_chart.elasticX = function (elasticX) {
if (!arguments.length) {
return _elasticX;
}
_elasticX = elasticX;
return _chart;
};
/**
* Get or set the x offset (horizontal space to the top left corner of a row) for labels on a particular row chart.
* @method labelOffsetX
* @memberof dc.rowChart
* @instance
* @param {Number} [labelOffsetX=10]
* @returns {Number|dc.rowChart}
*/
_chart.labelOffsetX = function (labelOffsetX) {
if (!arguments.length) {
return _labelOffsetX;
}
_labelOffsetX = labelOffsetX;
return _chart;
};
/**
* Get or set the y offset (vertical space to the top left corner of a row) for labels on a particular row chart.
* @method labelOffsetY
* @memberof dc.rowChart
* @instance
* @param {Number} [labelOffsety=15]
* @returns {Number|dc.rowChart}
*/
_chart.labelOffsetY = function (labelOffsety) {
if (!arguments.length) {
return _labelOffsetY;
}
_labelOffsetY = labelOffsety;
_hasLabelOffsetY = true;
return _chart;
};
/**
* Get of set the x offset (horizontal space between right edge of row and right edge or text.
* @method titleLabelOffsetX
* @memberof dc.rowChart
* @instance
* @param {Number} [titleLabelOffsetX=2]
* @returns {Number|dc.rowChart}
*/
_chart.titleLabelOffsetX = function (titleLabelOffsetX) {
if (!arguments.length) {
return _titleLabelOffsetX;
}
_titleLabelOffsetX = titleLabelOffsetX;
return _chart;
};
function isSelectedRow (d) {
return _chart.hasFilter(_chart.cappedKeyAccessor(d));
}
return _chart.anchor(parent, chartGroup);
};