dc
Version:
A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js
288 lines (253 loc) • 8.73 kB
JavaScript
/**
* This Mixin provides reusable functionalities for any chart that needs to visualize data using bubbles.
* @name bubbleMixin
* @memberof dc
* @mixin
* @mixes dc.colorMixin
* @param {Object} _chart
* @returns {dc.bubbleMixin}
*/
dc.bubbleMixin = function (_chart) {
var _maxBubbleRelativeSize = 0.3;
var _minRadiusWithLabel = 10;
var _sortBubbleSize = false;
var _elasticRadius = false;
_chart.BUBBLE_NODE_CLASS = 'node';
_chart.BUBBLE_CLASS = 'bubble';
_chart.MIN_RADIUS = 10;
_chart = dc.colorMixin(_chart);
_chart.renderLabel(true);
_chart.data(function (group) {
var data = group.all();
if (_sortBubbleSize) {
// sort descending so smaller bubbles are on top
var radiusAccessor = _chart.radiusValueAccessor();
data.sort(function (a, b) { return d3.descending(radiusAccessor(a), radiusAccessor(b)); });
}
return data;
});
var _r = d3.scale.linear().domain([0, 100]);
var _rValueAccessor = function (d) {
return d.r;
};
/**
* Get or set the bubble radius scale. By default the bubble chart uses
* {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#linear d3.scale.linear().domain([0, 100])}
* as its radius scale.
* @method r
* @memberof dc.bubbleMixin
* @instance
* @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
* @param {d3.scale} [bubbleRadiusScale=d3.scale.linear().domain([0, 100])]
* @returns {d3.scale|dc.bubbleMixin}
*/
_chart.r = function (bubbleRadiusScale) {
if (!arguments.length) {
return _r;
}
_r = bubbleRadiusScale;
return _chart;
};
/**
* Turn on or off the elastic bubble radius feature, or return the value of the flag. If this
* feature is turned on, then bubble radii will be automatically rescaled to fit the chart better.
* @method elasticRadius
* @memberof dc.bubbleChart
* @instance
* @param {Boolean} [elasticRadius=false]
* @returns {Boolean|dc.bubbleChart}
*/
_chart.elasticRadius = function (elasticRadius) {
if (!arguments.length) {
return _elasticRadius;
}
_elasticRadius = elasticRadius;
return _chart;
};
_chart.calculateRadiusDomain = function () {
if (_elasticRadius) {
_chart.r().domain([_chart.rMin(), _chart.rMax()]);
}
};
/**
* Get or set the radius value accessor function. If set, the radius value accessor function will
* be used to retrieve a data value for each bubble. The data retrieved then will be mapped using
* the r scale to the actual bubble radius. This allows you to encode a data dimension using bubble
* size.
* @method radiusValueAccessor
* @memberof dc.bubbleMixin
* @instance
* @param {Function} [radiusValueAccessor]
* @returns {Function|dc.bubbleMixin}
*/
_chart.radiusValueAccessor = function (radiusValueAccessor) {
if (!arguments.length) {
return _rValueAccessor;
}
_rValueAccessor = radiusValueAccessor;
return _chart;
};
_chart.rMin = function () {
var min = d3.min(_chart.data(), function (e) {
return _chart.radiusValueAccessor()(e);
});
return min;
};
_chart.rMax = function () {
var max = d3.max(_chart.data(), function (e) {
return _chart.radiusValueAccessor()(e);
});
return max;
};
_chart.bubbleR = function (d) {
var value = _chart.radiusValueAccessor()(d);
var r = _chart.r()(value);
if (isNaN(r) || value <= 0) {
r = 0;
}
return r;
};
var labelFunction = function (d) {
return _chart.label()(d);
};
var shouldLabel = function (d) {
return (_chart.bubbleR(d) > _minRadiusWithLabel);
};
var labelOpacity = function (d) {
return shouldLabel(d) ? 1 : 0;
};
var labelPointerEvent = function (d) {
return shouldLabel(d) ? 'all' : 'none';
};
_chart._doRenderLabel = function (bubbleGEnter) {
if (_chart.renderLabel()) {
var label = bubbleGEnter.select('text');
if (label.empty()) {
label = bubbleGEnter.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.3em')
.on('click', _chart.onClick);
}
label
.attr('opacity', 0)
.attr('pointer-events', labelPointerEvent)
.text(labelFunction);
dc.transition(label, _chart.transitionDuration(), _chart.transitionDelay())
.attr('opacity', labelOpacity);
}
};
_chart.doUpdateLabels = function (bubbleGEnter) {
if (_chart.renderLabel()) {
var labels = bubbleGEnter.select('text')
.attr('pointer-events', labelPointerEvent)
.text(labelFunction);
dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay())
.attr('opacity', labelOpacity);
}
};
var titleFunction = function (d) {
return _chart.title()(d);
};
_chart._doRenderTitles = function (g) {
if (_chart.renderTitle()) {
var title = g.select('title');
if (title.empty()) {
g.append('title').text(titleFunction);
}
}
};
_chart.doUpdateTitles = function (g) {
if (_chart.renderTitle()) {
g.select('title').text(titleFunction);
}
};
/**
* Turn on or off the bubble sorting feature, or return the value of the flag. If enabled,
* bubbles will be sorted by their radius, with smaller bubbles in front.
* @method sortBubbleSize
* @memberof dc.bubbleChart
* @instance
* @param {Boolean} [sortBubbleSize=false]
* @returns {Boolean|dc.bubbleChart}
*/
_chart.sortBubbleSize = function (sortBubbleSize) {
if (!arguments.length) {
return _sortBubbleSize;
}
_sortBubbleSize = sortBubbleSize;
return _chart;
};
/**
* Get or set the minimum radius. This will be used to initialize the radius scale's range.
* @method minRadius
* @memberof dc.bubbleMixin
* @instance
* @param {Number} [radius=10]
* @returns {Number|dc.bubbleMixin}
*/
_chart.minRadius = function (radius) {
if (!arguments.length) {
return _chart.MIN_RADIUS;
}
_chart.MIN_RADIUS = radius;
return _chart;
};
/**
* Get or set the minimum radius for label rendering. If a bubble's radius is less than this value
* then no label will be rendered.
* @method minRadiusWithLabel
* @memberof dc.bubbleMixin
* @instance
* @param {Number} [radius=10]
* @returns {Number|dc.bubbleMixin}
*/
_chart.minRadiusWithLabel = function (radius) {
if (!arguments.length) {
return _minRadiusWithLabel;
}
_minRadiusWithLabel = radius;
return _chart;
};
/**
* Get or set the maximum relative size of a bubble to the length of x axis. This value is useful
* when the difference in radius between bubbles is too great.
* @method maxBubbleRelativeSize
* @memberof dc.bubbleMixin
* @instance
* @param {Number} [relativeSize=0.3]
* @returns {Number|dc.bubbleMixin}
*/
_chart.maxBubbleRelativeSize = function (relativeSize) {
if (!arguments.length) {
return _maxBubbleRelativeSize;
}
_maxBubbleRelativeSize = relativeSize;
return _chart;
};
_chart.fadeDeselectedArea = function () {
if (_chart.hasFilter()) {
_chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function (d) {
if (_chart.isSelectedNode(d)) {
_chart.highlightSelected(this);
} else {
_chart.fadeDeselected(this);
}
});
} else {
_chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function () {
_chart.resetHighlight(this);
});
}
};
_chart.isSelectedNode = function (d) {
return _chart.hasFilter(d.key);
};
_chart.onClick = function (d) {
var filter = d.key;
dc.events.trigger(function () {
_chart.filter(filter);
_chart.redrawGroup();
});
};
return _chart;
};