| /* |
| * 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; |
| |
| /** |
| * Transforms the ChartData by transposing the columns and rows. A label column |
| * index in the original data will need to be specified (default to 0), all |
| * values in the specified label column will be used as the label for the |
| * transformed data, all the labels in the original Chart data columns will be |
| * populated in the label column as values of that column. |
| * |
| * All values in the data except for the data in the label column must have the |
| * same type; All columns except for the lable column must have the same |
| * formatter if a formatter exist for columns. |
| */ |
| class TransposeTransformer extends ChangeNotifier |
| implements ChartDataTransform, ChartData { |
| final SubscriptionsDisposer _dataSubscriptions = new SubscriptionsDisposer(); |
| ObservableList<ChartColumnSpec> columns = new ObservableList(); |
| ObservableList<Iterable> rows = new ObservableList(); |
| |
| // If specified, this values of this column in the input chart data will be |
| // used as labels of the transposed column label. Defaults to first column. |
| int _labelColumn; |
| ChartData _data; |
| |
| TransposeTransformer([this._labelColumn = 0]); |
| |
| /** |
| * Transforms the input data with the specified label column in the |
| * constructor. If the ChartData is Observable, changes fired by the input |
| * data will trigger tranform to be performed again to update the output rows |
| * and columns. |
| */ |
| ChartData transform(ChartData data) { |
| _data = data; |
| _registerListeners(); |
| _transform(); |
| return this; |
| } |
| |
| /** Registers listeners if input ChartData is Observable. */ |
| _registerListeners() { |
| _dataSubscriptions.dispose(); |
| |
| if(_data is Observable) { |
| var observable = (_data as Observable); |
| _dataSubscriptions.add(observable.changes.listen((records) { |
| _transform(); |
| |
| // NOTE: Currently we're only passing the first change because the chart |
| // area just draw with the updated data. When we add partial update |
| // to chart area, we'll need to handle this better. |
| notifyChange(records.first); |
| })); |
| } |
| } |
| |
| /** |
| * Performs the transpose transform with _data. This is called on transform |
| * and on changes if ChartData is Observable. |
| */ |
| _transform() { |
| // Assert all columns are of the same type and formatter, excluding the |
| // label column. |
| var type; |
| var formatter; |
| for (var i = 0; i < _data.columns.length; i++) { |
| if (i != _labelColumn) { |
| if (type == null) { |
| type = _data.columns.elementAt(i).type; |
| } else { |
| assert(type == _data.columns.elementAt(i).type); |
| } |
| if (formatter == null) { |
| formatter = _data.columns.elementAt(i).formatter; |
| } else { |
| assert(formatter == _data.columns.elementAt(i).formatter); |
| } |
| } |
| } |
| |
| columns.clear(); |
| rows.clear(); |
| rows.addAll(new List<Iterable>.generate(_data.columns.length - 1, (i) => [])); |
| |
| // Populate the transposed rows' data, excluding the label column, visit |
| // each value in the original data once. |
| var columnLabels = []; |
| for (var row in _data.rows) { |
| for (var i = 0; i < row.length; i++) { |
| var columnOffset = (i < _labelColumn) ? 0 : 1; |
| if (i != _labelColumn) { |
| (rows.elementAt(i - columnOffset) as List).add(row.elementAt(i)); |
| } else { |
| columnLabels.add(row.elementAt(i)); |
| } |
| } |
| } |
| |
| // Transpose the ColumnSpec's label into the column where the original |
| // column that is used as the new label. |
| for (var i = 0; i < rows.length; i++) { |
| var columnOffset = (i < _labelColumn) ? 0 : 1; |
| (rows.elementAt(i) as List).insert(_labelColumn, |
| _data.columns.elementAt(i + columnOffset).label); |
| |
| } |
| |
| // Construct new ColumnSpaces base on the label column. |
| for (var label in columnLabels) { |
| columns.add(new ChartColumnSpec(type: type, label: label, |
| formatter: formatter)); |
| } |
| columns.insert(_labelColumn, |
| new ChartColumnSpec(type: ChartColumnSpec.TYPE_STRING, label: |
| _data.columns.elementAt(_labelColumn).label)); |
| } |
| } |