You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/web/charts/common/test/chart/line/line_renderer_test.dart

641 lines
23 KiB

// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:charts_common/src/chart/line/line_renderer.dart';
import 'package:charts_common/src/chart/line/line_renderer_config.dart';
import 'package:charts_common/src/chart/common/processed_series.dart'
show MutableSeries, ImmutableSeries;
import 'package:charts_common/src/common/color.dart';
import 'package:charts_common/src/common/material_palette.dart'
show MaterialPalette;
import 'package:charts_common/src/data/series.dart' show Series;
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
/// Datum/Row for the chart.
class MyRow {
final String campaignString;
final int campaign;
final int clickCount;
final Color color;
final List<int> dashPattern;
final double strokeWidthPx;
MyRow(this.campaignString, this.campaign, this.clickCount, this.color,
this.dashPattern, this.strokeWidthPx);
}
class MockImmutableSeries<D> extends Mock implements ImmutableSeries<D> {
String _id;
MockImmutableSeries(this._id);
@override
String get id => _id;
}
void main() {
LineRenderer renderer;
List<MutableSeries<int>> numericSeriesList;
List<MutableSeries<String>> ordinalSeriesList;
List<MyRow> myFakeDesktopData;
List<MyRow> myFakeTabletData;
List<MyRow> myFakeMobileData;
setUp(() {
myFakeDesktopData = [
MyRow('MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, null, 2.0),
MyRow(
'MyCampaign2', 2, 25, MaterialPalette.green.shadeDefault, null, 2.0),
MyRow('MyCampaign3', 3, 100, MaterialPalette.red.shadeDefault, null, 2.0),
MyRow('MyOtherCampaign', 4, 75, MaterialPalette.red.shadeDefault, null,
2.0),
];
myFakeTabletData = [
MyRow(
'MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, [2, 2], 2.0),
MyRow(
'MyCampaign2', 2, 25, MaterialPalette.blue.shadeDefault, [3, 3], 2.0),
MyRow('MyCampaign3', 3, 100, MaterialPalette.blue.shadeDefault, [4, 4],
2.0),
MyRow('MyOtherCampaign', 4, 75, MaterialPalette.blue.shadeDefault, [4, 4],
2.0),
];
myFakeMobileData = [
MyRow('MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, null, 2.0),
MyRow('MyCampaign2', 2, 25, MaterialPalette.blue.shadeDefault, null, 3.0),
MyRow(
'MyCampaign3', 3, 100, MaterialPalette.blue.shadeDefault, null, 4.0),
MyRow('MyOtherCampaign', 4, 75, MaterialPalette.blue.shadeDefault, null,
4.0),
];
numericSeriesList = [
MutableSeries<int>(Series<MyRow, int>(
id: 'Desktop',
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (dynamic row, _) => row.campaign,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
data: myFakeDesktopData)),
MutableSeries<int>(Series<MyRow, int>(
id: 'Tablet',
colorFn: (_, __) => MaterialPalette.red.shadeDefault,
domainFn: (dynamic row, _) => row.campaign,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
strokeWidthPxFn: (_, __) => 1.25,
data: myFakeTabletData)),
MutableSeries<int>(Series<MyRow, int>(
id: 'Mobile',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (dynamic row, _) => row.campaign,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
strokeWidthPxFn: (_, __) => 3.0,
data: myFakeMobileData))
];
ordinalSeriesList = [
MutableSeries<String>(Series<MyRow, String>(
id: 'Desktop',
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (dynamic row, _) => row.campaignString,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
data: myFakeDesktopData)),
MutableSeries<String>(Series<MyRow, String>(
id: 'Tablet',
colorFn: (_, __) => MaterialPalette.red.shadeDefault,
domainFn: (dynamic row, _) => row.campaignString,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
strokeWidthPxFn: (_, __) => 1.25,
data: myFakeTabletData)),
MutableSeries<String>(Series<MyRow, String>(
id: 'Mobile',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (dynamic row, _) => row.campaignString,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
strokeWidthPxFn: (_, __) => 3.0,
data: myFakeMobileData))
];
});
group('preprocess', () {
test('with numeric data and simple lines', () {
renderer =
LineRenderer<num>(config: LineRendererConfig(strokeWidthPx: 2.0));
renderer.configureSeries(numericSeriesList);
renderer.preprocessSeries(numericSeriesList);
expect(numericSeriesList.length, equals(3));
// Validate Desktop series.
var series = numericSeriesList[0];
var styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
var segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(2.0));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
// Validate Tablet series.
series = numericSeriesList[1];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.red.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(1.25));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
// Validate Mobile series.
series = numericSeriesList[2];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(3.0));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
});
test('with numeric data and stacked lines', () {
renderer = LineRenderer<num>(
config: LineRendererConfig(stacked: true, strokeWidthPx: 2.0));
renderer.configureSeries(numericSeriesList);
renderer.preprocessSeries(numericSeriesList);
expect(numericSeriesList.length, equals(3));
// Validate Desktop series.
var series = numericSeriesList[0];
var styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
var segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(2.0));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
// Validate Tablet series.
series = numericSeriesList[1];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.red.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(1.25));
expect(series.measureOffsetFn(0), 5);
expect(series.measureOffsetFn(1), 25);
expect(series.measureOffsetFn(2), 100);
expect(series.measureOffsetFn(3), 75);
// Validate Mobile series.
series = numericSeriesList[2];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(3.0));
expect(series.measureOffsetFn(0), 10);
expect(series.measureOffsetFn(1), 50);
expect(series.measureOffsetFn(2), 200);
expect(series.measureOffsetFn(3), 150);
});
test('with numeric data and changes in style', () {
numericSeriesList = [
MutableSeries<int>(Series<MyRow, int>(
id: 'Desktop',
colorFn: (row, _) => row.color,
dashPatternFn: (row, _) => row.dashPattern,
strokeWidthPxFn: (row, _) => row.strokeWidthPx,
domainFn: (dynamic row, _) => row.campaign,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
data: myFakeDesktopData)),
MutableSeries<int>(Series<MyRow, int>(
id: 'Tablet',
colorFn: (row, _) => row.color,
dashPatternFn: (row, _) => row.dashPattern,
strokeWidthPxFn: (row, _) => row.strokeWidthPx,
domainFn: (dynamic row, _) => row.campaign,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
data: myFakeTabletData)),
MutableSeries<int>(Series<MyRow, int>(
id: 'Mobile',
colorFn: (row, _) => row.color,
dashPatternFn: (row, _) => row.dashPattern,
strokeWidthPxFn: (row, _) => row.strokeWidthPx,
domainFn: (dynamic row, _) => row.campaign,
measureFn: (dynamic row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
data: myFakeMobileData))
];
renderer =
LineRenderer<num>(config: LineRendererConfig(strokeWidthPx: 2.0));
renderer.configureSeries(numericSeriesList);
renderer.preprocessSeries(numericSeriesList);
expect(numericSeriesList.length, equals(3));
// Validate Desktop series.
var series = numericSeriesList[0];
var styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(3));
var segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(2));
expect(segment.strokeWidthPx, equals(2.0));
segment = styleSegments[1];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(2));
expect(segment.domainExtent.end, equals(3));
expect(segment.strokeWidthPx, equals(2.0));
segment = styleSegments[2];
expect(segment.color, equals(MaterialPalette.red.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(3));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(2.0));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
// Validate Tablet series.
series = numericSeriesList[1];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(3));
segment = segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, equals([2, 2]));
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(2));
expect(segment.strokeWidthPx, equals(2.0));
segment = styleSegments[1];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, equals([3, 3]));
expect(segment.domainExtent.start, equals(2));
expect(segment.domainExtent.end, equals(3));
expect(segment.strokeWidthPx, equals(2.0));
segment = styleSegments[2];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, equals([4, 4]));
expect(segment.domainExtent.start, equals(3));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(2.0));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
// Validate Mobile series.
series = numericSeriesList[2];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(3));
segment = segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(2));
expect(segment.strokeWidthPx, equals(2.0));
segment = styleSegments[1];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(2));
expect(segment.domainExtent.end, equals(3));
expect(segment.strokeWidthPx, equals(3.0));
segment = styleSegments[2];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals(3));
expect(segment.domainExtent.end, equals(4));
expect(segment.strokeWidthPx, equals(4.0));
expect(series.measureOffsetFn(0), 0);
expect(series.measureOffsetFn(1), 0);
expect(series.measureOffsetFn(2), 0);
expect(series.measureOffsetFn(3), 0);
});
test('with numeric data and repeats in style', () {
var myFakeData = [
MyRow(
'MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, null, 2.0),
MyRow('MyCampaign2', 2, 25, MaterialPalette.green.shadeDefault, null,
2.0),
MyRow('MyCampaign3', 3, 100, MaterialPalette.blue.shadeDefault, null,
2.0),
MyRow('MyCampaign4', 4, 75, MaterialPalette.green.shadeDefault, null,
2.0),
MyRow(
'MyCampaign1', 5, 5, MaterialPalette.blue.shadeDefault, null, 2.0),
MyRow('MyCampaign2', 6, 25, MaterialPalette.green.shadeDefault, null,
2.0),
MyRow('MyCampaign3', 7, 100, MaterialPalette.blue.shadeDefault, null,
2.0),
MyRow('MyCampaign4', 8, 75, MaterialPalette.green.shadeDefault, null,
2.0),
];
numericSeriesList = [
MutableSeries<int>(Series<MyRow, int>(
id: 'Desktop',
colorFn: (row, _) => row.color,
dashPatternFn: (row, _) => row.dashPattern,
strokeWidthPxFn: (row, _) => row.strokeWidthPx,
domainFn: (row, _) => row.campaign,
measureFn: (row, _) => row.clickCount,
measureOffsetFn: (_, __) => 0,
data: myFakeData)),
];
renderer =
LineRenderer<num>(config: LineRendererConfig(strokeWidthPx: 2.0));
renderer.configureSeries(numericSeriesList);
renderer.preprocessSeries(numericSeriesList);
expect(numericSeriesList.length, equals(1));
// Validate Desktop series.
var series = numericSeriesList[0];
var styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(8));
var segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.domainExtent.start, equals(1));
expect(segment.domainExtent.end, equals(2));
segment = styleSegments[1];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.domainExtent.start, equals(2));
expect(segment.domainExtent.end, equals(3));
segment = styleSegments[2];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.domainExtent.start, equals(3));
expect(segment.domainExtent.end, equals(4));
segment = styleSegments[3];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.domainExtent.start, equals(4));
expect(segment.domainExtent.end, equals(5));
segment = styleSegments[4];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.domainExtent.start, equals(5));
expect(segment.domainExtent.end, equals(6));
segment = styleSegments[5];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.domainExtent.start, equals(6));
expect(segment.domainExtent.end, equals(7));
segment = styleSegments[6];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.domainExtent.start, equals(7));
expect(segment.domainExtent.end, equals(8));
segment = styleSegments[7];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.domainExtent.start, equals(8));
expect(segment.domainExtent.end, equals(8));
});
test('with ordinal data and simple lines', () {
renderer =
LineRenderer<String>(config: LineRendererConfig(strokeWidthPx: 2.0));
renderer.configureSeries(ordinalSeriesList);
renderer.preprocessSeries(ordinalSeriesList);
expect(ordinalSeriesList.length, equals(3));
// Validate Desktop series.
var series = ordinalSeriesList[0];
var styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
var segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.blue.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals('MyCampaign1'));
expect(segment.domainExtent.end, equals('MyOtherCampaign'));
expect(segment.strokeWidthPx, equals(2.0));
// Validate Tablet series.
series = ordinalSeriesList[1];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.red.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals('MyCampaign1'));
expect(segment.domainExtent.end, equals('MyOtherCampaign'));
expect(segment.strokeWidthPx, equals(1.25));
// Validate Mobile series.
series = ordinalSeriesList[2];
styleSegments = series.getAttr(styleSegmentsKey);
expect(styleSegments.length, equals(1));
segment = styleSegments[0];
expect(segment.color, equals(MaterialPalette.green.shadeDefault));
expect(segment.dashPattern, isNull);
expect(segment.domainExtent.start, equals('MyCampaign1'));
expect(segment.domainExtent.end, equals('MyOtherCampaign'));
expect(segment.strokeWidthPx, equals(3.0));
});
});
group('Line merging', () {
List<ImmutableSeries<num>> series(List<String> keys) {
return keys.map((key) => MockImmutableSeries<num>(key)).toList();
}
test('simple beginning removal', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['b', 'c']));
// The series should still be there so that it can be animated out.
expect(tester.seriesKeys, equals(['a', 'b', 'c']));
});
test('simple middle removal', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['a', 'c']));
// The series should still be there so that it can be animated out.
expect(tester.seriesKeys, equals(['a', 'b', 'c']));
});
test('simple end removal', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['a', 'b']));
// The series should still be there so that it can be animated out.
expect(tester.seriesKeys, equals(['a', 'b', 'c']));
});
test('simple beginning addition', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['d', 'a', 'b', 'c']));
expect(tester.seriesKeys, equals(['d', 'a', 'b', 'c']));
});
test('simple middle addition', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['a', 'd', 'b', 'c']));
expect(tester.seriesKeys, equals(['a', 'd', 'b', 'c']));
});
test('simple end addition', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['a', 'b', 'c', 'd']));
expect(tester.seriesKeys, equals(['a', 'b', 'c', 'd']));
});
test('replacement begining', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['d', 'b', 'c']));
expect(tester.seriesKeys, equals(['a', 'd', 'b', 'c']));
});
test('replacement end', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['a', 'b', 'd']));
expect(tester.seriesKeys, equals(['a', 'b', 'c', 'd']));
});
test('full replacement', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c']);
tester.merge(series(['d', 'e', 'f']));
expect(tester.seriesKeys, equals(['a', 'b', 'c', 'd', 'e', 'f']));
});
test('mixed replacement', () {
final tester = LineRendererTester(LineRenderer<num>());
tester.setSeriesKeys(['a', 'b', 'c', 'd']);
tester.merge(series(['d', 'a', 'f', 'c']));
expect(tester.seriesKeys, equals(['d', 'a', 'b', 'f', 'c']));
});
});
}