| 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 |