From 33385a917c676f66c1521f5f317213ac145dabeb Mon Sep 17 00:00:00 2001 From: entronad Date: Mon, 20 Dec 2021 21:37:11 +0800 Subject: [PATCH] dash line --- DEVLOG.md | 4 ++++ example/lib/pages/echarts.dart | 2 ++ example/lib/pages/line_area.dart | 9 ++++++++- lib/graphic.dart | 1 + lib/src/aes/aes.dart | 3 --- lib/src/common/label.dart | 2 -- lib/src/common/styles.dart | 19 ++++++++++++++++++- lib/src/coord/coord.dart | 2 -- lib/src/dataflow/tuple.dart | 2 -- lib/src/graffiti/figure.dart | 2 -- lib/src/graffiti/graffiti.dart | 3 --- lib/src/guide/annotation/line.dart | 8 ++++---- lib/src/guide/annotation/tag.dart | 2 -- lib/src/guide/axis/circular.dart | 10 ++++------ lib/src/guide/axis/horizontal.dart | 14 ++++++-------- lib/src/guide/axis/radial.dart | 10 ++++------ lib/src/guide/axis/vertical.dart | 14 ++++++-------- lib/src/guide/interaction/crosshair.dart | 16 ++++++++-------- lib/src/guide/interaction/tooltip.dart | 3 --- lib/src/parse/parse.dart | 1 - lib/src/shape/area.dart | 2 -- lib/src/shape/interval.dart | 2 -- lib/src/shape/line.dart | 16 ++++++++++++---- lib/src/shape/point.dart | 2 -- lib/src/shape/polygon.dart | 1 - lib/src/util/path.dart | 17 +++++++++++++++++ pubspec.yaml | 1 + 27 files changed, 95 insertions(+), 73 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 6774d30..604bd22 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -3908,6 +3908,10 @@ antv scale 的 nice number计算已经早就改为用d3的了,后续需要更 charts_flutter中的虚线是自己实现的,只能画垂直或水平。fl_chart等中也是用的path_drawing +什么地方提供dash api,就是当这里不能直接设置path时,可直接设置path时自己调用Paths.dashLine + +不管是什么类型的 source path,都叫 dash line 吧,这好像比较符合 flutter的哲学,就是一段一段分隔的线 + hash选用31做底数,因为它1是个不大不小的底数,2.31 * i == (i << 5) - i ## TODO diff --git a/example/lib/pages/echarts.dart b/example/lib/pages/echarts.dart index 0958607..07d722b 100644 --- a/example/lib/pages/echarts.dart +++ b/example/lib/pages/echarts.dart @@ -230,6 +230,7 @@ class EchartsPage extends StatelessWidget { value: 11.14, style: StrokeStyle( color: const Color(0xff5470c6).withAlpha(100), + dash: [2], ), ), LineAnnotation( @@ -237,6 +238,7 @@ class EchartsPage extends StatelessWidget { value: 1.57, style: StrokeStyle( color: const Color(0xff91cc75).withAlpha(100), + dash: [2], ), ), MarkAnnotation( diff --git a/example/lib/pages/line_area.dart b/example/lib/pages/line_area.dart index e3d56cf..9b2d3ef 100644 --- a/example/lib/pages/line_area.dart +++ b/example/lib/pages/line_area.dart @@ -31,6 +31,13 @@ class LineAreaPage extends StatelessWidget { ), padding: const EdgeInsets.fromLTRB(20, 40, 20, 5), ), + Container( + child: const Text( + '- Dash line.', + ), + padding: const EdgeInsets.fromLTRB(10, 5, 10, 0), + alignment: Alignment.centerLeft, + ), Container( child: const Text( '- With time scale in domain dimension.', @@ -62,7 +69,7 @@ class LineAreaPage extends StatelessWidget { accessor: (TimeSeriesSales datum) => datum.sales, ), }, - elements: [LineElement()], + elements: [LineElement(shape: ShapeAttr(value: BasicLineShape(dash: [5, 2])))], axes: [ Defaults.horizontalAxis, Defaults.verticalAxis, diff --git a/lib/graphic.dart b/lib/graphic.dart index 60b9b97..3f5782b 100644 --- a/lib/graphic.dart +++ b/lib/graphic.dart @@ -203,3 +203,4 @@ export 'src/common/defaults.dart' show Defaults; export 'src/dataflow/tuple.dart' show Tuple, Aes; export 'src/util/path.dart' show Paths; +export 'package:path_drawing/path_drawing.dart' show DashOffset; diff --git a/lib/src/aes/aes.dart b/lib/src/aes/aes.dart index 33aaafe..3f54627 100644 --- a/lib/src/aes/aes.dart +++ b/lib/src/aes/aes.dart @@ -1,7 +1,4 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; -import 'package:graphic/graphic.dart'; import 'package:graphic/src/aes/position.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/dataflow/operator.dart'; diff --git a/lib/src/common/label.dart b/lib/src/common/label.dart index 42327ca..dc4bf45 100644 --- a/lib/src/common/label.dart +++ b/lib/src/common/label.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/aes/label.dart'; import 'package:graphic/src/graffiti/figure.dart'; diff --git a/lib/src/common/styles.dart b/lib/src/common/styles.dart index 0db9af3..8051095 100644 --- a/lib/src/common/styles.dart +++ b/lib/src/common/styles.dart @@ -1,11 +1,15 @@ import 'dart:ui'; +import 'package:collection/collection.dart'; +import 'package:graphic/src/util/path.dart'; + /// The style of a stroke. class StrokeStyle { /// Creates a stroke style StrokeStyle({ this.color = const Color(0xff000000), this.width = 1, + this.dash, }); /// The stroke color. @@ -14,8 +18,16 @@ class StrokeStyle { /// The stroke width. double width; + /// The circular array of dash offsets and lengths. + /// + /// For example, the array `[5, 10]` would result in dashes 5 pixels long + /// followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would + /// result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap, + /// a 10 pixel dash, etc. + List? dash; + bool operator ==(Object other) => - other is StrokeStyle && color == other.color && width == other.width; + other is StrokeStyle && color == other.color && width == other.width && DeepCollectionEquality().equals(dash, other.dash); /// Gets [Paint] object from this stroke style. /// @@ -25,4 +37,9 @@ class StrokeStyle { ..style = PaintingStyle.stroke ..color = color ..strokeWidth = width; + + /// Gets the dash line from a source path. + Path dashPath(Path path) => dash == null + ? path + : Paths.dashLine(source: path, dashArray: dash!); } diff --git a/lib/src/coord/coord.dart b/lib/src/coord/coord.dart index d4299e2..970d1bd 100644 --- a/lib/src/coord/coord.dart +++ b/lib/src/coord/coord.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/converter.dart'; import 'package:graphic/src/dataflow/operator.dart'; diff --git a/lib/src/dataflow/tuple.dart b/lib/src/dataflow/tuple.dart index 5264cc4..55b5a55 100644 --- a/lib/src/dataflow/tuple.dart +++ b/lib/src/dataflow/tuple.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/coord/coord.dart'; diff --git a/lib/src/graffiti/figure.dart b/lib/src/graffiti/figure.dart index bbbe72b..a35c27a 100644 --- a/lib/src/graffiti/figure.dart +++ b/lib/src/graffiti/figure.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; /// The base class of figures. diff --git a/lib/src/graffiti/graffiti.dart b/lib/src/graffiti/graffiti.dart index 098d04c..75ff611 100644 --- a/lib/src/graffiti/graffiti.dart +++ b/lib/src/graffiti/graffiti.dart @@ -1,6 +1,3 @@ -import 'dart:ui'; - -import 'package:flutter/painting.dart'; import 'package:flutter/widgets.dart'; import 'scene.dart'; diff --git a/lib/src/guide/annotation/line.dart b/lib/src/guide/annotation/line.dart index 741cabe..7e6ec2b 100644 --- a/lib/src/guide/annotation/line.dart +++ b/lib/src/guide/annotation/line.dart @@ -83,7 +83,7 @@ class LineAnnotRenderOp extends AnnotRenderOp { if (coord is PolarCoordConv && coord.getCanvasDim(dim) == 2) { scene.figures = [ PathFigure( - Path() + style.dashPath(Path() ..addArc( Rect.fromCircle( center: coord.center, @@ -91,21 +91,21 @@ class LineAnnotRenderOp extends AnnotRenderOp { ), coord.angles.first, coord.angles.last - coord.angles.first, - ), + )), style.toPaint(), ) ]; } else { scene.figures = [ PathFigure( - Paths.line( + style.dashPath(Paths.line( from: coord.convert( dim == 1 ? Offset(position, 0) : Offset(0, position), ), to: coord.convert( dim == 1 ? Offset(position, 1) : Offset(1, position), ), - ), + )), style.toPaint(), ) ]; diff --git a/lib/src/guide/annotation/tag.dart b/lib/src/guide/annotation/tag.dart index e20baf6..5a4953d 100644 --- a/lib/src/guide/annotation/tag.dart +++ b/lib/src/guide/annotation/tag.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/graffiti/figure.dart'; diff --git a/lib/src/guide/axis/circular.dart b/lib/src/guide/axis/circular.dart index f71a7fd..8b93206 100644 --- a/lib/src/guide/axis/circular.dart +++ b/lib/src/guide/axis/circular.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/common/styles.dart'; @@ -26,12 +24,12 @@ List
? renderCircularAxis( if (line != null) { rst.add(PathFigure( - Path() + line.dashPath(Path() ..addArc( Rect.fromCircle(center: coord.center, radius: r), coord.startAngle, coord.endAngle - coord.startAngle, - ), + )), line.toPaint()..style = PaintingStyle.stroke, )); } @@ -80,10 +78,10 @@ List
? renderCircularGrid( final angle = coord.convertAngle(tick.position); if (angle >= coord.startAngle && angle <= coord.endAngle) { rst.add(PathFigure( - Paths.line( + tick.grid!.dashPath(Paths.line( from: coord.polarToOffset(angle, coord.startRadius), to: coord.polarToOffset(angle, coord.endRadius), - ), + )), tick.grid!.toPaint(), )); } diff --git a/lib/src/guide/axis/horizontal.dart b/lib/src/guide/axis/horizontal.dart index bfde0e5..120e0fa 100644 --- a/lib/src/guide/axis/horizontal.dart +++ b/lib/src/guide/axis/horizontal.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/common/styles.dart'; @@ -25,10 +23,10 @@ List
? renderHorizontalAxis( if (line != null) { rst.add(PathFigure( - Paths.line( + line.dashPath(Paths.line( from: Offset(region.left, y), to: Offset(region.right, y), - ), + )), line.toPaint(), )); } @@ -40,10 +38,10 @@ List
? renderHorizontalAxis( if (x >= region.left && x <= region.right) { if (tick.tickLine != null) { rst.add(PathFigure( - Paths.line( + tick.tickLine!.style.dashPath(Paths.line( from: Offset(x, y), to: Offset(x, y + tick.tickLine!.length * flipSign), - ), + )), tick.tickLine!.style.toPaint(), )); } @@ -75,10 +73,10 @@ List
? renderHorizontalGrid( final x = coordLeft + tick.position * (coordRight - coordLeft); if (x >= region.left && x <= region.right) { rst.add(PathFigure( - Paths.line( + tick.grid!.dashPath(Paths.line( from: Offset(x, region.bottom), to: Offset(x, region.top), - ), + )), tick.grid!.toPaint(), )); } diff --git a/lib/src/guide/axis/radial.dart b/lib/src/guide/axis/radial.dart index 0330509..ffd7ea6 100644 --- a/lib/src/guide/axis/radial.dart +++ b/lib/src/guide/axis/radial.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/common/styles.dart'; @@ -61,10 +59,10 @@ List
? renderRadialAxis( if (line != null) { rst.add(PathFigure( - Paths.line( + line.dashPath(Paths.line( from: coord.polarToOffset(angle, coord.startRadius), to: coord.polarToOffset(angle, coord.endRadius), - ), + )), line.toPaint(), )); } @@ -102,12 +100,12 @@ List
? renderRadialGrid( final r = coord.convertRadius(tick.position); if (r >= coord.startRadius && r <= coord.endRadius) { rst.add(PathFigure( - Path() + tick.grid!.dashPath(Path() ..addArc( Rect.fromCircle(center: coord.center, radius: r), coord.startAngle, coord.endAngle - coord.startAngle, - ), + )), tick.grid!.toPaint()..style = PaintingStyle.stroke, )); } diff --git a/lib/src/guide/axis/vertical.dart b/lib/src/guide/axis/vertical.dart index 0385a8a..390e7b6 100644 --- a/lib/src/guide/axis/vertical.dart +++ b/lib/src/guide/axis/vertical.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/common/styles.dart'; @@ -25,10 +23,10 @@ List
? renderVerticalAxis( if (line != null) { rst.add(PathFigure( - Paths.line( + line.dashPath(Paths.line( from: Offset(x, region.bottom), to: Offset(x, region.top), - ), + )), line.toPaint(), )); } @@ -40,10 +38,10 @@ List
? renderVerticalAxis( if (y >= region.top && y <= region.bottom) { if (tick.tickLine != null) { rst.add(PathFigure( - Paths.line( + tick.tickLine!.style.dashPath(Paths.line( from: Offset(x, y), to: Offset(x - tick.tickLine!.length * flipSign, y), - ), + )), tick.tickLine!.style.toPaint(), )); } @@ -75,10 +73,10 @@ List
? renderVerticalGrid( final y = coordBottom - tick.position * (coordBottom - coordTop); if (y >= region.top && y <= region.bottom) { rst.add(PathFigure( - Paths.line( + tick.grid!.dashPath(Paths.line( from: Offset(region.left, y), to: Offset(region.right, y), - ), + )), tick.grid!.toPaint(), )); } diff --git a/lib/src/guide/interaction/crosshair.dart b/lib/src/guide/interaction/crosshair.dart index c48315e..0a9241a 100644 --- a/lib/src/guide/interaction/crosshair.dart +++ b/lib/src/guide/interaction/crosshair.dart @@ -143,19 +143,19 @@ class CrosshairRenderOp extends Render { final canvasCross = coord.convert(cross); if (canvasStyleX != null) { figures.add(PathFigure( - Paths.line( + canvasStyleX.dashPath(Paths.line( from: Offset(canvasCross.dx, region.top), to: Offset(canvasCross.dx, region.bottom), - ), + )), canvasStyleX.toPaint(), )); } if (canvasStyleY != null) { figures.add(PathFigure( - Paths.line( + canvasStyleY.dashPath(Paths.line( from: Offset(region.left, canvasCross.dy), to: Offset(region.right, canvasCross.dy), - ), + )), canvasStyleY.toPaint(), )); } @@ -165,10 +165,10 @@ class CrosshairRenderOp extends Render { final angle = polarCoord .convertAngle(polarCoord.transposed ? cross.dy : cross.dx); figures.add(PathFigure( - Paths.line( + canvasStyleX.dashPath(Paths.line( from: polarCoord.polarToOffset(angle, coord.startRadius), to: polarCoord.polarToOffset(angle, coord.endRadius), - ), + )), canvasStyleX.toPaint(), )); } @@ -176,12 +176,12 @@ class CrosshairRenderOp extends Render { final r = polarCoord .convertRadius(polarCoord.transposed ? cross.dx : cross.dy); figures.add(PathFigure( - Path() + canvasStyleY.dashPath(Path() ..addArc( Rect.fromCircle(center: coord.center, radius: r), coord.startAngle, coord.endAngle - coord.startAngle, - ), + )), canvasStyleY.toPaint()..style = PaintingStyle.stroke, )); } diff --git a/lib/src/guide/interaction/tooltip.dart b/lib/src/guide/interaction/tooltip.dart index 421748b..ea31077 100644 --- a/lib/src/guide/interaction/tooltip.dart +++ b/lib/src/guide/interaction/tooltip.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:collection/collection.dart'; import 'package:flutter/painting.dart'; import 'package:graphic/src/chart/chart.dart'; @@ -12,7 +10,6 @@ import 'package:graphic/src/dataflow/tuple.dart'; import 'package:graphic/src/graffiti/figure.dart'; import 'package:graphic/src/graffiti/scene.dart'; import 'package:graphic/src/interaction/selection/interval.dart'; -import 'package:graphic/src/interaction/selection/point.dart'; import 'package:graphic/src/interaction/selection/selection.dart'; import 'package:graphic/src/scale/scale.dart'; import 'package:graphic/src/util/assert.dart'; diff --git a/lib/src/parse/parse.dart b/lib/src/parse/parse.dart index 35ea083..03aa0bc 100644 --- a/lib/src/parse/parse.dart +++ b/lib/src/parse/parse.dart @@ -1,4 +1,3 @@ -import 'dart:ui'; import 'dart:math'; import 'package:flutter/painting.dart'; diff --git a/lib/src/shape/area.dart b/lib/src/shape/area.dart index 3753fb9..98974cf 100644 --- a/lib/src/shape/area.dart +++ b/lib/src/shape/area.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/coord/coord.dart'; diff --git a/lib/src/shape/interval.dart b/lib/src/shape/interval.dart index 6474f23..acf767e 100644 --- a/lib/src/shape/interval.dart +++ b/lib/src/shape/interval.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/coord/coord.dart'; diff --git a/lib/src/shape/line.dart b/lib/src/shape/line.dart index 7ad4418..e3fa831 100644 --- a/lib/src/shape/line.dart +++ b/lib/src/shape/line.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:collection/collection.dart'; import 'package:flutter/painting.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/coord/coord.dart'; @@ -35,6 +34,7 @@ class BasicLineShape extends LineShape { BasicLineShape({ this.smooth = false, this.loop = false, + this.dash, }); /// Whether this line is smooth. @@ -45,9 +45,17 @@ class BasicLineShape extends LineShape { /// It is usefull in the polar coordinate. final bool loop; + /// The circular array of dash offsets and lengths. + /// + /// For example, the array `[5, 10]` would result in dashes 5 pixels long + /// followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would + /// result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap, + /// a 10 pixel dash, etc. + final List? dash; + @override bool equalTo(Object other) => - other is BasicLineShape && smooth == other.smooth && loop == other.loop; + other is BasicLineShape && smooth == other.smooth && loop == other.loop && DeepCollectionEquality().equals(dash, other.dash); @override List
renderGroup( @@ -95,7 +103,7 @@ class BasicLineShape extends LineShape { final represent = group.first; rst.addAll(renderBasicItem( - path, + dash == null ? path : Paths.dashLine(source: path, dashArray: dash!), represent, true, represent.size ?? defaultSize, diff --git a/lib/src/shape/point.dart b/lib/src/shape/point.dart index b81ce34..e71a5a2 100644 --- a/lib/src/shape/point.dart +++ b/lib/src/shape/point.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/cupertino.dart'; import 'package:graphic/src/common/label.dart'; import 'package:graphic/src/coord/coord.dart'; diff --git a/lib/src/shape/polygon.dart b/lib/src/shape/polygon.dart index efc636a..ee3f62b 100644 --- a/lib/src/shape/polygon.dart +++ b/lib/src/shape/polygon.dart @@ -1,4 +1,3 @@ -import 'dart:ui'; import 'dart:math'; import 'package:flutter/painting.dart'; diff --git a/lib/src/util/path.dart b/lib/src/util/path.dart index 8a44054..0c38fec 100644 --- a/lib/src/util/path.dart +++ b/lib/src/util/path.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'dart:ui'; +import 'package:path_drawing/path_drawing.dart'; import 'package:vector_math/vector_math_64.dart'; import 'package:graphic/src/util/math.dart'; @@ -45,6 +46,22 @@ abstract class Paths { return path; } + /// A dash line path function. + /// + /// It is drawn from the segments of [source]. Passing a [source] that is an empty + /// path will return an empty path. Dash intervals are controled by the [dashArray]. + /// The [dashOffset] specifies an initial starting point for the dashing. + /// + /// This functions can either return a new path or add to existing [path]. + static Path dashLine({ + required Path source, + required List dashArray, + DashOffset? dashOffset, + Path? path, + }) => + (path ?? Path()) + ..addPath(dashPath(source, dashArray: CircularIntervalList(dashArray), dashOffset: dashOffset), Offset.zero); + /// A circle path function. /// /// This functions can either return a new path or add to existing [path]. diff --git a/pubspec.yaml b/pubspec.yaml index edb6d58..a2d5f21 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: flutter: sdk: flutter vector_math: ^2.1.0 + path_drawing: ^1.0.0 dev_dependencies: flutter_test: