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