blob: d1cad14395991994e8bcd6e809f7d58cf4e0fb6d [file] [log] [blame]
//
// 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 label 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<List> 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 transform 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;
FormatFunction 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<List>.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).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).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));
}
}