OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2014 Google Inc. All rights reserved. | |
3 * | |
4 * Use of this source code is governed by a BSD-style | |
5 * license that can be found in the LICENSE file or at | |
6 * https://developers.google.com/open-source/licenses/bsd | |
7 */ | |
8 | |
9 part of charted.charts; | |
10 | |
11 class BubbleChartRenderer extends CartesianRendererBase { | |
12 final Iterable<int> dimensionsUsingBand = const[]; | |
13 final double maxBubbleRadius; | |
14 final bool alwaysAnimate; | |
15 | |
16 Element _host; | |
17 Selection _group; | |
18 SelectionScope _scope; | |
19 | |
20 @override | |
21 final String name = "bubble-rdr"; | |
22 | |
23 BubbleChartRenderer({ | |
24 this.maxBubbleRadius: 20.0, | |
25 this.alwaysAnimate: false}); | |
26 | |
27 /* | |
28 * BubbleChart needs two dimension axes. | |
29 */ | |
30 @override | |
31 bool prepare(ChartArea area, ChartSeries series) { | |
32 _ensureAreaAndSeries(area, series); | |
33 return area is CartesianArea && area.useTwoDimensionAxes == true; | |
34 } | |
35 | |
36 @override | |
37 void draw(Element element, {Future schedulePostRender}) { | |
38 assert(series != null && area != null); | |
39 assert(element != null && element is GElement); | |
40 | |
41 if (_scope == null) { | |
42 _host = element; | |
43 _scope = new SelectionScope.element(element); | |
44 _group = _scope.selectElements([_host]); | |
45 } | |
46 | |
47 var geometry = area.layout.renderArea, | |
48 bubbleRadiusScale = area.measureScales(series).first, | |
49 xDimensionScale = area.dimensionScales.first, | |
50 yDimensionScale = area.dimensionScales.last, | |
51 theme = area.theme, | |
52 bubbleRadiusFactor = | |
53 maxBubbleRadius / min([geometry.width, geometry.height]); | |
54 | |
55 String color(i) => theme.getColorForKey(series.measures.elementAt(i)); | |
56 | |
57 // Measure values used to set size of the bubble. | |
58 var columns = []; | |
59 for (int m in series.measures) { | |
60 columns.add(new List.from( | |
61 area.data.rows.map((Iterable row) => row.elementAt(m)))); | |
62 } | |
63 | |
64 // Dimension values used to position the bubble. | |
65 var xDimensionIndex = area.config.dimensions.first, | |
66 yDimensionIndex = area.config.dimensions.last, | |
67 xDimensionVals = [], | |
68 yDimensionVals = []; | |
69 for (var row in area.data.rows) { | |
70 xDimensionVals.add(row.elementAt(xDimensionIndex)); | |
71 yDimensionVals.add(row.elementAt(yDimensionIndex)); | |
72 } | |
73 | |
74 var group = _group.selectAll('.measure-group').data(columns); | |
75 group.enter.append('g')..classed('measure-group'); | |
76 group.each((d, i, e) { | |
77 e.style.setProperty('fill', color(i)); | |
78 e.attributes['data-column'] = series.measures.elementAt(i); | |
79 }); | |
80 group.exit.remove(); | |
81 | |
82 var measures = group.selectAll('.bubble').dataWithCallback( | |
83 (d, i, e) => columns[i]); | |
84 | |
85 measures.enter.append('circle')..classed('bubble'); | |
86 measures.each((d, i, e) { | |
87 e.attributes | |
88 ..['transform'] = 'translate(' | |
89 '${xDimensionScale.scale(xDimensionVals[i])},' | |
90 '${yDimensionScale.scale(yDimensionVals[i])})' | |
91 ..['r'] = '${bubbleRadiusScale.scale(d) * bubbleRadiusFactor}' | |
92 ..['data-row'] = i; | |
93 }); | |
94 measures.exit.remove(); | |
95 handleStateChanges([]); | |
96 } | |
97 | |
98 @override | |
99 void dispose() { | |
100 if (_group == null) return; | |
101 _group.selectAll('.row-group').remove(); | |
102 } | |
103 | |
104 @override | |
105 double get bandInnerPadding => 1.0; | |
106 | |
107 @override | |
108 double get bandOuterPadding => | |
109 area.theme.getDimensionAxisTheme().axisOuterPadding; | |
110 | |
111 @override | |
112 Extent get extent { | |
113 assert(series != null && area != null); | |
114 var rows = area.data.rows, | |
115 max = rows[0][series.measures.first], | |
116 min = max; | |
117 | |
118 rows.forEach((row) { | |
119 series.measures.forEach((idx) { | |
120 if (row[idx] > max) max = row[idx]; | |
121 if (row[idx] < min) min = row[idx]; | |
122 }); | |
123 }); | |
124 return new Extent(min, max); | |
125 } | |
126 | |
127 @override | |
128 void handleStateChanges(List<ChangeRecord> changes) { | |
129 var groups = host.querySelectorAll('.bar-rdr-rowgroup'); | |
130 if (groups == null || groups.isEmpty) return; | |
131 | |
132 for(int i = 0, len = groups.length; i < len; ++i) { | |
133 var group = groups.elementAt(i), | |
134 bars = group.querySelectorAll('.bar-rdr-bar'), | |
135 row = int.parse(group.dataset['row']); | |
136 | |
137 for(int j = 0, barsCount = bars.length; j < barsCount; ++j) { | |
138 var bar = bars.elementAt(j), | |
139 column = int.parse(bar.dataset['column']), | |
140 color = colorForValue(column, row); | |
141 | |
142 bar.classes.removeAll(ChartState.VALUE_CLASS_NAMES); | |
143 bar.classes.addAll(stylesForValue(column, row)); | |
144 bar.style | |
145 ..setProperty('fill', color) | |
146 ..setProperty('stroke', color); | |
147 } | |
148 } | |
149 } | |
150 | |
151 void _event(StreamController controller, data, int index, Element e) { | |
152 if (controller == null) return; | |
153 var rowStr = e.parent.dataset['row']; | |
154 var row = rowStr != null ? int.parse(rowStr) : null; | |
155 controller.add( | |
156 new DefaultChartEventImpl(_scope.event, area, series, row, index, data))
; | |
157 } | |
158 } | |
OLD | NEW |