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 /** | 11 /// AggregationItem is created by [AggregationModel] to make access to facts |
12 * AggregationItem is created by [AggregationModel] to make access to facts | 12 /// observable. Users must use AggregationItem.isValid before trying to access |
13 * observable. Users must use AggregationItem.isValid before trying to access | 13 /// the aggregations. |
14 * the aggregations. | |
15 */ | |
16 abstract class AggregationItem extends ChangeNotifier { | 14 abstract class AggregationItem extends ChangeNotifier { |
17 /** | 15 /// List of dimension fields in effect |
18 * List of dimension fields in effect | |
19 */ | |
20 List<String> dimensions; | 16 List<String> dimensions; |
21 | 17 |
22 /** | 18 /// Check if this entity is valid. |
23 * Check if this entity is valid. | 19 /// Currently the only case where an entity becomes invalid |
24 * Currently the only case where an entity becomes invalid | 20 /// is when a groupBy is called on the model. |
25 * is when a groupBy is called on the model. | |
26 */ | |
27 bool get isValid; | 21 bool get isValid; |
28 | 22 |
29 /** | 23 /// Fetch the fact from AggregationModel and return it |
30 * Fetch the fact from AggregationModel and return it | 24 /// Currently takes keys in the form of "sum(spend)", where sum is |
31 * Currently takes keys in the form of "sum(spend)", where sum is | 25 /// the aggregation type and spend is fact's field name. |
32 * the aggregation type and spend is fact's field name. | 26 /// |
33 * | 27 /// Currently, "sum", "count", "min", "max", "avg", "valid" and "avgOfValid" |
34 * Currently, "sum", "count", "min", "max", "avg", "valid" and "avgOfValid" | 28 /// are supported as the operators. |
35 * are supported as the operators. | 29 operator [](String key); |
36 */ | |
37 | 30 |
38 operator[](String key); | 31 /// Check if we support a given key. |
39 | |
40 /** | |
41 * Check if we support a given key. | |
42 */ | |
43 bool containsKey(String key); | 32 bool containsKey(String key); |
44 | 33 |
45 /** | 34 /// List of valid field names for this entity. |
46 * List of valid field names for this entity. | 35 /// It's the combined list of accessors for individual items, items in |
47 * It's the combined list of accessors for individual items, items in | 36 /// the next dimension and all possible facts defined on the view. |
48 * the next dimension and all possible facts defined on the view. | |
49 */ | |
50 Iterable<String> get fieldNames; | 37 Iterable<String> get fieldNames; |
51 } | 38 } |
52 | 39 |
53 /* | 40 /// Implementation of AggregationItem |
54 * Implementation of AggregationItem | 41 /// Instances of _AggregationItemImpl are created only by AggregationModel |
55 * Instances of _AggregationItemImpl are created only by AggregationModel | |
56 */ | |
57 class _AggregationItemImpl extends ChangeNotifier implements AggregationItem { | 42 class _AggregationItemImpl extends ChangeNotifier implements AggregationItem { |
58 static final List<String> derivedAggregationTypes = ['count', 'avg']; | 43 static final List<String> derivedAggregationTypes = ['count', 'avg']; |
59 | 44 |
60 AggregationModel model; | 45 AggregationModel model; |
61 List<String> dimensions; | 46 List<String> dimensions; |
62 | 47 |
63 String _key; | 48 String _key; |
64 | 49 |
65 int _factsOffset; | 50 int _factsOffset; |
66 | 51 |
67 /* | 52 /// Currently entities are created only when they have valid aggregations |
68 * Currently entities are created only when they have valid aggregations | |
69 */ | |
70 _AggregationItemImpl(this.model, this.dimensions, this._key) { | 53 _AggregationItemImpl(this.model, this.dimensions, this._key) { |
71 if (model == null) { | 54 if (model == null) { |
72 throw new ArgumentError('Model cannot be null'); | 55 throw new ArgumentError('Model cannot be null'); |
73 } | 56 } |
74 if (_key == null) { | 57 if (_key == null) { |
75 _key = ''; | 58 _key = ''; |
76 } | 59 } |
77 | 60 |
78 // facts + list of items + list of children (drilldown) | 61 // facts + list of items + list of children (drilldown) |
79 _factsOffset = model._dimToAggrMap[_key]; | 62 _factsOffset = model._dimToAggrMap[_key]; |
80 } | 63 } |
81 | 64 |
82 /** | 65 /// _dimToAggrMap got updated on the model, update ourselves accordingly |
83 * _dimToAggrMap got updated on the model, update ourselves accordingly | |
84 */ | |
85 void update() { | 66 void update() { |
86 _factsOffset = model._dimToAggrMap[_key]; | 67 _factsOffset = model._dimToAggrMap[_key]; |
87 } | 68 } |
88 | 69 |
89 /* | 70 /// Mark this entity as invalid. |
90 * Mark this entity as invalid. | |
91 */ | |
92 void clear() { | 71 void clear() { |
93 _factsOffset = null; | 72 _factsOffset = null; |
94 } | 73 } |
95 | 74 |
96 bool get isValid => _factsOffset != null; | 75 bool get isValid => _factsOffset != null; |
97 | 76 |
98 dynamic operator[](String key) { | 77 dynamic operator [](String key) { |
99 if (!isValid) { | 78 if (!isValid) { |
100 throw new StateError('Entity is not valid anymore'); | 79 throw new StateError('Entity is not valid anymore'); |
101 } | 80 } |
102 | 81 |
103 int argPos = key.indexOf('('); | 82 int argPos = key.indexOf('('); |
104 if (argPos == -1) { | 83 if (argPos == -1) { |
105 return _nonAggregationMember(key); | 84 return _nonAggregationMember(key); |
106 } | 85 } |
107 | 86 |
108 String aggrFunc = key.substring(0, argPos); | 87 String aggrFunc = key.substring(0, argPos); |
109 int aggrFuncIndex = model.computedAggregationTypes.indexOf(aggrFunc); | 88 int aggrFuncIndex = model.computedAggregationTypes.indexOf(aggrFunc); |
110 if (aggrFuncIndex == -1 && !derivedAggregationTypes.contains(aggrFunc)) { | 89 if (aggrFuncIndex == -1 && !derivedAggregationTypes.contains(aggrFunc)) { |
111 throw new ArgumentError('Unknown aggregation method: ${aggrFunc}'); | 90 throw new ArgumentError('Unknown aggregation method: ${aggrFunc}'); |
112 } | 91 } |
113 | 92 |
114 String factName = key.substring(argPos + 1, key.lastIndexOf(')')); | 93 String factName = key.substring(argPos + 1, key.lastIndexOf(')')); |
115 int factIndex = model._factFields.indexOf(factName); | 94 int factIndex = model._factFields.indexOf(factName); |
116 | 95 |
117 // Try parsing int if every element in factFields is int. | 96 // Try parsing int if every element in factFields is int. |
118 if (model._factFields.every((e) => e is int)) { | 97 if (model._factFields.every((e) => e is int)) { |
119 factIndex = model._factFields.indexOf(int.parse(factName, | 98 factIndex = model._factFields.indexOf(int.parse(factName, onError: (e) { |
120 onError: (e) { | 99 throw new ArgumentError('Type of factFields are int but factName' + |
121 throw new ArgumentError('Type of factFields are int but factName' + | 100 'contains non int value'); |
122 'contains non int value'); | 101 })); |
123 })); | |
124 } | 102 } |
125 if (factIndex == -1) { | 103 if (factIndex == -1) { |
126 throw new ArgumentError('Model not configured for ${factName}'); | 104 throw new ArgumentError('Model not configured for ${factName}'); |
127 } | 105 } |
128 | 106 |
129 int offset = _factsOffset + factIndex * model._aggregationTypesCount; | 107 int offset = _factsOffset + factIndex * model._aggregationTypesCount; |
130 // No items for the corresponding fact, so return null. | 108 // No items for the corresponding fact, so return null. |
131 if (aggrFunc != 'count' && aggrFunc != 'avg' && | 109 if (aggrFunc != 'count' && |
| 110 aggrFunc != 'avg' && |
132 model._aggregations[offset + model._offsetCnt].toInt() == 0) { | 111 model._aggregations[offset + model._offsetCnt].toInt() == 0) { |
133 return null; | 112 return null; |
134 } | 113 } |
135 | 114 |
136 if (aggrFuncIndex != -1) { | 115 if (aggrFuncIndex != -1) { |
137 return model._aggregations[offset + aggrFuncIndex]; | 116 return model._aggregations[offset + aggrFuncIndex]; |
138 } else if (aggrFunc == 'count') { | 117 } else if (aggrFunc == 'count') { |
139 return model._aggregations[_factsOffset + | 118 return model._aggregations[_factsOffset + model._offsetFilteredCount] |
140 model._offsetFilteredCount].toInt(); | 119 .toInt(); |
141 } else if (aggrFunc == 'avg') { | 120 } else if (aggrFunc == 'avg') { |
142 return model._aggregations[offset + model._offsetSum] / | 121 return model._aggregations[offset + model._offsetSum] / |
143 model._aggregations[_factsOffset + model._offsetFilteredCount]. | 122 model._aggregations[_factsOffset + model._offsetFilteredCount] |
144 toInt(); | 123 .toInt(); |
145 } else if (aggrFunc == 'avgOfValid') { | 124 } else if (aggrFunc == 'avgOfValid') { |
146 return model._aggregations[offset + model._offsetSum] / | 125 return model._aggregations[offset + model._offsetSum] / |
147 model._aggregations[offset + model._offsetCnt].toInt(); | 126 model._aggregations[offset + model._offsetCnt].toInt(); |
148 } | 127 } |
149 return null; | 128 return null; |
150 } | 129 } |
151 | 130 |
152 dynamic _nonAggregationMember(String key) { | 131 dynamic _nonAggregationMember(String key) { |
153 if (key == 'items') { | 132 if (key == 'items') { |
154 return new _AggregationItemsIterator(model, dimensions, _key); | 133 return new _AggregationItemsIterator(model, dimensions, _key); |
155 } | 134 } |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
191 model._factFields.forEach((var name) { | 170 model._factFields.forEach((var name) { |
192 AggregationModel.supportedAggregationTypes.forEach((String aggrType) { | 171 AggregationModel.supportedAggregationTypes.forEach((String aggrType) { |
193 cache.add('${aggrType}(${name})'); | 172 cache.add('${aggrType}(${name})'); |
194 }); | 173 }); |
195 }); | 174 }); |
196 model._itemFieldNamesCache = cache; | 175 model._itemFieldNamesCache = cache; |
197 } | 176 } |
198 return model._itemFieldNamesCache; | 177 return model._itemFieldNamesCache; |
199 } | 178 } |
200 | 179 |
201 /* | 180 // TODO(prsd): Implementation of [Observable] |
202 * TODO(prsd): Implementation of [Observable] | |
203 */ | |
204 Stream<List<ChangeRecord>> get changes { | 181 Stream<List<ChangeRecord>> get changes { |
205 throw new UnimplementedError(); | 182 throw new UnimplementedError(); |
206 } | 183 } |
207 } | 184 } |
208 | 185 |
209 class _AggregationItemsIterator implements Iterator { | 186 class _AggregationItemsIterator implements Iterator { |
210 final AggregationModel model; | 187 final AggregationModel model; |
211 List<String> dimensions; | 188 List<String> dimensions; |
212 String key; | 189 String key; |
213 | 190 |
214 int _current; | 191 int _current; |
215 int _counter = 0; | 192 int _counter = 0; |
216 | 193 |
217 int _start; | 194 int _start; |
218 int _count; | 195 int _count; |
219 int _endOfRows; | 196 int _endOfRows; |
220 | 197 |
221 _AggregationItemsIterator(this.model, List<String> this.dimensions, | 198 _AggregationItemsIterator( |
222 String this.key) { | 199 this.model, List<String> this.dimensions, String this.key) { |
223 int offset = model._dimToAggrMap[key]; | 200 int offset = model._dimToAggrMap[key]; |
224 if (offset != null) { | 201 if (offset != null) { |
225 int factsEndOffset = offset + | 202 int factsEndOffset = |
226 model._factFields.length * model._aggregationTypesCount; | 203 offset + model._factFields.length * model._aggregationTypesCount; |
227 _start = model._aggregations[factsEndOffset].toInt(); | 204 _start = model._aggregations[factsEndOffset].toInt(); |
228 _count = model._aggregations[factsEndOffset + 1].toInt(); | 205 _count = model._aggregations[factsEndOffset + 1].toInt(); |
229 _endOfRows = model._rows.length; | 206 _endOfRows = model._rows.length; |
230 } | 207 } |
231 } | 208 } |
232 | 209 |
233 bool moveNext() { | 210 bool moveNext() { |
234 if (_current == null) { | 211 if (_current == null) { |
235 _current = _start; | 212 _current = _start; |
236 } else { | 213 } else { |
237 ++_current; | 214 ++_current; |
238 } | 215 } |
239 | 216 |
240 if (++_counter > _count) { | 217 if (++_counter > _count) { |
241 return false; | 218 return false; |
242 } | 219 } |
243 | 220 |
244 /* | 221 // If model had a filter applied, then check if _current points to a |
245 * If model had a filter applied, then check if _current points to a | 222 // filtered-in row, else skip till we find one. |
246 * filtered-in row, else skip till we find one. | 223 // Also, make sure (even if something else went wrong) we don't go |
247 * Also, make sure (even if something else went wrong) we don't go | 224 // beyond the number of items in the model. |
248 * beyond the number of items in the model. | |
249 */ | |
250 if (this.model._filterResults != null) { | 225 if (this.model._filterResults != null) { |
251 while ((this.model._filterResults[_current ~/ AggregationModel.SMI_BITS] & | 226 while ((this.model._filterResults[_current ~/ AggregationModel.SMI_BITS] & |
252 (1 << _current % AggregationModel.SMI_BITS)) == 0 && | 227 (1 << _current % AggregationModel.SMI_BITS)) == |
| 228 0 && |
253 _current <= _endOfRows) { | 229 _current <= _endOfRows) { |
254 ++_current; | 230 ++_current; |
255 } | 231 } |
256 } | 232 } |
257 return (_current < _endOfRows); | 233 return (_current < _endOfRows); |
258 } | 234 } |
259 | 235 |
260 get current { | 236 get current { |
261 if (_current == null || _counter > _count) { | 237 if (_current == null || _counter > _count) { |
262 return null; | 238 return null; |
263 } | 239 } |
264 return model._rows[model._sorted[_current]]; | 240 return model._rows[model._sorted[_current]]; |
265 } | 241 } |
266 } | 242 } |
OLD | NEW |