UNPKG

dc

Version:

A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js

435 lines (392 loc) 14.9 kB
/** * 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/d3/d3-3.x-api-reference/blob/master/Selections.md#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. * @returns {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; }); _chart.title(function (d) { // this basically just counteracts the setting of its own key/value accessors // see https://github.com/dc-js/dc.js/issues/702 return _chart.keyAccessor()(d) + ',' + _chart.valueAccessor()(d) + ': ' + _chart.existenceAccessor()(d); }); 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 _emptyOpacity = 0; var _nonemptyOpacity = 1; var _emptyColor = null; var _filtered = []; function elementSize (d, i) { if (!_existenceAccessor(d)) { return Math.pow(_emptySize, 2); } else if (_filtered[i]) { return Math.pow(_symbolSize, 2); } else { return Math.pow(_excludedSize, 2); } } _symbol.size(elementSize); 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.call(renderTitles, _chart.data()); symbols.each(function (d, i) { _filtered[i] = !_chart.filter() || _chart.filter().isFiltered([d.key[0], d.key[1]]); }); dc.transition(symbols, _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', function (d, i) { if (!_existenceAccessor(d)) { return _emptyOpacity; } else if (_filtered[i]) { return _nonemptyOpacity; } else { return _chart.excludedOpacity(); } }) .attr('fill', function (d, i) { if (_emptyColor && !_existenceAccessor(d)) { return _emptyColor; } else if (_chart.excludedColor() && !_filtered[i]) { return _chart.excludedColor(); } else { return _chart.getColor(d); } }) .attr('transform', _locator) .attr('d', _symbol); dc.transition(symbols.exit(), _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', 0).remove(); }; function renderTitles (symbol, d) { if (_chart.renderTitle()) { symbol.selectAll('title').remove(); symbol.append('title').text(function (d) { return _chart.title()(d); }); } } /** * 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] * @returns {Function|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/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_type d3.svg.symbol.type} * @example * // Circle type * chart.symbol('circle'); * // Square type * chart.symbol('square'); * @param {String|Function} [type='circle'] * @returns {String|Function|dc.scatterPlot} */ _chart.symbol = function (type) { if (!arguments.length) { return _symbol.type(); } _symbol.type(type); return _chart; }; /** * Get or set the symbol generator. By default `dc.scatterPlot` will use * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol d3.svg.symbol()} * to generate symbols. `dc.scatterPlot` will set the * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size size accessor} * on the symbol generator. * @method customSymbol * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol d3.svg.symbol} * @see {@link https://stackoverflow.com/questions/25332120/create-additional-d3-js-symbols Create additional D3.js symbols} * @param {String|Function} [customSymbol=d3.svg.symbol()] * @returns {String|Function|dc.scatterPlot} */ _chart.customSymbol = function (customSymbol) { if (!arguments.length) { return _symbol; } _symbol = customSymbol; _symbol.size(elementSize); return _chart; }; /** * Set or get radius for symbols. * @method symbolSize * @memberof dc.scatterPlot * @instance * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size} * @param {Number} [symbolSize=3] * @returns {Number|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/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size} * @param {Number} [highlightedSize=5] * @returns {Number|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/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size} * @param {Number} [excludedSize=null] * @returns {Number|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 * @param {Number} [excludedColor=null] * @returns {Number|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 * @param {Number} [excludedOpacity=1.0] * @returns {Number|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/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size} * @param {Number} [emptySize=0] * @returns {Number|dc.scatterPlot} */ _chart.hiddenSize = _chart.emptySize = function (emptySize) { if (!arguments.length) { return _emptySize; } _emptySize = emptySize; return _chart; }; /** * Set or get color for symbols when the group is empty. If null, just use the * {@link dc.colorMixin#colors colorMixin.colors} color scale zero value. * @name emptyColor * @memberof dc.scatterPlot * @instance * @param {String} [emptyColor=null] * @return {String} * @return {dc.scatterPlot}/ */ _chart.emptyColor = function (emptyColor) { if (!arguments.length) { return _emptyColor; } _emptyColor = emptyColor; return _chart; }; /** * Set or get opacity for symbols when the group is empty. * @name emptyOpacity * @memberof dc.scatterPlot * @instance * @param {Number} [emptyOpacity=0] * @return {Number} * @return {dc.scatterPlot} */ _chart.emptyOpacity = function (emptyOpacity) { if (!arguments.length) { return _emptyOpacity; } _emptyOpacity = emptyOpacity; return _chart; }; /** * Set or get opacity for symbols when the group is not empty. * @name nonemptyOpacity * @memberof dc.scatterPlot * @instance * @param {Number} [nonemptyOpacity=1] * @return {Number} * @return {dc.scatterPlot} */ _chart.nonemptyOpacity = function (nonemptyOpacity) { if (!arguments.length) { return _emptyOpacity; } _nonemptyOpacity = nonemptyOpacity; 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.chartBodyG().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.chartBodyG().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.chartBodyG().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(), _chart.transitionDelay()).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); };