blob: 3441d0f39843c465ed2c590b93a97528ce2d029a [file] [log] [blame]
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file or at
* https://developers.google.com/open-source/licenses/bsd
*/
part of charted.charts;
class BubbleChartRenderer extends CartesianRendererBase {
final Iterable<int> dimensionsUsingBand = const[];
final double maxBubbleRadius;
final bool alwaysAnimate;
Element _host;
Selection _group;
SelectionScope _scope;
@override
final String name = "bubble-rdr";
StreamController<ChartEvent> _mouseOverController;
StreamController<ChartEvent> _mouseOutController;
StreamController<ChartEvent> _mouseClickController;
BubbleChartRenderer({
this.maxBubbleRadius: 20.0,
this.alwaysAnimate: false});
/*
* BubbleChart needs two dimension axes.
*/
@override
bool prepare(ChartArea area, ChartSeries series) {
_ensureAreaAndSeries(area, series);
return area is CartesianArea && area.useTwoDimensionAxes == true;
}
@override
void draw(Element element, {Future schedulePostRender}) {
assert(series != null && area != null);
assert(element != null && element is GElement);
if (_scope == null) {
_host = element;
_scope = new SelectionScope.element(element);
_group = _scope.selectElements([_host]);
}
var geometry = area.layout.renderArea,
measuresCount = series.measures.length,
bubbleRadiusScale = area.measureScales(series).first,
xDimensionScale = area.dimensionScales.first,
yDimensionScale = area.dimensionScales.last,
theme = area.theme,
bubbleRadiusFactor =
maxBubbleRadius / min([geometry.width, geometry.height]);
String color(i) => theme.getColorForKey(series.measures.elementAt(i));
// Measure values used to set size of the bubble.
var columns = [];
for (int m in series.measures) {
columns.add(new List.from(
area.data.rows.map((Iterable row) => row.elementAt(m))));
}
// Dimension values used to position the bubble.
var xDimensionIndex = area.config.dimensions.first,
yDimensionIndex = area.config.dimensions.last,
xDimensionVals = [],
yDimensionVals = [];
for (var row in area.data.rows) {
xDimensionVals.add(row.elementAt(xDimensionIndex));
yDimensionVals.add(row.elementAt(yDimensionIndex));
}
var group = _group.selectAll('.measure-group').data(columns);
group.enter.append('g')..classed('measure-group');
group.each((d, i, e) {
e.style.setProperty('fill', color(i));
e.attributes['data-column'] = series.measures.elementAt(i);
});
group.exit.remove();
var measures = group.selectAll('.bubble').dataWithCallback(
(d, i, e) => columns[i]);
measures.enter.append('circle')..classed('bubble');
measures.each((d, i, e) {
e.attributes
..['transform'] = 'translate('
'${xDimensionScale.scale(xDimensionVals[i])},'
'${yDimensionScale.scale(yDimensionVals[i])})'
..['r'] = '${bubbleRadiusScale.scale(d) * bubbleRadiusFactor}'
..['data-row'] = i;
});
measures.exit.remove();
handleStateChanges([]);
}
@override
void dispose() {
if (_group == null) return;
_group.selectAll('.row-group').remove();
}
@override
double get bandInnerPadding => 1.0;
@override
double get bandOuterPadding => area.theme.dimensionAxisTheme.axisOuterPadding;
@override
Extent get extent {
assert(series != null && area != null);
var rows = area.data.rows,
max = rows[0][series.measures.first],
min = max;
rows.forEach((row) {
series.measures.forEach((idx) {
if (row[idx] > max) max = row[idx];
if (row[idx] < min) min = row[idx];
});
});
return new Extent(min, max);
}
@override
void handleStateChanges(List<ChangeRecord> changes) {
var groups = host.querySelectorAll('.bar-rdr-rowgroup');
if (groups == null || groups.isEmpty) return;
for(int i = 0, len = groups.length; i < len; ++i) {
var group = groups.elementAt(i),
bars = group.querySelectorAll('.bar-rdr-bar'),
row = int.parse(group.dataset['row']);
for(int j = 0, barsCount = bars.length; j < barsCount; ++j) {
var bar = bars.elementAt(j),
column = int.parse(bar.dataset['column']),
color = colorForValue(column, row);
bar.classes.removeAll(ChartState.VALUE_CLASS_NAMES);
bar.classes.addAll(stylesForValue(column, row));
bar.style
..setProperty('fill', color)
..setProperty('stroke', color);
}
}
}
void _event(StreamController controller, data, int index, Element e) {
if (controller == null) return;
var rowStr = e.parent.dataset['row'];
var row = rowStr != null ? int.parse(rowStr) : null;
controller.add(
new _ChartEvent(_scope.event, area, series, row, index, data));
}
}