/** * 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); };