/** * A scatter plot chart * * Examples: * - {@link http://dc-js.github.io/dc.js/examples/scatter.html Scatter Chart} * - {@link http://dc-js.github.io/dc.js/examples/multi-scatter.html Multi-Scatter Chart} * @class scatterPlot * @memberof dc * @mixes dc.coordinateGridMixin * @example * // create a scatter plot under #chart-container1 element using the default global chart group * var chart1 = dc.scatterPlot('#chart-container1'); * // create a scatter plot under #chart-container2 element using chart group A * var chart2 = dc.scatterPlot('#chart-container2', 'chartGroupA'); * // create a sub-chart under a composite parent chart * var chart3 = dc.scatterPlot(compositeChart); * @param {String|node|d3.selection} parent - Any valid * {@link https://github.com/mbostock/d3/wiki/Selections#selecting-elements 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. * @return {dc.scatterPlot} */ dc.scatterPlot = function (parent, chartGroup) { var _chart = dc.coordinateGridMixin({}); var _symbol = d3.svg.symbol(); var _existenceAccessor = function (d) { return d.value; }; var originalKeyAccessor = _chart.keyAccessor(); _chart.keyAccessor(function (d) { return originalKeyAccessor(d)[0]; }); _chart.valueAccessor(function (d) { return originalKeyAccessor(d)[1]; }); _chart.colorAccessor(function () { return _chart._groupName; }); var _locator = function (d) { return 'translate(' + _chart.x()(_chart.keyAccessor()(d)) + ',' + _chart.y()(_chart.valueAccessor()(d)) + ')'; }; var _highlightedSize = 7; var _symbolSize = 5; var _excludedSize = 3; var _excludedColor = null; var _excludedOpacity = 1.0; var _emptySize = 0; var _filtered = []; _symbol.size(function (d, i) { if (!_existenceAccessor(d)) { return _emptySize; } else if (_filtered[i]) { return Math.pow(_symbolSize, 2); } else { return Math.pow(_excludedSize, 2); } }); dc.override(_chart, '_filter', function (filter) { if (!arguments.length) { return _chart.__filter(); } return _chart.__filter(dc.filters.RangedTwoDimensionalFilter(filter)); }); _chart.plotData = function () { var symbols = _chart.chartBodyG().selectAll('path.symbol') .data(_chart.data()); symbols .enter() .append('path') .attr('class', 'symbol') .attr('opacity', 0) .attr('fill', _chart.getColor) .attr('transform', _locator); symbols.each(function (d, i) { _filtered[i] = !_chart.filter() || _chart.filter().isFiltered([d.key[0], d.key[1]]); }); dc.transition(symbols, _chart.transitionDuration()) .attr('opacity', function (d, i) { return !_existenceAccessor(d) ? 0 : _filtered[i] ? 1 : _chart.excludedOpacity(); }) .attr('fill', function (d, i) { return _chart.excludedColor() && !_filtered[i] ? _chart.excludedColor() : _chart.getColor(d); }) .attr('transform', _locator) .attr('d', _symbol); dc.transition(symbols.exit(), _chart.transitionDuration()) .attr('opacity', 0).remove(); }; /** * Get or set the existence accessor. If a point exists, it is drawn with * {@link dc.scatterPlot#symbolSize symbolSize} radius and * opacity 1; if it does not exist, it is drawn with * {@link dc.scatterPlot#emptySize emptySize} radius and opacity 0. By default, * the existence accessor checks if the reduced value is truthy. * @method existenceAccessor * @memberof dc.scatterPlot * @instance * @see {@link dc.scatterPlot#symbolSize symbolSize} * @see {@link dc.scatterPlot#emptySize emptySize} * @example * // default accessor * chart.existenceAccessor(function (d) { return d.value; }); * @param {Function} [accessor] * @return {Function} * @return {dc.scatterPlot} */ _chart.existenceAccessor = function (accessor) { if (!arguments.length) { return _existenceAccessor; } _existenceAccessor = accessor; return this; }; /** * Get or set the symbol type used for each point. By default the symbol is a circle. * Type can be a constant or an accessor. * @method symbol * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_type d3.svg.symbol().type()} * @example * // Circle type * chart.symbol('circle'); * // Square type * chart.symbol('square'); * @param {String|Function} [type='circle'] * @return {String|Function} * @return {dc.scatterPlot} */ _chart.symbol = function (type) { if (!arguments.length) { return _symbol.type(); } _symbol.type(type); return _chart; }; /** * Set or get radius for symbols. * @method symbolSize * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()} * @param {Number} [symbolSize=3] * @return {Number} * @return {dc.scatterPlot} */ _chart.symbolSize = function (symbolSize) { if (!arguments.length) { return _symbolSize; } _symbolSize = symbolSize; return _chart; }; /** * Set or get radius for highlighted symbols. * @method highlightedSize * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()} * @param {Number} [highlightedSize=5] * @return {Number} * @return {dc.scatterPlot} */ _chart.highlightedSize = function (highlightedSize) { if (!arguments.length) { return _highlightedSize; } _highlightedSize = highlightedSize; return _chart; }; /** * Set or get size for symbols excluded from this chart's filter. If null, no * special size is applied for symbols based on their filter status * @method excludedSize * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()} * @param {Number} [excludedSize=null] * @return {Number} * @return {dc.scatterPlot} */ _chart.excludedSize = function (excludedSize) { if (!arguments.length) { return _excludedSize; } _excludedSize = excludedSize; return _chart; }; /** * Set or get color for symbols excluded from this chart's filter. If null, no * special color is applied for symbols based on their filter status * @method excludedColor * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()} * @param {Number} [excludedColor=null] * @return {Number} * @return {dc.scatterPlot} */ _chart.excludedColor = function (excludedColor) { if (!arguments.length) { return _excludedColor; } _excludedColor = excludedColor; return _chart; }; /** * Set or get opacity for symbols excluded from this chart's filter. * @method excludedOpacity * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()} * @param {Number} [excludedOpacity=1.0] * @return {Number} * @return {dc.scatterPlot} */ _chart.excludedOpacity = function (excludedOpacity) { if (!arguments.length) { return _excludedOpacity; } _excludedOpacity = excludedOpacity; return _chart; }; /** * Set or get radius for symbols when the group is empty. * @method emptySize * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()} * @param {Number} [emptySize=0] * @return {Number} * @return {dc.scatterPlot} */ _chart.hiddenSize = _chart.emptySize = function (emptySize) { if (!arguments.length) { return _emptySize; } _emptySize = emptySize; return _chart; }; _chart.legendables = function () { return [{chart: _chart, name: _chart._groupName, color: _chart.getColor()}]; }; _chart.legendHighlight = function (d) { resizeSymbolsWhere(function (symbol) { return symbol.attr('fill') === d.color; }, _highlightedSize); _chart.selectAll('.chart-body path.symbol').filter(function () { return d3.select(this).attr('fill') !== d.color; }).classed('fadeout', true); }; _chart.legendReset = function (d) { resizeSymbolsWhere(function (symbol) { return symbol.attr('fill') === d.color; }, _symbolSize); _chart.selectAll('.chart-body path.symbol').filter(function () { return d3.select(this).attr('fill') !== d.color; }).classed('fadeout', false); }; function resizeSymbolsWhere (condition, size) { var symbols = _chart.selectAll('.chart-body path.symbol').filter(function () { return condition(d3.select(this)); }); var oldSize = _symbol.size(); _symbol.size(Math.pow(size, 2)); dc.transition(symbols, _chart.transitionDuration()).attr('d', _symbol); _symbol.size(oldSize); } _chart.setHandlePaths = function () { // no handle paths for poly-brushes }; _chart.extendBrush = function () { var extent = _chart.brush().extent(); if (_chart.round()) { extent[0] = extent[0].map(_chart.round()); extent[1] = extent[1].map(_chart.round()); _chart.g().select('.brush') .call(_chart.brush().extent(extent)); } return extent; }; _chart.brushIsEmpty = function (extent) { return _chart.brush().empty() || !extent || extent[0][0] >= extent[1][0] || extent[0][1] >= extent[1][1]; }; _chart._brushing = function () { var extent = _chart.extendBrush(); _chart.redrawBrush(_chart.g()); if (_chart.brushIsEmpty(extent)) { dc.events.trigger(function () { _chart.filter(null); _chart.redrawGroup(); }); } else { var ranged2DFilter = dc.filters.RangedTwoDimensionalFilter(extent); dc.events.trigger(function () { _chart.filter(null); _chart.filter(ranged2DFilter); _chart.redrawGroup(); }, dc.constants.EVENT_DELAY); } }; _chart.setBrushY = function (gBrush) { gBrush.call(_chart.brush().y(_chart.y())); }; return _chart.anchor(parent, chartGroup); };