David PHAM-VAN

Improve Chart labels

@@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
11 - Remove deprecated methods 11 - Remove deprecated methods
12 - Document.save() now returns a Future 12 - Document.save() now returns a Future
13 - Add Widget.draw() to paint any widget on a canvas 13 - Add Widget.draw() to paint any widget on a canvas
  14 +- Improve Chart labels
14 15
15 ## 1.13.0 16 ## 1.13.0
16 17
@@ -92,6 +92,7 @@ class Chart extends Widget implements Inherited { @@ -92,6 +92,7 @@ class Chart extends Widget implements Inherited {
92 if (left != null) left, 92 if (left != null) left,
93 Expanded( 93 Expanded(
94 child: Stack( 94 child: Stack(
  95 + overflow: Overflow.visible,
95 children: <Widget>[ 96 children: <Widget>[
96 grid, 97 grid,
97 if (overlay != null) overlay, 98 if (overlay != null) overlay,
@@ -18,15 +18,17 @@ import 'dart:math' as math; @@ -18,15 +18,17 @@ import 'dart:math' as math;
18 18
19 import 'package:pdf/pdf.dart'; 19 import 'package:pdf/pdf.dart';
20 20
  21 +import '../basic.dart';
21 import '../flex.dart'; 22 import '../flex.dart';
22 import '../geometry.dart'; 23 import '../geometry.dart';
  24 +import '../text.dart';
23 import '../text_style.dart'; 25 import '../text_style.dart';
24 -import '../theme.dart';  
25 import '../widget.dart'; 26 import '../widget.dart';
26 import 'chart.dart'; 27 import 'chart.dart';
27 import 'grid_cartesian.dart'; 28 import 'grid_cartesian.dart';
28 29
29 typedef GridAxisFormat = String Function(num value); 30 typedef GridAxisFormat = String Function(num value);
  31 +typedef GridAxisBuildLabel = Widget Function(num value);
30 32
31 abstract class GridAxis extends Widget { 33 abstract class GridAxis extends Widget {
32 GridAxis({ 34 GridAxis({
@@ -43,6 +45,8 @@ abstract class GridAxis extends Widget { @@ -43,6 +45,8 @@ abstract class GridAxis extends Widget {
43 bool divisionsDashed, 45 bool divisionsDashed,
44 bool ticks, 46 bool ticks,
45 bool axisTick, 47 bool axisTick,
  48 + this.angle = 0,
  49 + this.buildLabel,
46 }) : format = format ?? _defaultFormat, 50 }) : format = format ?? _defaultFormat,
47 color = color ?? PdfColors.black, 51 color = color ?? PdfColors.black,
48 width = width ?? 1, 52 width = width ?? 1,
@@ -59,6 +63,8 @@ abstract class GridAxis extends Widget { @@ -59,6 +63,8 @@ abstract class GridAxis extends Widget {
59 63
60 final GridAxisFormat format; 64 final GridAxisFormat format;
61 65
  66 + final GridAxisBuildLabel buildLabel;
  67 +
62 final TextStyle textStyle; 68 final TextStyle textStyle;
63 69
64 final double margin; 70 final double margin;
@@ -89,6 +95,8 @@ abstract class GridAxis extends Widget { @@ -89,6 +95,8 @@ abstract class GridAxis extends Widget {
89 95
90 double axisPosition = 0; 96 double axisPosition = 0;
91 97
  98 + final double angle;
  99 +
92 static String _defaultFormat(num v) => v.toString(); 100 static String _defaultFormat(num v) => v.toString();
93 101
94 double transfer(num input) { 102 double transfer(num input) {
@@ -116,6 +124,8 @@ class FixedAxis<T extends num> extends GridAxis { @@ -116,6 +124,8 @@ class FixedAxis<T extends num> extends GridAxis {
116 bool divisionsDashed, 124 bool divisionsDashed,
117 bool ticks, 125 bool ticks,
118 bool axisTick, 126 bool axisTick,
  127 + double angle = 0,
  128 + GridAxisBuildLabel buildLabel,
119 }) : assert(_isSortedAscending(values)), 129 }) : assert(_isSortedAscending(values)),
120 super( 130 super(
121 format: format, 131 format: format,
@@ -131,6 +141,8 @@ class FixedAxis<T extends num> extends GridAxis { @@ -131,6 +141,8 @@ class FixedAxis<T extends num> extends GridAxis {
131 divisionsDashed: divisionsDashed, 141 divisionsDashed: divisionsDashed,
132 ticks: ticks, 142 ticks: ticks,
133 axisTick: axisTick, 143 axisTick: axisTick,
  144 + angle: angle,
  145 + buildLabel: buildLabel,
134 ); 146 );
135 147
136 static FixedAxis<int> fromStrings( 148 static FixedAxis<int> fromStrings(
@@ -147,6 +159,8 @@ class FixedAxis<T extends num> extends GridAxis { @@ -147,6 +159,8 @@ class FixedAxis<T extends num> extends GridAxis {
147 bool divisionsDashed, 159 bool divisionsDashed,
148 bool ticks, 160 bool ticks,
149 bool axisTick, 161 bool axisTick,
  162 + double angle = 0,
  163 + GridAxisBuildLabel buildLabel,
150 }) { 164 }) {
151 return FixedAxis<int>( 165 return FixedAxis<int>(
152 List<int>.generate(values.length, (int index) => index), 166 List<int>.generate(values.length, (int index) => index),
@@ -163,6 +177,8 @@ class FixedAxis<T extends num> extends GridAxis { @@ -163,6 +177,8 @@ class FixedAxis<T extends num> extends GridAxis {
163 divisionsDashed: divisionsDashed, 177 divisionsDashed: divisionsDashed,
164 ticks: ticks, 178 ticks: ticks,
165 axisTick: axisTick, 179 axisTick: axisTick,
  180 + angle: angle,
  181 + buildLabel: buildLabel,
166 ); 182 );
167 } 183 }
168 184
@@ -202,6 +218,30 @@ class FixedAxis<T extends num> extends GridAxis { @@ -202,6 +218,30 @@ class FixedAxis<T extends num> extends GridAxis {
202 return null; 218 return null;
203 } 219 }
204 220
  221 + Widget _text(num value) {
  222 + final t = buildLabel == null
  223 + ? Text(format(value), style: textStyle)
  224 + : buildLabel(value);
  225 + if (angle == 0.0) {
  226 + return t;
  227 + }
  228 +
  229 + return Transform.rotateBox(
  230 + angle: angle,
  231 + child: t,
  232 + );
  233 + }
  234 +
  235 + int _angleDirection() {
  236 + if (angle == 0.0) {
  237 + return 0;
  238 + }
  239 + if (angle % math.pi > math.pi / 2) {
  240 + return -1;
  241 + }
  242 + return 1;
  243 + }
  244 +
205 @override 245 @override
206 void layout(Context context, BoxConstraints constraints, 246 void layout(Context context, BoxConstraints constraints,
207 {bool parentUsesSize = false}) { 247 {bool parentUsesSize = false}) {
@@ -209,26 +249,28 @@ class FixedAxis<T extends num> extends GridAxis { @@ -209,26 +249,28 @@ class FixedAxis<T extends num> extends GridAxis {
209 '$runtimeType cannot be used without a Chart widget'); 249 '$runtimeType cannot be used without a Chart widget');
210 250
211 final size = constraints.biggest; 251 final size = constraints.biggest;
212 - final style = Theme.of(context).defaultTextStyle.merge(textStyle);  
213 - final font = style.font.getFont(context);  
214 252
215 var maxWidth = 0.0; 253 var maxWidth = 0.0;
216 var maxHeight = 0.0; 254 var maxHeight = 0.0;
217 - PdfFontMetrics metricsFirst;  
218 - PdfFontMetrics metrics; 255 + PdfPoint first;
  256 + PdfPoint last;
  257 +
219 for (final value in values) { 258 for (final value in values) {
220 - metrics = font.stringMetrics(format(value)) * style.fontSize;  
221 - metricsFirst ??= metrics;  
222 - maxWidth = math.max(maxWidth, metrics.maxWidth);  
223 - maxHeight = math.max(maxHeight, metrics.maxHeight); 259 + last = Widget.measure(_text(value), context: context);
  260 + maxWidth = math.max(maxWidth, last.x);
  261 + maxHeight = math.max(maxHeight, last.y);
  262 + first ??= last;
224 } 263 }
225 264
  265 + final ad = _angleDirection();
  266 +
226 switch (direction) { 267 switch (direction) {
227 case Axis.horizontal: 268 case Axis.horizontal:
228 _textMargin = margin ?? 2; 269 _textMargin = margin ?? 2;
229 _axisTick ??= false; 270 _axisTick ??= false;
230 - final minStart = metricsFirst.maxWidth / 2;  
231 - _marginEnd = math.max(_marginEnd, metrics.maxWidth / 2); 271 + final minStart = ad == 0 ? first.x / 2 : (ad > 0 ? first.x : 0.0);
  272 + _marginEnd = math.max(
  273 + _marginEnd, ad == 0 ? last.x / 2 : (ad > 0 ? 0.0 : last.x));
232 crossAxisPosition = math.max(crossAxisPosition, minStart); 274 crossAxisPosition = math.max(crossAxisPosition, minStart);
233 axisPosition = math.max(axisPosition, maxHeight + _textMargin); 275 axisPosition = math.max(axisPosition, maxHeight + _textMargin);
234 box = PdfRect(0, 0, size.x, axisPosition); 276 box = PdfRect(0, 0, size.x, axisPosition);
@@ -236,9 +278,9 @@ class FixedAxis<T extends num> extends GridAxis { @@ -236,9 +278,9 @@ class FixedAxis<T extends num> extends GridAxis {
236 case Axis.vertical: 278 case Axis.vertical:
237 _textMargin = margin ?? 10; 279 _textMargin = margin ?? 10;
238 _axisTick ??= true; 280 _axisTick ??= true;
239 - _marginEnd = math.max(_marginEnd, metrics.maxHeight / 2);  
240 - final minStart = metricsFirst.maxHeight / 2;  
241 - _marginEnd = math.max(_marginEnd, metrics.maxWidth / 2); 281 + _marginEnd = math.max(
  282 + _marginEnd, ad == 0 ? last.x / 2 : (ad < 0 ? last.x : 0.0));
  283 + final minStart = ad == 0 ? first.y / 2 : (ad > 0 ? first.x : 0.0);
242 crossAxisPosition = math.max(crossAxisPosition, minStart); 284 crossAxisPosition = math.max(crossAxisPosition, minStart);
243 axisPosition = math.max(axisPosition, maxWidth + _textMargin); 285 axisPosition = math.max(axisPosition, maxWidth + _textMargin);
244 box = PdfRect(0, 0, axisPosition, size.y); 286 box = PdfRect(0, 0, axisPosition, size.y);
@@ -273,21 +315,18 @@ class FixedAxis<T extends num> extends GridAxis { @@ -273,21 +315,18 @@ class FixedAxis<T extends num> extends GridAxis {
273 ..setLineJoin(PdfLineJoin.bevel) 315 ..setLineJoin(PdfLineJoin.bevel)
274 ..strokePath(); 316 ..strokePath();
275 317
  318 + final ad = _angleDirection();
  319 +
276 for (final y in values) { 320 for (final y in values) {
277 - final v = format(y);  
278 - final style = Theme.of(context).defaultTextStyle.merge(textStyle);  
279 - final font = style.font.getFont(context);  
280 - final metrics = font.stringMetrics(v) * style.fontSize;  
281 final p = toChart(y); 321 final p = toChart(y);
282 322
283 - context.canvas  
284 - ..setColor(style.color)  
285 - ..drawString(  
286 - style.font.getFont(context),  
287 - style.fontSize,  
288 - v,  
289 - axisPosition - _textMargin - metrics.maxWidth,  
290 - p - (metrics.ascent + metrics.descent) / 2, 323 + Widget.draw(
  324 + _text(y),
  325 + offset: PdfPoint(axisPosition - _textMargin, p),
  326 + context: context,
  327 + alignment: ad == 0
  328 + ? Alignment.centerRight
  329 + : (ad > 0 ? Alignment.topRight : Alignment.bottomRight),
291 ); 330 );
292 } 331 }
293 } 332 }
@@ -318,21 +357,18 @@ class FixedAxis<T extends num> extends GridAxis { @@ -318,21 +357,18 @@ class FixedAxis<T extends num> extends GridAxis {
318 ..setLineJoin(PdfLineJoin.bevel) 357 ..setLineJoin(PdfLineJoin.bevel)
319 ..strokePath(); 358 ..strokePath();
320 359
  360 + final ad = _angleDirection();
  361 +
321 for (final num x in values) { 362 for (final num x in values) {
322 - final v = format(x);  
323 - final style = Theme.of(context).defaultTextStyle.merge(textStyle);  
324 - final font = style.font.getFont(context);  
325 - final metrics = font.stringMetrics(v) * style.fontSize;  
326 final p = toChart(x); 363 final p = toChart(x);
327 364
328 - context.canvas  
329 - ..setColor(style.color)  
330 - ..drawString(  
331 - style.font.getFont(context),  
332 - style.fontSize,  
333 - v,  
334 - p - metrics.maxWidth / 2,  
335 - axisPosition - metrics.ascent - _textMargin, 365 + Widget.draw(
  366 + _text(x),
  367 + offset: PdfPoint(p, axisPosition - _textMargin),
  368 + context: context,
  369 + alignment: ad == 0
  370 + ? Alignment.topCenter
  371 + : (ad > 0 ? Alignment.topRight : Alignment.topLeft),
336 ); 372 );
337 } 373 }
338 } 374 }