mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 15:28:44 +00:00
Add flutter_web samples (#75)
This commit is contained in:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
60
web/charts/common/test/chart/cartesian/axis/axis_test.dart
Normal file
60
web/charts/common/test/chart/cartesian/axis/axis_test.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/spec/tick_spec.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/static_tick_provider.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
import 'package:charts_common/src/common/text_element.dart';
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockTickDrawStrategy extends Mock implements TickDrawStrategy<num> {}
|
||||
|
||||
class MockGraphicsFactory extends Mock implements GraphicsFactory {
|
||||
TextElement createTextElement(String _) {
|
||||
return MockTextElement();
|
||||
}
|
||||
}
|
||||
|
||||
class MockTextElement extends Mock implements TextElement {}
|
||||
|
||||
StaticTickProvider<num> _createProvider(List<num> values) =>
|
||||
StaticTickProvider<num>(values.map((v) => TickSpec(v)).toList());
|
||||
|
||||
void main() {
|
||||
test('changing first tick only', () {
|
||||
var axis = NumericAxis(
|
||||
tickProvider: _createProvider([1, 10]),
|
||||
);
|
||||
|
||||
var tester = AxisTester(axis);
|
||||
axis.tickDrawStrategy = MockTickDrawStrategy();
|
||||
axis.graphicsFactory = MockGraphicsFactory();
|
||||
tester.scale.range = new ScaleOutputExtent(0, 300);
|
||||
|
||||
axis.updateTicks();
|
||||
|
||||
axis.tickProvider = _createProvider([5, 10]);
|
||||
axis.updateTicks();
|
||||
|
||||
// The old value should still be there as it gets animated out, but the
|
||||
// values should be sorted by their position.
|
||||
expect(tester.axisValues, equals([1, 5, 10]));
|
||||
});
|
||||
}
|
||||
186
web/charts/common/test/chart/cartesian/axis/axis_tick_test.dart
Normal file
186
web/charts/common/test/chart/cartesian/axis/axis_tick_test.dart
Normal file
@@ -0,0 +1,186 @@
|
||||
// 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/common/text_element.dart';
|
||||
import 'package:charts_common/src/common/text_measurement.dart';
|
||||
import 'package:charts_common/src/common/text_style.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis_tick.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// Fake [TextElement] for testing.
|
||||
class FakeTextElement implements TextElement {
|
||||
final String text;
|
||||
double opacity;
|
||||
|
||||
TextMeasurement measurement;
|
||||
TextStyle textStyle;
|
||||
int maxWidth;
|
||||
MaxWidthStrategy maxWidthStrategy;
|
||||
TextDirection textDirection;
|
||||
|
||||
FakeTextElement(this.text);
|
||||
}
|
||||
|
||||
/// Helper to create a tick for testing.
|
||||
Tick<String> _createTestTick(String value, double locationPx) {
|
||||
return new Tick(
|
||||
value: value,
|
||||
textElement: new FakeTextElement(value),
|
||||
locationPx: locationPx);
|
||||
}
|
||||
|
||||
void _verify(Tick<String> tick, {double location, double opacity}) {
|
||||
expect(tick.locationPx, equals(location));
|
||||
expect((tick.textElement as FakeTextElement).opacity, equals(opacity));
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Tick first render.
|
||||
test('tick created for the first time', () {
|
||||
final tick = new AxisTicks(_createTestTick('a', 100.0));
|
||||
|
||||
// Animate in the tick, there was no previous position to animated in from
|
||||
// so the tick appears in the target immediately.
|
||||
tick.setCurrentTick(0.0);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(0.75);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
});
|
||||
|
||||
// Tick that is animated in.
|
||||
test('tick created with a previous location', () {
|
||||
final tick = new AxisTicks(_createTestTick('a', 200.0))
|
||||
..animateInFrom(100.0);
|
||||
|
||||
tick.setCurrentTick(0.0);
|
||||
_verify(tick, location: 100.0, opacity: 0.0);
|
||||
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 125.0, opacity: 0.25);
|
||||
|
||||
tick.setCurrentTick(0.75);
|
||||
_verify(tick, location: 175.0, opacity: 0.75);
|
||||
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 200.0, opacity: 1.0);
|
||||
});
|
||||
|
||||
// Tick that is being animated out.
|
||||
test('tick is animated in and then out', () {
|
||||
final tick = new AxisTicks(_createTestTick('a', 100.0));
|
||||
|
||||
// Animate in the tick, there was no previous position to animated in from
|
||||
// so the tick appears in the target immediately.
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
// Change to animate the tick out.
|
||||
tick.animateOut(0.0);
|
||||
|
||||
expect(tick.markedForRemoval, isTrue);
|
||||
|
||||
tick.setCurrentTick(0.0);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 75.0, opacity: 0.75);
|
||||
|
||||
tick.setCurrentTick(0.75);
|
||||
_verify(tick, location: 25.0, opacity: 0.25);
|
||||
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 0.0, opacity: 0.0);
|
||||
});
|
||||
|
||||
test('tick target change after reaching target', () {
|
||||
final tick = new AxisTicks(_createTestTick('a', 100.0));
|
||||
|
||||
// Animate in the tick.
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
tick.setNewTarget(200.0);
|
||||
|
||||
expect(tick.markedForRemoval, isFalse);
|
||||
|
||||
tick.setCurrentTick(0.0);
|
||||
_verify(tick, location: 100.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 125.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(0.75);
|
||||
_verify(tick, location: 175.0, opacity: 1.0);
|
||||
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 200.0, opacity: 1.0);
|
||||
});
|
||||
|
||||
test('tick target change before reaching initial target', () {
|
||||
final tick = new AxisTicks(_createTestTick('a', 400.0))..animateInFrom(0.0);
|
||||
|
||||
// Animate in the tick.
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 100.0, opacity: 0.25);
|
||||
|
||||
tick.setNewTarget(200.0);
|
||||
|
||||
expect(tick.markedForRemoval, isFalse);
|
||||
|
||||
tick.setCurrentTick(0.0);
|
||||
_verify(tick, location: 100.0, opacity: 0.25);
|
||||
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 125.0, opacity: 0.4375);
|
||||
|
||||
tick.setCurrentTick(0.75);
|
||||
_verify(tick, location: 175.0, opacity: 0.8125);
|
||||
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 200.0, opacity: 1.0);
|
||||
});
|
||||
|
||||
test('tick target animate out before reaching initial target', () {
|
||||
final tick = new AxisTicks(_createTestTick('a', 400.0))..animateInFrom(0.0);
|
||||
|
||||
// Animate in the tick.
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 100.0, opacity: 0.25);
|
||||
|
||||
tick.animateOut(200.0);
|
||||
|
||||
expect(tick.markedForRemoval, isTrue);
|
||||
|
||||
tick.setCurrentTick(0.0);
|
||||
_verify(tick, location: 100.0, opacity: 0.25);
|
||||
|
||||
tick.setCurrentTick(0.25);
|
||||
_verify(tick, location: 125.0, opacity: 0.1875);
|
||||
|
||||
tick.setCurrentTick(0.75);
|
||||
_verify(tick, location: 175.0, opacity: 0.0625);
|
||||
|
||||
tick.setCurrentTick(1.0);
|
||||
_verify(tick, location: 200.0, opacity: 0.0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/collision_report.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_canvas.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/common/unitconverter/unit_converter.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
import 'package:charts_common/src/common/line_style.dart';
|
||||
import 'package:charts_common/src/common/text_style.dart';
|
||||
import 'package:charts_common/src/common/text_element.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockNumericScale extends Mock implements NumericScale {}
|
||||
|
||||
/// A fake draw strategy that reports collision and alternate ticks
|
||||
///
|
||||
/// Reports collision when the tick count is greater than or equal to
|
||||
/// [collidesAfterTickCount].
|
||||
///
|
||||
/// Reports alternate rendering after tick count is greater than or equal to
|
||||
/// [alternateRenderingAfterTickCount].
|
||||
class FakeDrawStrategy extends BaseTickDrawStrategy<num> {
|
||||
final int collidesAfterTickCount;
|
||||
final int alternateRenderingAfterTickCount;
|
||||
|
||||
FakeDrawStrategy(
|
||||
this.collidesAfterTickCount, this.alternateRenderingAfterTickCount)
|
||||
: super(null, new FakeGraphicsFactory());
|
||||
|
||||
@override
|
||||
CollisionReport collides(List<Tick<num>> ticks, _) {
|
||||
final ticksCollide = ticks.length >= collidesAfterTickCount;
|
||||
final alternateTicksUsed = ticks.length >= alternateRenderingAfterTickCount;
|
||||
|
||||
return new CollisionReport(
|
||||
ticksCollide: ticksCollide,
|
||||
ticks: ticks,
|
||||
alternateTicksUsed: alternateTicksUsed);
|
||||
}
|
||||
|
||||
@override
|
||||
void draw(ChartCanvas canvas, Tick<num> tick,
|
||||
{AxisOrientation orientation,
|
||||
Rectangle<int> axisBounds,
|
||||
Rectangle<int> drawAreaBounds,
|
||||
bool isFirst,
|
||||
bool isLast}) {}
|
||||
}
|
||||
|
||||
/// A fake [GraphicsFactory] that returns [MockTextStyle] and [MockTextElement].
|
||||
class FakeGraphicsFactory extends GraphicsFactory {
|
||||
@override
|
||||
TextStyle createTextPaint() => new MockTextStyle();
|
||||
|
||||
@override
|
||||
TextElement createTextElement(String text) => new MockTextElement(text);
|
||||
|
||||
@override
|
||||
LineStyle createLinePaint() => new MockLinePaint();
|
||||
}
|
||||
|
||||
class MockTextStyle extends Mock implements TextStyle {}
|
||||
|
||||
class MockTextElement extends Mock implements TextElement {
|
||||
String text;
|
||||
|
||||
MockTextElement(this.text);
|
||||
}
|
||||
|
||||
class MockLinePaint extends Mock implements LineStyle {}
|
||||
|
||||
class MockChartContext extends Mock implements ChartContext {}
|
||||
|
||||
/// A celsius to fahrenheit converter for testing axis with unit converter.
|
||||
class CelsiusToFahrenheitConverter implements UnitConverter<num, num> {
|
||||
const CelsiusToFahrenheitConverter();
|
||||
|
||||
@override
|
||||
num convert(num value) => (value * 1.8) + 32.0;
|
||||
|
||||
@override
|
||||
num invert(num value) => (value - 32.0) / 1.8;
|
||||
}
|
||||
|
||||
void main() {
|
||||
FakeGraphicsFactory graphicsFactory;
|
||||
MockNumericScale scale;
|
||||
BucketingNumericTickProvider tickProvider;
|
||||
TickFormatter<num> formatter;
|
||||
ChartContext context;
|
||||
|
||||
setUp(() {
|
||||
graphicsFactory = new FakeGraphicsFactory();
|
||||
scale = new MockNumericScale();
|
||||
tickProvider = new BucketingNumericTickProvider();
|
||||
formatter = new NumericTickFormatter();
|
||||
context = new MockChartContext();
|
||||
});
|
||||
|
||||
group('threshold', () {
|
||||
test('tick generated correctly with no ticks between it and zero', () {
|
||||
tickProvider
|
||||
..dataIsInWholeNumbers = false
|
||||
..threshold = 0.1
|
||||
..showBucket = true
|
||||
..setFixedTickCount(21)
|
||||
..allowedSteps = [1.0, 2.5, 5.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(0.1, 0.7));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
when(scale[0.1]).thenReturn(90.0);
|
||||
when(scale[0]).thenReturn(100.0);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
// Verify.
|
||||
// We expect to have 20 ticks, because the expected tick at 0.05 should be
|
||||
// removed from the list.
|
||||
expect(ticks, hasLength(20));
|
||||
|
||||
// Verify that we still have a 0 tick with an empty label.
|
||||
expect(ticks[0].labelOffsetPx, isNull);
|
||||
expect(ticks[0].locationPx, equals(100.0));
|
||||
expect(ticks[0].value, equals(0.0));
|
||||
expect(ticks[0].textElement.text, equals(''));
|
||||
|
||||
// Verify that we have a threshold tick.
|
||||
expect(ticks[1].labelOffsetPx, equals(5.0));
|
||||
expect(ticks[1].locationPx, equals(90.0));
|
||||
expect(ticks[1].value, equals(0.10));
|
||||
expect(ticks[1].textElement.text, equals('< 0.1'));
|
||||
|
||||
// Verify that the rest of the ticks are all above the threshold in value
|
||||
// and have normal labels.
|
||||
var aboveThresholdTicks = ticks.sublist(2);
|
||||
aboveThresholdTicks.retainWhere((Tick tick) => tick.value > 0.1);
|
||||
expect(aboveThresholdTicks, hasLength(18));
|
||||
|
||||
aboveThresholdTicks = ticks.sublist(2);
|
||||
aboveThresholdTicks.retainWhere((Tick tick) =>
|
||||
tick.textElement.text != '' && !tick.textElement.text.contains('<'));
|
||||
expect(aboveThresholdTicks, hasLength(18));
|
||||
|
||||
aboveThresholdTicks = ticks.sublist(2);
|
||||
aboveThresholdTicks
|
||||
.retainWhere((Tick tick) => tick.labelOffsetPx == null);
|
||||
expect(aboveThresholdTicks, hasLength(18));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
// 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 'dart:math';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/spec/axis_spec.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_canvas.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
import 'package:charts_common/src/common/line_style.dart';
|
||||
import 'package:charts_common/src/common/text_element.dart';
|
||||
import 'package:charts_common/src/common/text_measurement.dart';
|
||||
import 'package:charts_common/src/common/text_style.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockContext extends Mock implements ChartContext {}
|
||||
|
||||
/// Implementation of [BaseTickDrawStrategy] that does nothing on draw.
|
||||
class BaseTickDrawStrategyImpl<D> extends BaseTickDrawStrategy<D> {
|
||||
BaseTickDrawStrategyImpl(
|
||||
ChartContext chartContext, GraphicsFactory graphicsFactory,
|
||||
{TextStyleSpec labelStyleSpec,
|
||||
LineStyleSpec axisLineStyleSpec,
|
||||
TickLabelAnchor labelAnchor,
|
||||
TickLabelJustification labelJustification,
|
||||
int labelOffsetFromAxisPx,
|
||||
int labelOffsetFromTickPx,
|
||||
int minimumPaddingBetweenLabelsPx})
|
||||
: super(chartContext, graphicsFactory,
|
||||
labelStyleSpec: labelStyleSpec,
|
||||
axisLineStyleSpec: axisLineStyleSpec,
|
||||
labelAnchor: labelAnchor,
|
||||
labelJustification: labelJustification,
|
||||
labelOffsetFromAxisPx: labelOffsetFromAxisPx,
|
||||
labelOffsetFromTickPx: labelOffsetFromTickPx,
|
||||
minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx);
|
||||
|
||||
void draw(ChartCanvas canvas, Tick<D> tick,
|
||||
{AxisOrientation orientation,
|
||||
Rectangle<int> axisBounds,
|
||||
Rectangle<int> drawAreaBounds,
|
||||
bool isFirst,
|
||||
bool isLast}) {}
|
||||
}
|
||||
|
||||
/// Fake [TextElement] for testing.
|
||||
///
|
||||
/// [baseline] returns the same value as the [verticalSliceWidth] specified.
|
||||
class FakeTextElement implements TextElement {
|
||||
final String text;
|
||||
final TextMeasurement measurement;
|
||||
TextStyle textStyle;
|
||||
int maxWidth;
|
||||
MaxWidthStrategy maxWidthStrategy;
|
||||
TextDirection textDirection;
|
||||
double opacity;
|
||||
|
||||
FakeTextElement(
|
||||
this.text,
|
||||
this.textDirection,
|
||||
double horizontalSliceWidth,
|
||||
double verticalSliceWidth,
|
||||
) : measurement = new TextMeasurement(
|
||||
horizontalSliceWidth: horizontalSliceWidth,
|
||||
verticalSliceWidth: verticalSliceWidth);
|
||||
}
|
||||
|
||||
class MockGraphicsFactory extends Mock implements GraphicsFactory {}
|
||||
|
||||
class MockLineStyle extends Mock implements LineStyle {}
|
||||
|
||||
class MockTextStyle extends Mock implements TextStyle {}
|
||||
|
||||
/// Helper function to create [Tick] for testing.
|
||||
Tick<String> createTick(String value, double locationPx,
|
||||
{double horizontalWidth,
|
||||
double verticalWidth,
|
||||
TextDirection textDirection}) {
|
||||
return new Tick<String>(
|
||||
value: value,
|
||||
locationPx: locationPx,
|
||||
textElement: new FakeTextElement(
|
||||
value, textDirection, horizontalWidth, verticalWidth));
|
||||
}
|
||||
|
||||
void main() {
|
||||
GraphicsFactory graphicsFactory;
|
||||
ChartContext chartContext;
|
||||
|
||||
setUpAll(() {
|
||||
graphicsFactory = new MockGraphicsFactory();
|
||||
when(graphicsFactory.createLinePaint()).thenReturn(new MockLineStyle());
|
||||
when(graphicsFactory.createTextPaint()).thenReturn(new MockTextStyle());
|
||||
|
||||
chartContext = new MockContext();
|
||||
when(chartContext.chartContainerIsRtl).thenReturn(false);
|
||||
when(chartContext.isRtl).thenReturn(false);
|
||||
});
|
||||
|
||||
group('collision detection - vertically drawn axis', () {
|
||||
test('ticks do not collide', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2)
|
||||
createTick('B', 20.0, verticalWidth: 8.0), // 20.0 - 30.0 (28.0 + 2)
|
||||
createTick('C', 30.0, verticalWidth: 8.0), // 30.0 - 40.0 (38.0 + 2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('ticks collide because it does not have minimum padding', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2)
|
||||
createTick('B', 20.0, verticalWidth: 9.0), // 20.0 - 31.0 (28.0 + 3)
|
||||
createTick('C', 30.0, verticalWidth: 8.0), // 30.0 - 40.0 (38.0 + 2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('first tick causes collision', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 11.0), // 10.0 - 21.0
|
||||
createTick('B', 20.0, verticalWidth: 10.0), // 20.0 - 30.0
|
||||
createTick('C', 30.0, verticalWidth: 10.0), // 30.0 - 40.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('last tick causes collision', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 10.0), // 10.0 - 20.0
|
||||
createTick('B', 20.0, verticalWidth: 10.0), // 20.0 - 30.0
|
||||
createTick('C', 29.0, verticalWidth: 10.0), // 29.0 - 40.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('ticks do not collide for inside tick label anchor', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2)
|
||||
createTick('B', 25.0, verticalWidth: 8.0), // 20.0 - 30.0 (25 + 2 + 1)
|
||||
createTick('C', 40.0, verticalWidth: 8.0), // 30.0 - 40.0 (40-8-2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('ticks collide for inside anchor - first tick too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 9.0), // 10.0 - 21.0 (19.0 + 2)
|
||||
createTick('B', 25.0, verticalWidth: 8.0), // 20.0 - 30.0 (25 + 2 + 1)
|
||||
createTick('C', 40.0, verticalWidth: 8.0), // 30.0 - 40.0 (40-8-2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('ticks collide for inside anchor - center tick too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2)
|
||||
createTick('B', 25.0, verticalWidth: 9.0), // 19.5 - 30.5 (25 + 2.5 + 1)
|
||||
createTick('C', 40.0, verticalWidth: 8.0), // 30.0 - 40.0 (40-8-2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('ticks collide for inside anchor - last tick too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2)
|
||||
createTick('B', 25.0, verticalWidth: 8.0), // 20.0 - 30.0 (25 + 2 + 1)
|
||||
createTick('C', 40.0, verticalWidth: 9.0), // 29.0 - 40.0 (40-9-2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('collision detection - horizontally drawn axis', () {
|
||||
test('ticks do not collide for TickLabelAnchor.before', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 2,
|
||||
labelAnchor: TickLabelAnchor.before);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, horizontalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2)
|
||||
createTick('B', 20.0, horizontalWidth: 8.0), // 20.0 - 30.0 (28.0 + 2)
|
||||
createTick('C', 30.0, horizontalWidth: 8.0), // 30.0 - 40.0 (38.0 + 2)
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('ticks do not collide for TickLabelAnchor.inside', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0,
|
||||
horizontalWidth: 10.0,
|
||||
textDirection: TextDirection.ltr), // 10.0 - 20.0
|
||||
createTick('B', 25.0,
|
||||
horizontalWidth: 10.0,
|
||||
textDirection: TextDirection.center), // 20.0 - 30.0
|
||||
createTick('C', 40.0,
|
||||
horizontalWidth: 10.0,
|
||||
textDirection: TextDirection.rtl), // 30.0 - 40.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('ticks collide - first tick too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, horizontalWidth: 11.0), // 10.0 - 21.0
|
||||
createTick('B', 25.0, horizontalWidth: 10.0), // 20.0 - 30.0
|
||||
createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('ticks collide - middle tick too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0
|
||||
createTick('B', 25.0, horizontalWidth: 11.0), // 19.5 - 30.5
|
||||
createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
|
||||
test('ticks collide - last tick too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0
|
||||
createTick('B', 25.0, horizontalWidth: 10.0), // 20.0 - 30.0
|
||||
createTick('C', 40.0, horizontalWidth: 11.0), // 29.0 - 40.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('collision detection - unsorted ticks', () {
|
||||
test('ticks do not collide', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0
|
||||
createTick('B', 25.0, horizontalWidth: 10.0), // 20.0 - 30.0
|
||||
createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('ticks collide - tick B is too large', () {
|
||||
final drawStrategy = new BaseTickDrawStrategyImpl(
|
||||
chartContext, graphicsFactory,
|
||||
minimumPaddingBetweenLabelsPx: 0,
|
||||
labelAnchor: TickLabelAnchor.inside);
|
||||
|
||||
final ticks = [
|
||||
createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0
|
||||
createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0
|
||||
createTick('B', 25.0, horizontalWidth: 11.0), // 19.5 - 30.5
|
||||
];
|
||||
|
||||
final report = drawStrategy.collides(ticks, AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('collision detection - corner cases', () {
|
||||
test('null ticks do not collide', () {
|
||||
final drawStrategy =
|
||||
new BaseTickDrawStrategyImpl(chartContext, graphicsFactory);
|
||||
|
||||
final report = drawStrategy.collides(null, AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('empty tick list do not collide', () {
|
||||
final drawStrategy =
|
||||
new BaseTickDrawStrategyImpl(chartContext, graphicsFactory);
|
||||
|
||||
final report = drawStrategy.collides([], AxisOrientation.left);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
|
||||
test('single tick does not collide', () {
|
||||
final drawStrategy =
|
||||
new BaseTickDrawStrategyImpl(chartContext, graphicsFactory);
|
||||
|
||||
final report = drawStrategy.collides(
|
||||
[createTick('A', 10.0, horizontalWidth: 10.0)],
|
||||
AxisOrientation.bottom);
|
||||
|
||||
expect(report.ticksCollide, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// 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 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
import 'package:charts_common/src/common/line_style.dart';
|
||||
import 'package:charts_common/src/common/text_style.dart';
|
||||
import 'package:charts_common/src/common/text_element.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_canvas.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/collision_report.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/end_points_tick_provider.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/simple_ordinal_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/date_time_extents.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/date_time_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/date_time_tick_formatter.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'time/simple_date_time_factory.dart' show SimpleDateTimeFactory;
|
||||
|
||||
class MockDateTimeScale extends Mock implements DateTimeScale {}
|
||||
|
||||
class MockNumericScale extends Mock implements NumericScale {}
|
||||
|
||||
class MockOrdinalScale extends Mock implements SimpleOrdinalScale {}
|
||||
|
||||
/// A fake draw strategy that reports collision and alternate ticks
|
||||
///
|
||||
/// Reports collision when the tick count is greater than or equal to
|
||||
/// [collidesAfterTickCount].
|
||||
///
|
||||
/// Reports alternate rendering after tick count is greater than or equal to
|
||||
/// [alternateRenderingAfterTickCount].
|
||||
class FakeDrawStrategy<D> extends BaseTickDrawStrategy<D> {
|
||||
final int collidesAfterTickCount;
|
||||
final int alternateRenderingAfterTickCount;
|
||||
|
||||
FakeDrawStrategy(
|
||||
this.collidesAfterTickCount, this.alternateRenderingAfterTickCount)
|
||||
: super(null, new FakeGraphicsFactory());
|
||||
|
||||
@override
|
||||
CollisionReport collides(List<Tick<D>> ticks, _) {
|
||||
final ticksCollide = ticks.length >= collidesAfterTickCount;
|
||||
final alternateTicksUsed = ticks.length >= alternateRenderingAfterTickCount;
|
||||
|
||||
return new CollisionReport(
|
||||
ticksCollide: ticksCollide,
|
||||
ticks: ticks,
|
||||
alternateTicksUsed: alternateTicksUsed);
|
||||
}
|
||||
|
||||
@override
|
||||
void draw(ChartCanvas canvas, Tick<D> tick,
|
||||
{AxisOrientation orientation,
|
||||
Rectangle<int> axisBounds,
|
||||
Rectangle<int> drawAreaBounds,
|
||||
bool isFirst,
|
||||
bool isLast}) {}
|
||||
}
|
||||
|
||||
/// A fake [GraphicsFactory] that returns [MockTextStyle] and [MockTextElement].
|
||||
class FakeGraphicsFactory extends GraphicsFactory {
|
||||
@override
|
||||
TextStyle createTextPaint() => new MockTextStyle();
|
||||
|
||||
@override
|
||||
TextElement createTextElement(String text) => new MockTextElement();
|
||||
|
||||
@override
|
||||
LineStyle createLinePaint() => new MockLinePaint();
|
||||
}
|
||||
|
||||
class MockTextStyle extends Mock implements TextStyle {}
|
||||
|
||||
class MockTextElement extends Mock implements TextElement {}
|
||||
|
||||
class MockLinePaint extends Mock implements LineStyle {}
|
||||
|
||||
class MockChartContext extends Mock implements ChartContext {}
|
||||
|
||||
void main() {
|
||||
const dateTimeFactory = const SimpleDateTimeFactory();
|
||||
FakeGraphicsFactory graphicsFactory;
|
||||
EndPointsTickProvider tickProvider;
|
||||
ChartContext context;
|
||||
|
||||
setUp(() {
|
||||
graphicsFactory = new FakeGraphicsFactory();
|
||||
context = new MockChartContext();
|
||||
});
|
||||
|
||||
test('dateTime_choosesEndPointTicks', () {
|
||||
final formatter = new DateTimeTickFormatter(dateTimeFactory);
|
||||
final scale = new MockDateTimeScale();
|
||||
tickProvider = new EndPointsTickProvider<DateTime>();
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy<DateTime>(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new DateTimeExtents(
|
||||
start: new DateTime(2018, 8, 1), end: new DateTime(2018, 8, 11)));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
when(scale.domainStepSize).thenReturn(1000.0);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <DateTime, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(2));
|
||||
expect(ticks[0].value, equals(new DateTime(2018, 8, 1)));
|
||||
expect(ticks[1].value, equals(new DateTime(2018, 8, 11)));
|
||||
});
|
||||
|
||||
test('numeric_choosesEndPointTicks', () {
|
||||
final formatter = new NumericTickFormatter();
|
||||
final scale = new MockNumericScale();
|
||||
tickProvider = new EndPointsTickProvider<num>();
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy<num>(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(10.0, 70.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
when(scale.domainStepSize).thenReturn(1000.0);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(2));
|
||||
expect(ticks[0].value, equals(10));
|
||||
expect(ticks[1].value, equals(70));
|
||||
});
|
||||
|
||||
test('ordinal_choosesEndPointTicks', () {
|
||||
final formatter = new OrdinalTickFormatter();
|
||||
final scale = new SimpleOrdinalScale();
|
||||
scale.addDomain('A');
|
||||
scale.addDomain('B');
|
||||
scale.addDomain('C');
|
||||
scale.addDomain('D');
|
||||
tickProvider = new EndPointsTickProvider<String>();
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy<String>(10, 10);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <String, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(2));
|
||||
expect(ticks[0].value, equals('A'));
|
||||
expect(ticks[1].value, equals('D'));
|
||||
});
|
||||
|
||||
test('dateTime_emptySeriesChoosesNoTicks', () {
|
||||
final formatter = new DateTimeTickFormatter(dateTimeFactory);
|
||||
final scale = new MockDateTimeScale();
|
||||
tickProvider = new EndPointsTickProvider<DateTime>();
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy<DateTime>(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new DateTimeExtents(
|
||||
start: new DateTime(2018, 8, 1), end: new DateTime(2018, 8, 11)));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
// An un-configured axis has no domain step size, and its scale defaults to
|
||||
// infinity.
|
||||
when(scale.domainStepSize).thenReturn(double.infinity);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <DateTime, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(0));
|
||||
});
|
||||
|
||||
test('numeric_emptySeriesChoosesNoTicks', () {
|
||||
final formatter = new NumericTickFormatter();
|
||||
final scale = new MockNumericScale();
|
||||
tickProvider = new EndPointsTickProvider<num>();
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy<num>(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(10.0, 70.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
// An un-configured axis has no domain step size, and its scale defaults to
|
||||
// infinity.
|
||||
when(scale.domainStepSize).thenReturn(double.infinity);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(0));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// 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/cartesian/axis/numeric_extents.dart'
|
||||
show NumericExtents;
|
||||
import 'package:charts_common/src/chart/cartesian/axis/linear/linear_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/scale.dart'
|
||||
show RangeBandConfig, ScaleOutputExtent, StepSizeConfig;
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const EPSILON = 0.001;
|
||||
|
||||
void main() {
|
||||
group('Stacking bars', () {
|
||||
test('basic apply survives copy and reset', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.addDomain(100.0);
|
||||
scale.addDomain(130.0);
|
||||
scale.addDomain(200.0);
|
||||
scale.addDomain(170.0);
|
||||
scale.range = new ScaleOutputExtent(2000, 1000);
|
||||
|
||||
expect(scale.range.start, equals(2000));
|
||||
expect(scale.range.end, equals(1000));
|
||||
expect(scale.range.diff, equals(-1000));
|
||||
|
||||
expect(scale.dataExtent.min, equals(100.0));
|
||||
expect(scale.dataExtent.max, equals(200.0));
|
||||
|
||||
expect(scale[100.0], closeTo(2000, EPSILON));
|
||||
expect(scale[200.0], closeTo(1000, EPSILON));
|
||||
expect(scale[166.0], closeTo(1340, EPSILON));
|
||||
expect(scale[0.0], closeTo(3000, EPSILON));
|
||||
expect(scale[300.0], closeTo(0, EPSILON));
|
||||
|
||||
// test copy
|
||||
LinearScale other = scale.copy();
|
||||
expect(other[166.0], closeTo(1340, EPSILON));
|
||||
expect(other.range.start, equals(2000));
|
||||
expect(other.range.end, equals(1000));
|
||||
|
||||
// test reset
|
||||
other.resetDomain();
|
||||
other.resetViewportSettings();
|
||||
other.addDomain(10.0);
|
||||
other.addDomain(20.0);
|
||||
expect(other.dataExtent.min, equals(10.0));
|
||||
expect(other.dataExtent.max, equals(20.0));
|
||||
expect(other.viewportDomain.min, equals(10.0));
|
||||
expect(other.viewportDomain.max, equals(20.0));
|
||||
|
||||
expect(other[15.0], closeTo(1500, EPSILON));
|
||||
// original scale shouldn't have been touched.
|
||||
expect(scale[166.0], closeTo(1340, EPSILON));
|
||||
|
||||
// should always return true.
|
||||
expect(scale.canTranslate(3.14), isTrue);
|
||||
});
|
||||
|
||||
test('viewport assigned domain extent applies to scale', () {
|
||||
LinearScale scale = new LinearScale()..keepViewportWithinData = false;
|
||||
scale.addDomain(50.0);
|
||||
scale.addDomain(70.0);
|
||||
scale.viewportDomain = new NumericExtents(100.0, 200.0);
|
||||
scale.range = new ScaleOutputExtent(0, 200);
|
||||
|
||||
expect(scale[200.0], closeTo(200, EPSILON));
|
||||
expect(scale[100.0], closeTo(0, EPSILON));
|
||||
expect(scale[50.0], closeTo(-100, EPSILON));
|
||||
expect(scale[150.0], closeTo(100, EPSILON));
|
||||
|
||||
scale.resetDomain();
|
||||
scale.resetViewportSettings();
|
||||
scale.addDomain(50.0);
|
||||
scale.addDomain(100.0);
|
||||
scale.viewportDomain = new NumericExtents(0.0, 100.0);
|
||||
scale.range = new ScaleOutputExtent(0, 200);
|
||||
|
||||
expect(scale[0.0], closeTo(0, EPSILON));
|
||||
expect(scale[100.0], closeTo(200, EPSILON));
|
||||
expect(scale[50.0], closeTo(100, EPSILON));
|
||||
expect(scale[200.0], closeTo(400, EPSILON));
|
||||
});
|
||||
|
||||
test('comparing domain and range to viewport handles extent edges', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.range = new ScaleOutputExtent(1000, 1400);
|
||||
scale.domainOverride = new NumericExtents(100.0, 300.0);
|
||||
scale.viewportDomain = new NumericExtents(200.0, 300.0);
|
||||
|
||||
expect(scale.viewportDomain, equals(new NumericExtents(200.0, 300.0)));
|
||||
|
||||
expect(scale[210.0], closeTo(1040, EPSILON));
|
||||
expect(scale[400.0], closeTo(1800, EPSILON));
|
||||
expect(scale[100.0], closeTo(600, EPSILON));
|
||||
|
||||
expect(scale.compareDomainValueToViewport(199.0), equals(-1));
|
||||
expect(scale.compareDomainValueToViewport(200.0), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(201.0), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(299.0), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(300.0), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(301.0), equals(1));
|
||||
|
||||
expect(scale.isRangeValueWithinViewport(999.0), isFalse);
|
||||
expect(scale.isRangeValueWithinViewport(1100.0), isTrue);
|
||||
expect(scale.isRangeValueWithinViewport(1401.0), isFalse);
|
||||
});
|
||||
|
||||
test('scale applies in reverse', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.range = new ScaleOutputExtent(1000, 1400);
|
||||
scale.domainOverride = new NumericExtents(100.0, 300.0);
|
||||
scale.viewportDomain = new NumericExtents(200.0, 300.0);
|
||||
|
||||
expect(scale.reverse(1040.0), closeTo(210.0, EPSILON));
|
||||
expect(scale.reverse(1800.0), closeTo(400.0, EPSILON));
|
||||
expect(scale.reverse(600.0), closeTo(100.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale works with a range from larger to smaller', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.range = new ScaleOutputExtent(1400, 1000);
|
||||
scale.domainOverride = new NumericExtents(100.0, 300.0);
|
||||
scale.viewportDomain = new NumericExtents(200.0, 300.0);
|
||||
|
||||
expect(scale[200.0], closeTo(1400.0, EPSILON));
|
||||
expect(scale[250.0], closeTo(1200.0, EPSILON));
|
||||
expect(scale[300.0], closeTo(1000.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scaleFactor and translate applies to scale', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.range = new ScaleOutputExtent(1000, 1200);
|
||||
scale.domainOverride = new NumericExtents(100.0, 200.0);
|
||||
scale.setViewportSettings(4.0, -50.0);
|
||||
|
||||
expect(scale[100.0], closeTo(950.0, EPSILON));
|
||||
expect(scale[200.0], closeTo(1750.0, EPSILON));
|
||||
expect(scale[150.0], closeTo(1350.0, EPSILON));
|
||||
expect(scale[106.25], closeTo(1000.0, EPSILON));
|
||||
expect(scale[131.25], closeTo(1200.0, EPSILON));
|
||||
|
||||
expect(scale.compareDomainValueToViewport(106.0), equals(-1));
|
||||
expect(scale.compareDomainValueToViewport(106.25), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(107.0), equals(0));
|
||||
|
||||
expect(scale.compareDomainValueToViewport(131.0), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(131.25), equals(0));
|
||||
expect(scale.compareDomainValueToViewport(132.0), equals(1));
|
||||
|
||||
expect(scale.isRangeValueWithinViewport(999.0), isFalse);
|
||||
expect(scale.isRangeValueWithinViewport(1100.0), isTrue);
|
||||
expect(scale.isRangeValueWithinViewport(1201.0), isFalse);
|
||||
});
|
||||
|
||||
test('scale handles single point', () {
|
||||
LinearScale domainScale = new LinearScale();
|
||||
domainScale.range = new ScaleOutputExtent(1000, 1200);
|
||||
domainScale.addDomain(50.0);
|
||||
|
||||
// A single point should render in the middle of the scale.
|
||||
expect(domainScale[50.0], closeTo(1100.0, EPSILON));
|
||||
});
|
||||
|
||||
test('testAllZeros', () {
|
||||
LinearScale measureScale = new LinearScale();
|
||||
measureScale.range = new ScaleOutputExtent(1000, 1200);
|
||||
measureScale.addDomain(0.0);
|
||||
|
||||
expect(measureScale[0.0], closeTo(1100.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale calculates step size', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(3.0);
|
||||
scale.addDomain(11.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
// 1 - 11 has 6 steps of size 2, 0 - 12
|
||||
expect(scale.rangeBand, closeTo(100.0 / 6.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale applies rangeBand to detected step size', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(0.5);
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(2.0);
|
||||
scale.addDomain(10.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
// 100 range / 10 steps * 0.5percentStep = 5
|
||||
expect(scale.rangeBand, closeTo(5.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale stepSize calculation survives copy', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.stepSizeConfig = new StepSizeConfig.fixedDomain(1.0);
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(3.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
expect(scale.copy().rangeBand, closeTo(100.0 / 3.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale rangeBand calculation survives copy', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.rangeBandConfig = new RangeBandConfig.fixedPixel(123.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(3.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
expect(scale.copy().rangeBand, closeTo(123, EPSILON));
|
||||
});
|
||||
|
||||
test('scale rangeBand works for single domain value', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
expect(scale.rangeBand, closeTo(100, EPSILON));
|
||||
});
|
||||
|
||||
test('scale rangeBand works for multiple domains of the same value', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
expect(scale.rangeBand, closeTo(100.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale rangeBand is zero when no domains are added', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
expect(scale.rangeBand, closeTo(0.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale domain info reset on resetDomain', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(3.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
scale.setViewportSettings(1000.0, 2000.0);
|
||||
|
||||
scale.resetDomain();
|
||||
scale.resetViewportSettings();
|
||||
expect(scale.viewportScalingFactor, closeTo(1.0, EPSILON));
|
||||
expect(scale.viewportTranslatePx, closeTo(0, EPSILON));
|
||||
expect(scale.range, equals(new ScaleOutputExtent(100, 200)));
|
||||
});
|
||||
|
||||
test('scale handles null domain values', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(null);
|
||||
scale.addDomain(3.0);
|
||||
scale.addDomain(11.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
|
||||
expect(scale.rangeBand, closeTo(100.0 / 6.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale domainOverride survives copy', () {
|
||||
LinearScale scale = new LinearScale()..keepViewportWithinData = false;
|
||||
scale.addDomain(1.0);
|
||||
scale.addDomain(3.0);
|
||||
scale.range = new ScaleOutputExtent(100, 200);
|
||||
scale.setViewportSettings(2.0, 10.0);
|
||||
scale.domainOverride = new NumericExtents(0.0, 100.0);
|
||||
|
||||
LinearScale other = scale.copy();
|
||||
|
||||
expect(other.domainOverride, equals(new NumericExtents(0.0, 100.0)));
|
||||
expect(other[5.0], closeTo(120.0, EPSILON));
|
||||
});
|
||||
|
||||
test('scale calculates a scaleFactor given a domain window', () {
|
||||
LinearScale scale = new LinearScale();
|
||||
scale.addDomain(100.0);
|
||||
scale.addDomain(130.0);
|
||||
scale.addDomain(200.0);
|
||||
scale.addDomain(170.0);
|
||||
|
||||
expect(scale.computeViewportScaleFactor(10.0), closeTo(10, EPSILON));
|
||||
expect(scale.computeViewportScaleFactor(100.0), closeTo(1, EPSILON));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
// 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 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
import 'package:charts_common/src/common/line_style.dart';
|
||||
import 'package:charts_common/src/common/text_style.dart';
|
||||
import 'package:charts_common/src/common/text_element.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_canvas.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/common/unitconverter/unit_converter.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/collision_report.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick_provider.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_tick_provider.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockNumericScale extends Mock implements NumericScale {}
|
||||
|
||||
/// A fake draw strategy that reports collision and alternate ticks
|
||||
///
|
||||
/// Reports collision when the tick count is greater than or equal to
|
||||
/// [collidesAfterTickCount].
|
||||
///
|
||||
/// Reports alternate rendering after tick count is greater than or equal to
|
||||
/// [alternateRenderingAfterTickCount].
|
||||
class FakeDrawStrategy extends BaseTickDrawStrategy<num> {
|
||||
final int collidesAfterTickCount;
|
||||
final int alternateRenderingAfterTickCount;
|
||||
|
||||
FakeDrawStrategy(
|
||||
this.collidesAfterTickCount, this.alternateRenderingAfterTickCount)
|
||||
: super(null, new FakeGraphicsFactory());
|
||||
|
||||
@override
|
||||
CollisionReport collides(List<Tick<num>> ticks, _) {
|
||||
final ticksCollide = ticks.length >= collidesAfterTickCount;
|
||||
final alternateTicksUsed = ticks.length >= alternateRenderingAfterTickCount;
|
||||
|
||||
return new CollisionReport(
|
||||
ticksCollide: ticksCollide,
|
||||
ticks: ticks,
|
||||
alternateTicksUsed: alternateTicksUsed);
|
||||
}
|
||||
|
||||
@override
|
||||
void draw(ChartCanvas canvas, Tick<num> tick,
|
||||
{AxisOrientation orientation,
|
||||
Rectangle<int> axisBounds,
|
||||
Rectangle<int> drawAreaBounds,
|
||||
bool isFirst,
|
||||
bool isLast}) {}
|
||||
}
|
||||
|
||||
/// A fake [GraphicsFactory] that returns [MockTextStyle] and [MockTextElement].
|
||||
class FakeGraphicsFactory extends GraphicsFactory {
|
||||
@override
|
||||
TextStyle createTextPaint() => new MockTextStyle();
|
||||
|
||||
@override
|
||||
TextElement createTextElement(String text) => new MockTextElement();
|
||||
|
||||
@override
|
||||
LineStyle createLinePaint() => new MockLinePaint();
|
||||
}
|
||||
|
||||
class MockTextStyle extends Mock implements TextStyle {}
|
||||
|
||||
class MockTextElement extends Mock implements TextElement {}
|
||||
|
||||
class MockLinePaint extends Mock implements LineStyle {}
|
||||
|
||||
class MockChartContext extends Mock implements ChartContext {}
|
||||
|
||||
/// A celsius to fahrenheit converter for testing axis with unit converter.
|
||||
class CelsiusToFahrenheitConverter implements UnitConverter<num, num> {
|
||||
const CelsiusToFahrenheitConverter();
|
||||
|
||||
@override
|
||||
num convert(num value) => (value * 1.8) + 32.0;
|
||||
|
||||
@override
|
||||
num invert(num value) => (value - 32.0) / 1.8;
|
||||
}
|
||||
|
||||
void main() {
|
||||
FakeGraphicsFactory graphicsFactory;
|
||||
MockNumericScale scale;
|
||||
NumericTickProvider tickProvider;
|
||||
TickFormatter<num> formatter;
|
||||
ChartContext context;
|
||||
|
||||
setUp(() {
|
||||
graphicsFactory = new FakeGraphicsFactory();
|
||||
scale = new MockNumericScale();
|
||||
tickProvider = new NumericTickProvider();
|
||||
formatter = new NumericTickFormatter();
|
||||
context = new MockChartContext();
|
||||
});
|
||||
|
||||
test('singleTickCount_choosesTicksWithSmallestStepCoveringDomain', () {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = false
|
||||
..setFixedTickCount(4)
|
||||
..allowedSteps = [1.0, 2.5, 5.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(10.0, 70.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(4));
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(25));
|
||||
expect(ticks[2].value, equals(50));
|
||||
expect(ticks[3].value, equals(75));
|
||||
});
|
||||
|
||||
test(
|
||||
'tickCountRangeChoosesTicksWithMostTicksAndSmallestIntervalCoveringDomain',
|
||||
() {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = false
|
||||
..setTickCount(5, 3)
|
||||
..allowedSteps = [1.0, 2.5, 5.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(10.0, 80.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(5));
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(25));
|
||||
expect(ticks[2].value, equals(50));
|
||||
expect(ticks[3].value, equals(75));
|
||||
expect(ticks[4].value, equals(100));
|
||||
});
|
||||
|
||||
test('choosesNonAlternateRenderingTicksEvenIfIntervalIsLarger', () {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = false
|
||||
..setTickCount(5, 3)
|
||||
..allowedSteps = [1.0, 2.5, 6.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 5);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(10.0, 80.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(3));
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(60));
|
||||
expect(ticks[2].value, equals(120));
|
||||
});
|
||||
|
||||
test('choosesNonCollidingTicksEvenIfIntervalIsLarger', () {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = false
|
||||
..setTickCount(5, 3)
|
||||
..allowedSteps = [1.0, 2.5, 6.0];
|
||||
final drawStrategy = new FakeDrawStrategy(5, 5);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(10.0, 80.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks, hasLength(3));
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(60));
|
||||
expect(ticks[2].value, equals(120));
|
||||
});
|
||||
|
||||
test('zeroBound_alwaysReturnsZeroTick', () {
|
||||
tickProvider
|
||||
..zeroBound = true
|
||||
..dataIsInWholeNumbers = false
|
||||
..setFixedTickCount(3)
|
||||
..allowedSteps = [1.0, 2.5, 5.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(55.0, 135.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
final tickValues = ticks.map((tick) => tick.value).toList();
|
||||
|
||||
expect(tickValues, contains(0.0));
|
||||
});
|
||||
|
||||
test('boundsCrossOrigin_alwaysReturnsZeroTick', () {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = false
|
||||
..setFixedTickCount(3)
|
||||
..allowedSteps = [1.0, 2.5, 5.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(-55.0, 135.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
final tickValues = ticks.map((tick) => tick.value).toList();
|
||||
|
||||
expect(tickValues, contains(0.0));
|
||||
});
|
||||
|
||||
test('boundsCrossOrigin_returnsValidTickRange', () {
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(-55.0, 135.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
final tickValues = ticks.map((tick) => tick.value).toList();
|
||||
|
||||
// We expect to see a range of ticks that crosses zero.
|
||||
expect(tickValues,
|
||||
equals([-60.0, -30.0, 0.0, 30.0, 60.0, 90.0, 120.0, 150.0]));
|
||||
});
|
||||
|
||||
test('dataIsWholeNumbers_returnsWholeNumberTicks', () {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = true
|
||||
..setFixedTickCount(3)
|
||||
..allowedSteps = [1.0, 2.5, 5.0];
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(0.25, 0.75));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(1));
|
||||
expect(ticks[2].value, equals(2));
|
||||
});
|
||||
|
||||
test('choosesTicksBasedOnPreferredAxisUnits', () {
|
||||
tickProvider
|
||||
..zeroBound = true
|
||||
..dataIsInWholeNumbers = false
|
||||
..setFixedTickCount(3)
|
||||
..allowedSteps = [5.0]
|
||||
..dataToAxisUnitConverter = const CelsiusToFahrenheitConverter();
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(0.0, 20.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks[0].value, closeTo(-17.8, 0.1)); // 0 in axis units
|
||||
expect(ticks[1].value, closeTo(10, 0.1)); // 50 in axis units
|
||||
expect(ticks[2].value, closeTo(37.8, 0.1)); // 100 in axis units
|
||||
});
|
||||
|
||||
test('handlesVerySmallMeasures', () {
|
||||
tickProvider
|
||||
..zeroBound = true
|
||||
..dataIsInWholeNumbers = false
|
||||
..setFixedTickCount(5);
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
|
||||
when(scale.viewportDomain)
|
||||
.thenReturn(new NumericExtents(0.000001, 0.000002));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks.length, equals(5));
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(0.0000005));
|
||||
expect(ticks[2].value, equals(0.0000010));
|
||||
expect(ticks[3].value, equals(0.0000015));
|
||||
expect(ticks[4].value, equals(0.000002));
|
||||
});
|
||||
|
||||
test('handlesVerySmallMeasuresForWholeNumbers', () {
|
||||
tickProvider
|
||||
..zeroBound = true
|
||||
..dataIsInWholeNumbers = true
|
||||
..setFixedTickCount(5);
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
|
||||
when(scale.viewportDomain)
|
||||
.thenReturn(new NumericExtents(0.000001, 0.000002));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks.length, equals(5));
|
||||
expect(ticks[0].value, equals(0));
|
||||
expect(ticks[1].value, equals(1));
|
||||
expect(ticks[2].value, equals(2));
|
||||
expect(ticks[3].value, equals(3));
|
||||
expect(ticks[4].value, equals(4));
|
||||
});
|
||||
|
||||
test('handlesVerySmallMeasuresForWholeNumbersWithoutZero', () {
|
||||
tickProvider
|
||||
..zeroBound = false
|
||||
..dataIsInWholeNumbers = true
|
||||
..setFixedTickCount(5);
|
||||
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
|
||||
when(scale.viewportDomain)
|
||||
.thenReturn(new NumericExtents(101.000001, 101.000002));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(ticks.length, equals(5));
|
||||
expect(ticks[0].value, equals(101));
|
||||
expect(ticks[1].value, equals(102));
|
||||
expect(ticks[2].value, equals(103));
|
||||
expect(ticks[3].value, equals(104));
|
||||
expect(ticks[4].value, equals(105));
|
||||
});
|
||||
|
||||
test('handles tick hint for non zero ticks', () {
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(20.0, 35.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
// Step Size: 3,
|
||||
// Previous start tick: 10
|
||||
// Previous window: 10 - 25
|
||||
// Previous ticks: 10, 13, 16, 19, 22, 25
|
||||
final tickHint = new TickHint(10, 25, tickCount: 6);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null,
|
||||
tickHint: tickHint,
|
||||
);
|
||||
|
||||
// New adjusted ticks for window 20 - 35
|
||||
// Should have ticks 22, 25, 28, 31, 34, 37
|
||||
expect(ticks, hasLength(6));
|
||||
expect(ticks[0].value, equals(22));
|
||||
expect(ticks[1].value, equals(25));
|
||||
expect(ticks[2].value, equals(28));
|
||||
expect(ticks[3].value, equals(31));
|
||||
expect(ticks[4].value, equals(34));
|
||||
expect(ticks[5].value, equals(37));
|
||||
});
|
||||
|
||||
test('handles tick hint for negative starting ticks', () {
|
||||
final drawStrategy = new FakeDrawStrategy(10, 10);
|
||||
when(scale.viewportDomain).thenReturn(new NumericExtents(-35.0, -20.0));
|
||||
when(scale.rangeWidth).thenReturn(1000);
|
||||
|
||||
// Step Size: 3,
|
||||
// Previous start tick: -25
|
||||
// Previous window: -25 to -10
|
||||
// Previous ticks: -25, -22, -19, -16, -13, -10
|
||||
final tickHint = new TickHint(-25, -10, tickCount: 6);
|
||||
|
||||
final ticks = tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null,
|
||||
tickHint: tickHint,
|
||||
);
|
||||
|
||||
// New adjusted ticks for window -35 to -20
|
||||
// Should have ticks -34, -31, -28, -25, -22, -19
|
||||
expect(ticks, hasLength(6));
|
||||
expect(ticks[0].value, equals(-34));
|
||||
expect(ticks[1].value, equals(-31));
|
||||
expect(ticks[2].value, equals(-28));
|
||||
expect(ticks[3].value, equals(-25));
|
||||
expect(ticks[4].value, equals(-22));
|
||||
expect(ticks[5].value, equals(-19));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// 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/cartesian/axis/scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/simple_ordinal_scale.dart';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const EPSILON = 0.001;
|
||||
|
||||
void main() {
|
||||
SimpleOrdinalScale scale;
|
||||
|
||||
setUp(() {
|
||||
scale = new SimpleOrdinalScale();
|
||||
scale.addDomain('a');
|
||||
scale.addDomain('b');
|
||||
scale.addDomain('c');
|
||||
scale.addDomain('d');
|
||||
|
||||
scale.range = new ScaleOutputExtent(2000, 1000);
|
||||
});
|
||||
|
||||
group('conversion', () {
|
||||
test('with duplicate keys', () {
|
||||
scale.addDomain('c');
|
||||
scale.addDomain('a');
|
||||
|
||||
// Current RangeBandConfig.styleAssignedPercent sets size to 0.65 percent.
|
||||
expect(scale.rangeBand, closeTo(250 * 0.65, EPSILON));
|
||||
expect(scale['a'], closeTo(2000 - 125, EPSILON));
|
||||
expect(scale['b'], closeTo(2000 - 375, EPSILON));
|
||||
expect(scale['c'], closeTo(2000 - 625, EPSILON));
|
||||
});
|
||||
|
||||
test('invalid domain does not throw exception', () {
|
||||
expect(scale['e'], 0);
|
||||
});
|
||||
|
||||
test('invalid domain can translate is false', () {
|
||||
expect(scale.canTranslate('e'), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('copy', () {
|
||||
test('can convert domain', () {
|
||||
final copied = scale.copy();
|
||||
expect(copied['c'], closeTo(2000 - 625, EPSILON));
|
||||
});
|
||||
|
||||
test('does not affect original', () {
|
||||
final copied = scale.copy();
|
||||
copied.addDomain('bar');
|
||||
|
||||
expect(copied.canTranslate('bar'), isTrue);
|
||||
expect(scale.canTranslate('bar'), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('reset', () {
|
||||
test('clears domains', () {
|
||||
scale.resetDomain();
|
||||
scale.addDomain('foo');
|
||||
scale.addDomain('bar');
|
||||
|
||||
expect(scale['foo'], closeTo(2000 - 250, EPSILON));
|
||||
});
|
||||
});
|
||||
|
||||
group('set RangeBandConfig', () {
|
||||
test('fixed pixel range band changes range band', () {
|
||||
scale.rangeBandConfig = new RangeBandConfig.fixedPixel(123.0);
|
||||
|
||||
expect(scale.rangeBand, closeTo(123.0, EPSILON));
|
||||
|
||||
// Adding another domain to ensure it still doesn't change.
|
||||
scale.addDomain('foo');
|
||||
expect(scale.rangeBand, closeTo(123.0, EPSILON));
|
||||
});
|
||||
|
||||
test('percent range band changes range band', () {
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(0.5);
|
||||
// 125 = 0.5f * 1000pixels / 4domains
|
||||
expect(scale.rangeBand, closeTo(125.0, EPSILON));
|
||||
});
|
||||
|
||||
test('space from step changes range band', () {
|
||||
scale.rangeBandConfig =
|
||||
new RangeBandConfig.fixedPixelSpaceBetweenStep(50.0);
|
||||
// 200 = 1000pixels / 4domains) - 50
|
||||
expect(scale.rangeBand, closeTo(200.0, EPSILON));
|
||||
});
|
||||
|
||||
test('fixed domain throws argument exception', () {
|
||||
expect(() => scale.rangeBandConfig = new RangeBandConfig.fixedDomain(5.0),
|
||||
throwsArgumentError);
|
||||
});
|
||||
|
||||
test('type of none throws argument exception', () {
|
||||
expect(() => scale.rangeBandConfig = new RangeBandConfig.none(),
|
||||
throwsArgumentError);
|
||||
});
|
||||
|
||||
test('set to null throws argument exception', () {
|
||||
expect(() => scale.rangeBandConfig = null, throwsArgumentError);
|
||||
});
|
||||
});
|
||||
|
||||
group('set step size config', () {
|
||||
test('to null does not throw', () {
|
||||
scale.stepSizeConfig = null;
|
||||
});
|
||||
|
||||
test('to auto does not throw', () {
|
||||
scale.stepSizeConfig = new StepSizeConfig.auto();
|
||||
});
|
||||
|
||||
test('to fixed domain throw arugment exception', () {
|
||||
expect(() => scale.stepSizeConfig = new StepSizeConfig.fixedDomain(1.0),
|
||||
throwsArgumentError);
|
||||
});
|
||||
|
||||
test('to fixed pixel throw arugment exception', () {
|
||||
expect(() => scale.stepSizeConfig = new StepSizeConfig.fixedPixels(1.0),
|
||||
throwsArgumentError);
|
||||
});
|
||||
});
|
||||
|
||||
group('set range persists', () {
|
||||
test('', () {
|
||||
expect(scale.range.start, equals(2000));
|
||||
expect(scale.range.end, equals(1000));
|
||||
expect(scale.range.min, equals(1000));
|
||||
expect(scale.range.max, equals(2000));
|
||||
expect(scale.rangeWidth, equals(1000));
|
||||
|
||||
expect(scale.isRangeValueWithinViewport(1500.0), isTrue);
|
||||
expect(scale.isRangeValueWithinViewport(1000.0), isTrue);
|
||||
expect(scale.isRangeValueWithinViewport(2000.0), isTrue);
|
||||
|
||||
expect(scale.isRangeValueWithinViewport(500.0), isFalse);
|
||||
expect(scale.isRangeValueWithinViewport(2500.0), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('scale factor', () {
|
||||
test('sets', () {
|
||||
scale.setViewportSettings(2.0, -700.0);
|
||||
|
||||
expect(scale.viewportScalingFactor, closeTo(2.0, EPSILON));
|
||||
expect(scale.viewportTranslatePx, closeTo(-700.0, EPSILON));
|
||||
});
|
||||
|
||||
test('rangeband is scaled', () {
|
||||
scale.setViewportSettings(2.0, -700.0);
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
|
||||
expect(scale.rangeBand, closeTo(500.0, EPSILON));
|
||||
});
|
||||
|
||||
test('translate to pixels is scaled', () {
|
||||
scale.setViewportSettings(2.0, -700.0);
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.range = new ScaleOutputExtent(1000, 2000);
|
||||
|
||||
final scaledStepWidth = 500.0;
|
||||
final scaledInitialShift = 250.0;
|
||||
|
||||
expect(scale['a'], closeTo(1000 + scaledInitialShift - 700, EPSILON));
|
||||
|
||||
expect(scale['b'],
|
||||
closeTo(1000 + scaledInitialShift - 700 + scaledStepWidth, EPSILON));
|
||||
});
|
||||
|
||||
test('only b and c should be within the viewport', () {
|
||||
scale.setViewportSettings(2.0, -700.0);
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(1.0);
|
||||
scale.range = new ScaleOutputExtent(1000, 2000);
|
||||
|
||||
expect(scale.compareDomainValueToViewport('a'), equals(-1));
|
||||
expect(scale.compareDomainValueToViewport('c'), equals(0));
|
||||
expect(scale.compareDomainValueToViewport('d'), equals(1));
|
||||
expect(scale.compareDomainValueToViewport('f'), isNot(0));
|
||||
});
|
||||
});
|
||||
|
||||
group('viewport', () {
|
||||
test('set adjust scale to show viewport', () {
|
||||
scale.range = new ScaleOutputExtent(1000, 2000);
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(0.5);
|
||||
scale.setViewport(2, 'b');
|
||||
|
||||
expect(scale['a'], closeTo(750, EPSILON));
|
||||
expect(scale['b'], closeTo(1250, EPSILON));
|
||||
expect(scale['c'], closeTo(1750, EPSILON));
|
||||
expect(scale['d'], closeTo(2250, EPSILON));
|
||||
expect(scale.compareDomainValueToViewport('a'), equals(-1));
|
||||
expect(scale.compareDomainValueToViewport('b'), equals(0));
|
||||
expect(scale.compareDomainValueToViewport('c'), equals(0));
|
||||
expect(scale.compareDomainValueToViewport('d'), equals(1));
|
||||
});
|
||||
|
||||
test('illegal to set window size less than one', () {
|
||||
expect(() => scale.setViewport(0, 'b'), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('set starting value if starting domain is not in domain list', () {
|
||||
scale.range = new ScaleOutputExtent(1000, 2000);
|
||||
scale.rangeBandConfig = new RangeBandConfig.percentOfStep(0.5);
|
||||
scale.setViewport(2, 'f');
|
||||
|
||||
expect(scale['a'], closeTo(1250, EPSILON));
|
||||
expect(scale['b'], closeTo(1750, EPSILON));
|
||||
expect(scale['c'], closeTo(2250, EPSILON));
|
||||
expect(scale['d'], closeTo(2750, EPSILON));
|
||||
});
|
||||
|
||||
test('get size returns number of full steps that fit scale range', () {
|
||||
scale.range = new ScaleOutputExtent(1000, 2000);
|
||||
|
||||
scale.setViewportSettings(2.0, 0.0);
|
||||
expect(scale.viewportDataSize, equals(2));
|
||||
|
||||
scale.setViewportSettings(5.0, 0.0);
|
||||
expect(scale.viewportDataSize, equals(0));
|
||||
});
|
||||
|
||||
test('get starting viewport gets first fully visible domain', () {
|
||||
scale.range = new ScaleOutputExtent(1000, 2000);
|
||||
|
||||
scale.setViewportSettings(2.0, -500.0);
|
||||
expect(scale.viewportStartingDomain, equals('b'));
|
||||
|
||||
scale.setViewportSettings(2.0, -100.0);
|
||||
expect(scale.viewportStartingDomain, equals('b'));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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/cartesian/axis/static_tick_provider.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/linear/linear_scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/scale.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/spec/tick_spec.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockChartContext extends Mock implements ChartContext {}
|
||||
|
||||
class MockGraphicsFactory extends Mock implements GraphicsFactory {}
|
||||
|
||||
class MockNumericTickFormatter extends Mock implements TickFormatter<num> {}
|
||||
|
||||
class FakeNumericTickFormatter implements TickFormatter<num> {
|
||||
int calledTimes = 0;
|
||||
|
||||
@override
|
||||
List<String> format(List<num> tickValues, Map<num, String> cache,
|
||||
{num stepSize}) {
|
||||
calledTimes += 1;
|
||||
|
||||
return tickValues.map((value) => value.toString()).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class MockDrawStrategy extends Mock implements BaseTickDrawStrategy {}
|
||||
|
||||
void main() {
|
||||
ChartContext context;
|
||||
GraphicsFactory graphicsFactory;
|
||||
TickFormatter formatter;
|
||||
BaseTickDrawStrategy drawStrategy;
|
||||
LinearScale scale;
|
||||
|
||||
setUp(() {
|
||||
context = new MockChartContext();
|
||||
graphicsFactory = new MockGraphicsFactory();
|
||||
formatter = new MockNumericTickFormatter();
|
||||
drawStrategy = new MockDrawStrategy();
|
||||
scale = new LinearScale()..range = new ScaleOutputExtent(0, 300);
|
||||
});
|
||||
|
||||
group('scale is extended with static tick values', () {
|
||||
test('values extend existing domain values', () {
|
||||
final tickProvider = new StaticTickProvider<num>([
|
||||
new TickSpec<num>(50, label: '50'),
|
||||
new TickSpec<num>(75, label: '75'),
|
||||
new TickSpec<num>(100, label: '100'),
|
||||
]);
|
||||
|
||||
scale.addDomain(60);
|
||||
scale.addDomain(80);
|
||||
|
||||
expect(scale.dataExtent.min, equals(60));
|
||||
expect(scale.dataExtent.max, equals(80));
|
||||
|
||||
tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(scale.dataExtent.min, equals(50));
|
||||
expect(scale.dataExtent.max, equals(100));
|
||||
});
|
||||
|
||||
test('values within data extent', () {
|
||||
final tickProvider = new StaticTickProvider<num>([
|
||||
new TickSpec<num>(50, label: '50'),
|
||||
new TickSpec<num>(75, label: '75'),
|
||||
new TickSpec<num>(100, label: '100'),
|
||||
]);
|
||||
|
||||
scale.addDomain(0);
|
||||
scale.addDomain(150);
|
||||
|
||||
expect(scale.dataExtent.min, equals(0));
|
||||
expect(scale.dataExtent.max, equals(150));
|
||||
|
||||
tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: formatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(scale.dataExtent.min, equals(0));
|
||||
expect(scale.dataExtent.max, equals(150));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatter', () {
|
||||
test('is not called when all ticks have labels', () {
|
||||
final tickProvider = new StaticTickProvider<num>([
|
||||
new TickSpec<num>(50, label: '50'),
|
||||
new TickSpec<num>(75, label: '75'),
|
||||
new TickSpec<num>(100, label: '100'),
|
||||
]);
|
||||
|
||||
final fakeFormatter = new FakeNumericTickFormatter();
|
||||
|
||||
tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: fakeFormatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(fakeFormatter.calledTimes, equals(0));
|
||||
});
|
||||
|
||||
test('is called when one ticks does not have label', () {
|
||||
final tickProvider = new StaticTickProvider<num>([
|
||||
new TickSpec<num>(50, label: '50'),
|
||||
new TickSpec<num>(75),
|
||||
new TickSpec<num>(100, label: '100'),
|
||||
]);
|
||||
|
||||
final fakeFormatter = new FakeNumericTickFormatter();
|
||||
|
||||
tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: fakeFormatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(fakeFormatter.calledTimes, equals(1));
|
||||
});
|
||||
|
||||
test('is called when all ticks do not have labels', () {
|
||||
final tickProvider = new StaticTickProvider<num>([
|
||||
new TickSpec<num>(50),
|
||||
new TickSpec<num>(75),
|
||||
new TickSpec<num>(100),
|
||||
]);
|
||||
|
||||
final fakeFormatter = new FakeNumericTickFormatter();
|
||||
|
||||
tickProvider.getTicks(
|
||||
context: context,
|
||||
graphicsFactory: graphicsFactory,
|
||||
scale: scale,
|
||||
formatter: fakeFormatter,
|
||||
formatterValueCache: <num, String>{},
|
||||
tickDrawStrategy: drawStrategy,
|
||||
orientation: null);
|
||||
|
||||
expect(fakeFormatter.calledTimes, equals(1));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// 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/cartesian/axis/time/time_tick_formatter.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/date_time_tick_formatter.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const EPSILON = 0.001;
|
||||
|
||||
typedef bool IsTransitionFunction(DateTime tickValue, DateTime prevTickValue);
|
||||
|
||||
class FakeTimeTickFormatter implements TimeTickFormatter {
|
||||
static const firstTick = '-firstTick-';
|
||||
static const simpleTick = '-simpleTick-';
|
||||
static const transitionTick = '-transitionTick-';
|
||||
static final transitionAlwaysFalse = (_, __) => false;
|
||||
|
||||
final String id;
|
||||
final IsTransitionFunction isTransitionFunction;
|
||||
|
||||
FakeTimeTickFormatter(this.id, {IsTransitionFunction isTransitionFunction})
|
||||
: isTransitionFunction = isTransitionFunction ?? transitionAlwaysFalse;
|
||||
|
||||
@override
|
||||
String formatFirstTick(DateTime date) =>
|
||||
id + firstTick + date.millisecondsSinceEpoch.toString();
|
||||
|
||||
@override
|
||||
String formatSimpleTick(DateTime date) =>
|
||||
id + simpleTick + date.millisecondsSinceEpoch.toString();
|
||||
|
||||
@override
|
||||
String formatTransitionTick(DateTime date) =>
|
||||
id + transitionTick + date.millisecondsSinceEpoch.toString();
|
||||
|
||||
@override
|
||||
bool isTransition(DateTime tickValue, DateTime prevTickValue) =>
|
||||
isTransitionFunction(tickValue, prevTickValue);
|
||||
}
|
||||
|
||||
void main() {
|
||||
TimeTickFormatter timeFormatter1;
|
||||
TimeTickFormatter timeFormatter2;
|
||||
TimeTickFormatter timeFormatter3;
|
||||
|
||||
setUp(() {
|
||||
timeFormatter1 = new FakeTimeTickFormatter('fake1');
|
||||
timeFormatter2 = new FakeTimeTickFormatter('fake2');
|
||||
timeFormatter3 = new FakeTimeTickFormatter('fake3');
|
||||
});
|
||||
|
||||
group('Uses formatter', () {
|
||||
test('with largest interval less than diff between tickValues', () {
|
||||
final formatter = new DateTimeTickFormatter.withFormatters(
|
||||
{10: timeFormatter1, 100: timeFormatter2, 1000: timeFormatter3});
|
||||
final formatterCache = <DateTime, String>{};
|
||||
|
||||
final ticksWith10Diff = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(10),
|
||||
new DateTime.fromMillisecondsSinceEpoch(20)
|
||||
];
|
||||
final ticksWith20Diff = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(20),
|
||||
new DateTime.fromMillisecondsSinceEpoch(40)
|
||||
];
|
||||
final ticksWith100Diff = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(100),
|
||||
new DateTime.fromMillisecondsSinceEpoch(200)
|
||||
];
|
||||
final ticksWith200Diff = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(200),
|
||||
new DateTime.fromMillisecondsSinceEpoch(400)
|
||||
];
|
||||
final ticksWith1000Diff = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(1000),
|
||||
new DateTime.fromMillisecondsSinceEpoch(2000)
|
||||
];
|
||||
|
||||
final expectedLabels10Diff = [
|
||||
'fake1-firstTick-0',
|
||||
'fake1-simpleTick-10',
|
||||
'fake1-simpleTick-20'
|
||||
];
|
||||
final expectedLabels20Diff = [
|
||||
'fake1-firstTick-0',
|
||||
'fake1-simpleTick-20',
|
||||
'fake1-simpleTick-40'
|
||||
];
|
||||
final expectedLabels100Diff = [
|
||||
'fake2-firstTick-0',
|
||||
'fake2-simpleTick-100',
|
||||
'fake2-simpleTick-200'
|
||||
];
|
||||
final expectedLabels200Diff = [
|
||||
'fake2-firstTick-0',
|
||||
'fake2-simpleTick-200',
|
||||
'fake2-simpleTick-400'
|
||||
];
|
||||
final expectedLabels1000Diff = [
|
||||
'fake3-firstTick-0',
|
||||
'fake3-simpleTick-1000',
|
||||
'fake3-simpleTick-2000'
|
||||
];
|
||||
|
||||
final actualLabelsWith10Diff =
|
||||
formatter.format(ticksWith10Diff, formatterCache, stepSize: 10);
|
||||
final actualLabelsWith20Diff =
|
||||
formatter.format(ticksWith20Diff, formatterCache, stepSize: 20);
|
||||
final actualLabelsWith100Diff =
|
||||
formatter.format(ticksWith100Diff, formatterCache, stepSize: 100);
|
||||
final actualLabelsWith200Diff =
|
||||
formatter.format(ticksWith200Diff, formatterCache, stepSize: 200);
|
||||
final actualLabelsWith1000Diff =
|
||||
formatter.format(ticksWith1000Diff, formatterCache, stepSize: 1000);
|
||||
|
||||
expect(actualLabelsWith10Diff, equals(expectedLabels10Diff));
|
||||
expect(actualLabelsWith20Diff, equals(expectedLabels20Diff));
|
||||
|
||||
expect(actualLabelsWith100Diff, equals(expectedLabels100Diff));
|
||||
expect(actualLabelsWith200Diff, equals(expectedLabels200Diff));
|
||||
expect(actualLabelsWith1000Diff, equals(expectedLabels1000Diff));
|
||||
});
|
||||
|
||||
test('with smallest interval when no smaller one exists', () {
|
||||
final formatter = new DateTimeTickFormatter.withFormatters(
|
||||
{10: timeFormatter1, 100: timeFormatter2});
|
||||
final formatterCache = <DateTime, String>{};
|
||||
|
||||
final ticks = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(1),
|
||||
new DateTime.fromMillisecondsSinceEpoch(2)
|
||||
];
|
||||
final expectedLabels = [
|
||||
'fake1-firstTick-0',
|
||||
'fake1-simpleTick-1',
|
||||
'fake1-simpleTick-2'
|
||||
];
|
||||
final actualLabels = formatter.format(ticks, formatterCache, stepSize: 1);
|
||||
|
||||
expect(actualLabels, equals(expectedLabels));
|
||||
});
|
||||
|
||||
test('with smallest interval for single tick input', () {
|
||||
final formatter = new DateTimeTickFormatter.withFormatters(
|
||||
{10: timeFormatter1, 100: timeFormatter2});
|
||||
final formatterCache = <DateTime, String>{};
|
||||
|
||||
final ticks = [new DateTime.fromMillisecondsSinceEpoch(5000)];
|
||||
final expectedLabels = ['fake1-firstTick-5000'];
|
||||
final actualLabels = formatter.format(ticks, formatterCache, stepSize: 0);
|
||||
expect(actualLabels, equals(expectedLabels));
|
||||
});
|
||||
|
||||
test('on empty input doesnt break', () {
|
||||
final formatter =
|
||||
new DateTimeTickFormatter.withFormatters({10: timeFormatter1});
|
||||
final formatterCache = <DateTime, String>{};
|
||||
|
||||
final actualLabels =
|
||||
formatter.format(<DateTime>[], formatterCache, stepSize: 10);
|
||||
expect(actualLabels, isEmpty);
|
||||
});
|
||||
|
||||
test('that formats transition tick with transition format', () {
|
||||
final timeFormatter = new FakeTimeTickFormatter('fake',
|
||||
isTransitionFunction: (DateTime tickValue, _) =>
|
||||
tickValue.millisecondsSinceEpoch == 20);
|
||||
final formatterCache = <DateTime, String>{};
|
||||
|
||||
final formatter =
|
||||
new DateTimeTickFormatter.withFormatters({10: timeFormatter});
|
||||
|
||||
final ticks = [
|
||||
new DateTime.fromMillisecondsSinceEpoch(0),
|
||||
new DateTime.fromMillisecondsSinceEpoch(10),
|
||||
new DateTime.fromMillisecondsSinceEpoch(20),
|
||||
new DateTime.fromMillisecondsSinceEpoch(30)
|
||||
];
|
||||
|
||||
final expectedLabels = [
|
||||
'fake-firstTick-0',
|
||||
'fake-simpleTick-10',
|
||||
'fake-transitionTick-20',
|
||||
'fake-simpleTick-30'
|
||||
];
|
||||
final actualLabels =
|
||||
formatter.format(ticks, formatterCache, stepSize: 10);
|
||||
|
||||
expect(actualLabels, equals(expectedLabels));
|
||||
});
|
||||
});
|
||||
|
||||
group('check custom time tick formatters', () {
|
||||
test('throws arugment error if time resolution key is not positive', () {
|
||||
// -1 is reserved for any, if there is only one formatter, -1 is allowed.
|
||||
expect(
|
||||
() => new DateTimeTickFormatter.withFormatters(
|
||||
{-1: timeFormatter1, 2: timeFormatter2}),
|
||||
throwsArgumentError);
|
||||
});
|
||||
|
||||
test('throws argument error if formatters is null or empty', () {
|
||||
expect(() => new DateTimeTickFormatter.withFormatters(null),
|
||||
throwsArgumentError);
|
||||
expect(() => new DateTimeTickFormatter.withFormatters({}),
|
||||
throwsArgumentError);
|
||||
});
|
||||
|
||||
test('throws arugment error if formatters are not sorted', () {
|
||||
expect(
|
||||
() => new DateTimeTickFormatter.withFormatters({
|
||||
3: timeFormatter1,
|
||||
1: timeFormatter2,
|
||||
2: timeFormatter3,
|
||||
}),
|
||||
throwsArgumentError);
|
||||
|
||||
expect(
|
||||
() => new DateTimeTickFormatter.withFormatters({
|
||||
1: timeFormatter1,
|
||||
3: timeFormatter2,
|
||||
2: timeFormatter3,
|
||||
}),
|
||||
throwsArgumentError);
|
||||
|
||||
expect(
|
||||
() => new DateTimeTickFormatter.withFormatters({
|
||||
2: timeFormatter1,
|
||||
3: timeFormatter2,
|
||||
1: timeFormatter3,
|
||||
}),
|
||||
throwsArgumentError);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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/common/date_time_factory.dart';
|
||||
import 'package:intl/intl.dart' show DateFormat;
|
||||
|
||||
/// Returns DateTime for testing.
|
||||
class SimpleDateTimeFactory implements DateTimeFactory {
|
||||
const SimpleDateTimeFactory();
|
||||
|
||||
@override
|
||||
DateTime createDateTimeFromMilliSecondsSinceEpoch(
|
||||
int millisecondsSinceEpoch) =>
|
||||
new DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch);
|
||||
|
||||
@override
|
||||
DateTime createDateTime(int year,
|
||||
[int month = 1,
|
||||
int day = 1,
|
||||
int hour = 0,
|
||||
int minute = 0,
|
||||
int second = 0,
|
||||
int millisecond = 0,
|
||||
int microsecond = 0]) =>
|
||||
new DateTime(
|
||||
year, month, day, hour, minute, second, millisecond, microsecond);
|
||||
|
||||
@override
|
||||
DateFormat createDateFormat(String pattern) => new DateFormat(pattern);
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
// 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/cartesian/axis/time/date_time_extents.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/day_time_stepper.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/hour_time_stepper.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/minute_time_stepper.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/month_time_stepper.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/time/year_time_stepper.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'simple_date_time_factory.dart' show SimpleDateTimeFactory;
|
||||
|
||||
const EPSILON = 0.001;
|
||||
|
||||
void main() {
|
||||
const dateTimeFactory = const SimpleDateTimeFactory();
|
||||
const millisecondsInHour = 3600 * 1000;
|
||||
|
||||
setUp(() {});
|
||||
|
||||
group('Day time stepper', () {
|
||||
test('get steps with 1 day increments', () {
|
||||
final stepper = new DayTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 8, 20), end: new DateTime(2017, 8, 25));
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(1);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 8, 20),
|
||||
new DateTime(2017, 8, 21),
|
||||
new DateTime(2017, 8, 22),
|
||||
new DateTime(2017, 8, 23),
|
||||
new DateTime(2017, 8, 24),
|
||||
new DateTime(2017, 8, 25),
|
||||
]));
|
||||
});
|
||||
|
||||
test('get steps with 5 day increments', () {
|
||||
final stepper = new DayTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 8, 10),
|
||||
end: new DateTime(2017, 8, 26),
|
||||
);
|
||||
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(5);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(4));
|
||||
// Note, this is because 5 day increments in a month is 1,6,11,16,21,26,31
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 8, 11),
|
||||
new DateTime(2017, 8, 16),
|
||||
new DateTime(2017, 8, 21),
|
||||
new DateTime(2017, 8, 26),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving forward change', () {
|
||||
final stepper = new DayTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 begin on March 12
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 3, 11),
|
||||
end: new DateTime(2017, 3, 13),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(1);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(3));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 3, 11),
|
||||
new DateTime(2017, 3, 12),
|
||||
new DateTime(2017, 3, 13),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving backward change', () {
|
||||
final stepper = new DayTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 end on November 5
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 11, 4),
|
||||
end: new DateTime(2017, 11, 6),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(1);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(3));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 11, 4),
|
||||
new DateTime(2017, 11, 5),
|
||||
new DateTime(2017, 11, 6),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group('Hour time stepper', () {
|
||||
test('gets steps in 1 hour increments', () {
|
||||
final stepper = new HourTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 8, 20, 10),
|
||||
end: new DateTime(2017, 8, 20, 15),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(1);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 8, 20, 10),
|
||||
new DateTime(2017, 8, 20, 11),
|
||||
new DateTime(2017, 8, 20, 12),
|
||||
new DateTime(2017, 8, 20, 13),
|
||||
new DateTime(2017, 8, 20, 14),
|
||||
new DateTime(2017, 8, 20, 15),
|
||||
]));
|
||||
});
|
||||
|
||||
test('gets steps in 4 hour increments', () {
|
||||
final stepper = new HourTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 8, 20, 10),
|
||||
end: new DateTime(2017, 8, 21, 10),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(4);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 8, 20, 12),
|
||||
new DateTime(2017, 8, 20, 16),
|
||||
new DateTime(2017, 8, 20, 20),
|
||||
new DateTime(2017, 8, 21, 0),
|
||||
new DateTime(2017, 8, 21, 4),
|
||||
new DateTime(2017, 8, 21, 8),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving forward change in 1 hour increments',
|
||||
() {
|
||||
final stepper = new HourTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 3, 12, 0),
|
||||
end: new DateTime(2017, 3, 12, 5),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(1);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(5));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 3, 12, 0),
|
||||
new DateTime(2017, 3, 12, 1),
|
||||
new DateTime(2017, 3, 12, 3),
|
||||
new DateTime(2017, 3, 12, 4),
|
||||
new DateTime(2017, 3, 12, 5),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving backward change in 1 hour increments',
|
||||
() {
|
||||
final stepper = new HourTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 end on November 5. At 2am, clocks are turned to 1am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 11, 5, 0),
|
||||
end: new DateTime(2017, 11, 5, 4),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(1);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 11, 5, 0),
|
||||
new DateTime(2017, 11, 5, 0)
|
||||
.add(new Duration(milliseconds: millisecondsInHour)),
|
||||
new DateTime(2017, 11, 5, 0)
|
||||
.add(new Duration(milliseconds: millisecondsInHour * 2)),
|
||||
new DateTime(2017, 11, 5, 2),
|
||||
new DateTime(2017, 11, 5, 3),
|
||||
new DateTime(2017, 11, 5, 4),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving forward change in 4 hour increments',
|
||||
() {
|
||||
final stepper = new HourTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 3, 12, 0),
|
||||
end: new DateTime(2017, 3, 13, 0),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(4);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 3, 12, 4),
|
||||
new DateTime(2017, 3, 12, 8),
|
||||
new DateTime(2017, 3, 12, 12),
|
||||
new DateTime(2017, 3, 12, 16),
|
||||
new DateTime(2017, 3, 12, 20),
|
||||
new DateTime(2017, 3, 13, 0),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving backward change in 4 hour increments',
|
||||
() {
|
||||
final stepper = new HourTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 end on November 5.
|
||||
// At 2am, clocks are turned to 1am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 11, 5, 0),
|
||||
end: new DateTime(2017, 11, 6, 0),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(4);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(7));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 11, 5, 0)
|
||||
.add(new Duration(milliseconds: millisecondsInHour)),
|
||||
new DateTime(2017, 11, 5, 4),
|
||||
new DateTime(2017, 11, 5, 8),
|
||||
new DateTime(2017, 11, 5, 12),
|
||||
new DateTime(2017, 11, 5, 16),
|
||||
new DateTime(2017, 11, 5, 20),
|
||||
new DateTime(2017, 11, 6, 0),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group('Minute time stepper', () {
|
||||
test('gets steps with 5 minute increments', () {
|
||||
final stepper = new MinuteTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 8, 20, 3, 46),
|
||||
end: new DateTime(2017, 8, 20, 4, 02),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(5);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(3));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 8, 20, 3, 50),
|
||||
new DateTime(2017, 8, 20, 3, 55),
|
||||
new DateTime(2017, 8, 20, 4),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving forward change', () {
|
||||
final stepper = new MinuteTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 3, 12, 1, 40),
|
||||
end: new DateTime(2017, 3, 12, 4, 02),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(15);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 3, 12, 1, 45),
|
||||
new DateTime(2017, 3, 12, 3),
|
||||
new DateTime(2017, 3, 12, 3, 15),
|
||||
new DateTime(2017, 3, 12, 3, 30),
|
||||
new DateTime(2017, 3, 12, 3, 45),
|
||||
new DateTime(2017, 3, 12, 4),
|
||||
]));
|
||||
});
|
||||
|
||||
test('steps correctly after daylight saving forward change', () {
|
||||
final stepper = new MinuteTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 3, 12, 3, 02),
|
||||
end: new DateTime(2017, 3, 12, 4, 02),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(30);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(2));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 3, 12, 3, 30),
|
||||
new DateTime(2017, 3, 12, 4),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step through daylight saving backward change', () {
|
||||
final stepper = new MinuteTimeStepper(dateTimeFactory);
|
||||
// DST for PST 2017 end on November 5.
|
||||
// At 2am, clocks are turned to 1am.
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 11, 5)
|
||||
.add(new Duration(hours: 1, minutes: 29)),
|
||||
end: new DateTime(2017, 11, 5, 3, 02));
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(30);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(6));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
// The first 1:30am
|
||||
new DateTime(2017, 11, 5).add(new Duration(hours: 1, minutes: 30)),
|
||||
// The 2nd 1am.
|
||||
new DateTime(2017, 11, 5).add(new Duration(hours: 2)),
|
||||
// The 2nd 1:30am
|
||||
new DateTime(2017, 11, 5).add(new Duration(hours: 2, minutes: 30)),
|
||||
// 2am
|
||||
new DateTime(2017, 11, 5).add(new Duration(hours: 3)),
|
||||
// 2:30am
|
||||
new DateTime(2017, 11, 5).add(new Duration(hours: 3, minutes: 30)),
|
||||
// 3am
|
||||
new DateTime(2017, 11, 5, 3)
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group('Month time stepper', () {
|
||||
test('steps crosses the year', () {
|
||||
final stepper = new MonthTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 5),
|
||||
end: new DateTime(2018, 9),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(4);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(4));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 8),
|
||||
new DateTime(2017, 12),
|
||||
new DateTime(2018, 4),
|
||||
new DateTime(2018, 8),
|
||||
]));
|
||||
});
|
||||
|
||||
test('steps within one year', () {
|
||||
final stepper = new MonthTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017, 1),
|
||||
end: new DateTime(2017, 5),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(2);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(2));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2017, 2),
|
||||
new DateTime(2017, 4),
|
||||
]));
|
||||
});
|
||||
|
||||
test('step before would allow ticks to include last month of the year', () {
|
||||
final stepper = new MonthTimeStepper(dateTimeFactory);
|
||||
final time = new DateTime(2017, 10);
|
||||
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 1),
|
||||
equals(new DateTime(2017, 10)));
|
||||
|
||||
// Months - 3, 6, 9, 12
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 3),
|
||||
equals(new DateTime(2017, 9)));
|
||||
|
||||
// Months - 6, 12
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 6),
|
||||
equals(new DateTime(2017, 6)));
|
||||
});
|
||||
|
||||
test('step before for January', () {
|
||||
final stepper = new MonthTimeStepper(dateTimeFactory);
|
||||
final time = new DateTime(2017, 1);
|
||||
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 1),
|
||||
equals(new DateTime(2017, 1)));
|
||||
|
||||
// Months - 3, 6, 9, 12
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 3),
|
||||
equals(new DateTime(2016, 12)));
|
||||
|
||||
// Months - 6, 12
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 6),
|
||||
equals(new DateTime(2016, 12)));
|
||||
});
|
||||
|
||||
test('step before for December', () {
|
||||
final stepper = new MonthTimeStepper(dateTimeFactory);
|
||||
final time = new DateTime(2017, 12);
|
||||
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 1),
|
||||
equals(new DateTime(2017, 12)));
|
||||
|
||||
// Months - 3, 6, 9, 12
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 3),
|
||||
equals(new DateTime(2017, 12)));
|
||||
|
||||
// Months - 6, 12
|
||||
expect(stepper.getStepTimeBeforeInclusive(time, 6),
|
||||
equals(new DateTime(2017, 12)));
|
||||
});
|
||||
});
|
||||
|
||||
group('Year stepper', () {
|
||||
test('steps in 10 year increments', () {
|
||||
final stepper = new YearTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(2017),
|
||||
end: new DateTime(2042),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(10);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(3));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(2020),
|
||||
new DateTime(2030),
|
||||
new DateTime(2040),
|
||||
]));
|
||||
});
|
||||
|
||||
test('steps through negative year', () {
|
||||
final stepper = new YearTimeStepper(dateTimeFactory);
|
||||
final extent = new DateTimeExtents(
|
||||
start: new DateTime(-420),
|
||||
end: new DateTime(240),
|
||||
);
|
||||
final stepIterable = stepper.getSteps(extent)..iterator.reset(200);
|
||||
final steps = stepIterable.toList();
|
||||
|
||||
expect(steps.length, equals(4));
|
||||
expect(
|
||||
steps,
|
||||
equals([
|
||||
new DateTime(-400),
|
||||
new DateTime(-200),
|
||||
new DateTime(0),
|
||||
new DateTime(200),
|
||||
]));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'simple_date_time_factory.dart' show SimpleDateTimeFactory;
|
||||
|
||||
const EPSILON = 0.001;
|
||||
|
||||
void main() {
|
||||
const dateTimeFactory = const SimpleDateTimeFactory();
|
||||
|
||||
group('Find closest step size from stepper', () {
|
||||
test('from exactly matching step size', () {
|
||||
final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider(
|
||||
dateTimeFactory);
|
||||
final oneHourMs = (new Duration(hours: 1)).inMilliseconds;
|
||||
final closestStepSize = stepper.getClosestStepSize(oneHourMs);
|
||||
|
||||
expect(closestStepSize, equals(oneHourMs));
|
||||
});
|
||||
|
||||
test('choose smallest increment if step is smaller than smallest increment',
|
||||
() {
|
||||
final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider(
|
||||
dateTimeFactory);
|
||||
final oneHourMs = (new Duration(hours: 1)).inMilliseconds;
|
||||
final closestStepSize = stepper
|
||||
.getClosestStepSize((new Duration(minutes: 56)).inMilliseconds);
|
||||
|
||||
expect(closestStepSize, equals(oneHourMs));
|
||||
});
|
||||
|
||||
test('choose largest increment if step is larger than largest increment',
|
||||
() {
|
||||
final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider(
|
||||
dateTimeFactory);
|
||||
final oneDayMs = (new Duration(hours: 24)).inMilliseconds;
|
||||
final closestStepSize =
|
||||
stepper.getClosestStepSize((new Duration(hours: 25)).inMilliseconds);
|
||||
|
||||
expect(closestStepSize, equals(oneDayMs));
|
||||
});
|
||||
|
||||
test('choose closest increment if exact not found', () {
|
||||
final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider(
|
||||
dateTimeFactory);
|
||||
final threeHoursMs = (new Duration(hours: 3)).inMilliseconds;
|
||||
final closestStepSize = stepper.getClosestStepSize(
|
||||
(new Duration(hours: 3, minutes: 28)).inMilliseconds);
|
||||
|
||||
expect(closestStepSize, equals(threeHoursMs));
|
||||
});
|
||||
});
|
||||
}
|
||||
105
web/charts/common/test/chart/cartesian/cartesian_chart_test.dart
Normal file
105
web/charts/common/test/chart/cartesian/cartesian_chart_test.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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/cartesian/cartesian_chart.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/spec/date_time_axis_spec.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/spec/numeric_axis_spec.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/time_series/time_series_chart.dart';
|
||||
import 'package:charts_common/src/common/graphics_factory.dart';
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockContext extends Mock implements ChartContext {}
|
||||
|
||||
class MockGraphicsFactory extends Mock implements GraphicsFactory {}
|
||||
|
||||
class FakeNumericChart extends NumericCartesianChart {
|
||||
FakeNumericChart() {
|
||||
context = new MockContext();
|
||||
graphicsFactory = new MockGraphicsFactory();
|
||||
}
|
||||
|
||||
@override
|
||||
void initDomainAxis() {
|
||||
// Purposely bypass the renderer code.
|
||||
}
|
||||
}
|
||||
|
||||
class FakeOrdinalChart extends OrdinalCartesianChart {
|
||||
FakeOrdinalChart() {
|
||||
context = new MockContext();
|
||||
graphicsFactory = new MockGraphicsFactory();
|
||||
}
|
||||
|
||||
@override
|
||||
void initDomainAxis() {
|
||||
// Purposely bypass the renderer code.
|
||||
}
|
||||
}
|
||||
|
||||
class FakeTimeSeries extends TimeSeriesChart {
|
||||
FakeTimeSeries() {
|
||||
context = new MockContext();
|
||||
graphicsFactory = new MockGraphicsFactory();
|
||||
}
|
||||
|
||||
@override
|
||||
void initDomainAxis() {
|
||||
// Purposely bypass the renderer code.
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Axis reset with new axis spec', () {
|
||||
test('for ordinal chart', () {
|
||||
final chart = new FakeOrdinalChart();
|
||||
chart.configurationChanged();
|
||||
final domainAxis = chart.domainAxis;
|
||||
expect(domainAxis, isNotNull);
|
||||
|
||||
chart.domainAxisSpec = new OrdinalAxisSpec();
|
||||
chart.configurationChanged();
|
||||
|
||||
expect(domainAxis, isNot(chart.domainAxis));
|
||||
});
|
||||
|
||||
test('for numeric chart', () {
|
||||
final chart = new FakeNumericChart();
|
||||
chart.configurationChanged();
|
||||
final domainAxis = chart.domainAxis;
|
||||
expect(domainAxis, isNotNull);
|
||||
|
||||
chart.domainAxisSpec = new NumericAxisSpec();
|
||||
chart.configurationChanged();
|
||||
|
||||
expect(domainAxis, isNot(chart.domainAxis));
|
||||
});
|
||||
|
||||
test('for time series chart', () {
|
||||
final chart = new FakeTimeSeries();
|
||||
chart.configurationChanged();
|
||||
final domainAxis = chart.domainAxis;
|
||||
expect(domainAxis, isNotNull);
|
||||
|
||||
chart.domainAxisSpec = new DateTimeAxisSpec();
|
||||
chart.configurationChanged();
|
||||
|
||||
expect(domainAxis, isNot(chart.domainAxis));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
// 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 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/cartesian_renderer.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_canvas.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/series_datum.dart';
|
||||
import 'package:charts_common/src/common/symbol_renderer.dart';
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// For testing viewport start / end.
|
||||
class FakeCartesianRenderer extends BaseCartesianRenderer {
|
||||
@override
|
||||
List<DatumDetails> getNearestDatumDetailPerSeries(Point<double> chartPoint,
|
||||
bool byDomain, Rectangle<int> boundsOverride) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
void paint(ChartCanvas canvas, double animationPercent) {}
|
||||
|
||||
@override
|
||||
void update(List<ImmutableSeries> seriesList, bool isAnimating) {}
|
||||
|
||||
@override
|
||||
SymbolRenderer get symbolRenderer => null;
|
||||
|
||||
DatumDetails addPositionToDetailsForSeriesDatum(
|
||||
DatumDetails details, SeriesDatum seriesDatum) {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
class MockAxis extends Mock implements Axis {}
|
||||
|
||||
void main() {
|
||||
BaseCartesianRenderer renderer;
|
||||
|
||||
setUp(() {
|
||||
renderer = new FakeCartesianRenderer();
|
||||
});
|
||||
|
||||
group('find viewport start', () {
|
||||
test('several domains are in the viewport', () {
|
||||
final data = [0, 1, 2, 3, 4, 5, 6];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(4)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(5)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(6)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(2));
|
||||
});
|
||||
|
||||
test('extents are all in the viewport, use the first domain', () {
|
||||
// Start of viewport is the same as the start of the domain.
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(any)).thenReturn(0);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(0));
|
||||
});
|
||||
|
||||
test('is the first domain', () {
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(0));
|
||||
});
|
||||
|
||||
test('is the last domain', () {
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(0);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
test('is the middle', () {
|
||||
final data = [0, 1, 2, 3, 4, 5, 6];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(4)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(5)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(6)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
test('viewport is between data', () {
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(1));
|
||||
});
|
||||
|
||||
// Error case, viewport shouldn't be set to the outside of the extents.
|
||||
// We still want to provide a value.
|
||||
test('all extents greater than viewport ', () {
|
||||
// Return the right most value as start of viewport.
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(any)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
// Error case, viewport shouldn't be set to the outside of the extents.
|
||||
// We still want to provide a value.
|
||||
test('all extents less than viewport ', () {
|
||||
// Return the left most value as the start of the viewport.
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(any)).thenReturn(-1);
|
||||
|
||||
final start = renderer.findNearestViewportStart(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(0));
|
||||
});
|
||||
});
|
||||
|
||||
group('find viewport end', () {
|
||||
test('several domains are in the viewport', () {
|
||||
final data = [0, 1, 2, 3, 4, 5, 6];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(4)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(5)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(6)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(4));
|
||||
});
|
||||
|
||||
test('extents are all in the viewport, use the last domain', () {
|
||||
// Start of viewport is the same as the end of the domain.
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(any)).thenReturn(0);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
test('is the first domain', () {
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(0));
|
||||
});
|
||||
|
||||
test('is the last domain', () {
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(0);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
test('is the middle', () {
|
||||
final data = [0, 1, 2, 3, 4, 5, 6];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(0);
|
||||
when(axis.compareDomainValueToViewport(4)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(5)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(6)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
test('viewport is between data', () {
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(0)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(1)).thenReturn(-1);
|
||||
when(axis.compareDomainValueToViewport(2)).thenReturn(1);
|
||||
when(axis.compareDomainValueToViewport(3)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(2));
|
||||
});
|
||||
|
||||
// Error case, viewport shouldn't be set to the outside of the extents.
|
||||
// We still want to provide a value.
|
||||
test('all extents greater than viewport ', () {
|
||||
// Return the right most value as start of viewport.
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(any)).thenReturn(1);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(3));
|
||||
});
|
||||
|
||||
// Error case, viewport shouldn't be set to the outside of the extents.
|
||||
// We still want to provide a value.
|
||||
test('all extents less than viewport ', () {
|
||||
// Return the left most value as the start of the viewport.
|
||||
final data = [0, 1, 2, 3];
|
||||
final domainFn = (int index) => data[index];
|
||||
final axis = new MockAxis();
|
||||
when(axis.compareDomainValueToViewport(any)).thenReturn(-1);
|
||||
|
||||
final start = renderer.findNearestViewportEnd(axis, domainFn, data);
|
||||
|
||||
expect(start, equals(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user