From 0de21f8ce77505fd9ecd6e0f13e84dd721073734 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:28:53 +0800 Subject: [PATCH 1/2] feat: add counter text type for tick contract markers Introduce `MarkerTextType` enum (`plain` / `counter`) on `ChartMarker` to control how the `text` field is rendered on the dashed connector line. - Add `makeDelimitedTextPainter` helper that splits text on a delimiter and applies distinct styles to each part (e.g. "2" vs "/10") - Render text inline on the dashed line in `TickMarkerIconPainter`, splitting the line around the label when text is present - Add `markerTextColor` to `MarkerStyle` and both default themes --- .../markers/chart_marker.dart | 17 +++ .../tick_marker_icon_painter.dart | 126 ++++++++++++++++-- .../helpers/paint_functions/paint_text.dart | 27 ++++ lib/src/theme/chart_default_dark_theme.dart | 1 + lib/src/theme/chart_default_light_theme.dart | 1 + lib/src/theme/colors.dart | 6 + .../theme/painting_styles/marker_style.dart | 4 + 7 files changed, 172 insertions(+), 10 deletions(-) diff --git a/lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart b/lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart index da4580636..d559e34f9 100644 --- a/lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart +++ b/lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart @@ -148,6 +148,19 @@ enum MarkerType { checkpointSpot, } +/// Determines how the [ChartMarker.text] is styled. +enum MarkerTextType { + /// Renders the text in a uniform style. + plain, + + /// Renders the text as a progress counter (e.g. `"2/10"`). + /// + /// The text must contain a `/` separator. The part before `/` is rendered + /// prominently to indicate the current value, while the `/` and the part + /// after it are rendered smaller to indicate the total. + counter, +} + /// A specialized marker class for displaying various types of markers on a financial chart. /// /// `ChartMarker` extends the base `Marker` class to provide additional functionality @@ -195,6 +208,7 @@ class ChartMarker extends Marker { VoidCallback? onTap, this.markerType, this.text, + this.textType = MarkerTextType.plain, this.color, this.hasReducedOpacity = false, this.displayOffset = Offset.zero, @@ -221,6 +235,9 @@ class ChartMarker extends Marker { /// If null, no text is displayed for the marker. final String? text; + /// Controls how [text] is styled. + final MarkerTextType textType; + /// The color of the marker. /// /// If provided, this color overrides the default direction-based coloring. diff --git a/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart b/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart index cc9496223..2b0af0ea4 100644 --- a/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/markers/marker_group.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/marker_group_icon_painter.dart'; @@ -196,16 +197,35 @@ class TickMarkerIconPainter extends MarkerGroupIconPainter { YAxisConfig.instance.yAxisClipping(canvas, size, () { // Horizontal dashed line from contractMarker to start time if (_contractMarkerOffset != null && _startCollapsedOffset != null) { - paintHorizontalDashedLine( - canvas, - _contractMarkerOffset.dx, - _startCollapsedOffset.dx, - _contractMarkerOffset.dy, - finalLineColor, - 1, - dashWidth: 2, - dashSpace: 2, - ); + final ChartMarker? contractMarker = markerGroup.markers + .firstWhereOrNull((m) => m.markerType == MarkerType.contractMarker); + final String? contractText = contractMarker?.text; + + if (contractMarker != null && + contractText != null && + contractText.isNotEmpty) { + _paintDashedLineWithText( + canvas, + _contractMarkerOffset, + _startCollapsedOffset, + finalLineColor, + contractText, + contractMarker.textType, + theme, + opacity, + ); + } else { + paintHorizontalDashedLine( + canvas, + _startCollapsedOffset.dx, + _contractMarkerOffset.dx, + _contractMarkerOffset.dy, + finalLineColor, + 1, + dashWidth: 2, + dashSpace: 2, + ); + } } final Paint solidLinePaint = Paint() @@ -279,6 +299,92 @@ class TickMarkerIconPainter extends MarkerGroupIconPainter { }); } + /// Draws the dashed connector line with a text label split into it. + /// + /// The text is positioned near [lineEnd] (the start time collapsed marker) + /// with a small padding gap. The dashed line is drawn on both sides of the + /// text. + void _paintDashedLineWithText( + Canvas canvas, + Offset lineStart, + Offset lineEnd, + Color lineColor, + String text, + MarkerTextType textType, + ChartTheme theme, + double opacity, + ) { + const double _textPaddingFromStartLine = 16; + const double _textGap = 4; + + final Color textColor = + theme.markerStyle.markerTextColor.withOpacity(opacity); + + final TextPainter textPainter = textType == MarkerTextType.counter + ? makeDelimitedTextPainter( + text, + delimiter: '/', + primaryStyle: TextStyle( + color: textColor, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + secondaryStyle: TextStyle( + color: textColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ) + : makeTextPainter( + text, + TextStyle( + color: textColor, + fontSize: 12, + fontWeight: FontWeight.w700, + height: 1, + ), + ); + + final double textRight = lineEnd.dx - _textPaddingFromStartLine; + final double textLeft = textRight - textPainter.width; + + if (lineStart.dx < textLeft - _textGap) { + // Left portion of dashed line. + paintHorizontalDashedLine( + canvas, + textLeft - _textGap, + lineStart.dx, + lineStart.dy, + lineColor, + 1, + dashWidth: 2, + dashSpace: 2, + ); + } + + // Right portion of dashed line (text to startTimeCollapsed). + if (lineStart.dx < lineEnd.dx) { + paintHorizontalDashedLine( + canvas, + textRight + _textGap, + lineEnd.dx, + lineStart.dy, + lineColor, + 1, + dashWidth: 2, + dashSpace: 2, + ); + } + + // Draw text centered vertically on the line. + paintWithTextPainter( + canvas, + painter: textPainter, + anchor: Offset(textLeft, lineStart.dy), + anchorAlignment: Alignment.centerLeft, + ); + } + /// Renders an individual marker based on its type. /// /// This private method handles the rendering of different types of markers diff --git a/lib/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart b/lib/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart index 04efa7220..6ffa58d15 100644 --- a/lib/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart +++ b/lib/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart @@ -74,6 +74,33 @@ TextPainter makeFittedTextPainter( return makeTextPainter(text, fittedStyle); } +/// Constructs a [TextPainter] by splitting [text] on the first occurrence of +/// [delimiter] and applying [primaryStyle] to the part before it and +/// [secondaryStyle] to the delimiter and the part after it. +/// +/// Falls back to [makeTextPainter] with [primaryStyle] if [delimiter] is not +/// found or appears at the start of [text]. +TextPainter makeDelimitedTextPainter( + String text, { + required String delimiter, + required TextStyle primaryStyle, + required TextStyle secondaryStyle, +}) { + final int index = text.indexOf(delimiter); + if (index > 0) { + return TextPainter( + text: TextSpan( + children: [ + TextSpan(text: text.substring(0, index), style: primaryStyle), + TextSpan(text: text.substring(index), style: secondaryStyle), + ], + ), + textDirection: TextDirection.ltr, + )..layout(); + } + return makeTextPainter(text, primaryStyle); +} + /// Paints on the canvas with the given text painter. void paintWithTextPainter( Canvas canvas, { diff --git a/lib/src/theme/chart_default_dark_theme.dart b/lib/src/theme/chart_default_dark_theme.dart index 3e1f559c5..2ffc95268 100644 --- a/lib/src/theme/chart_default_dark_theme.dart +++ b/lib/src/theme/chart_default_dark_theme.dart @@ -256,5 +256,6 @@ class ChartDefaultDarkTheme extends ChartDefaultTheme { lineDefaultColor: DarkThemeColors.markerPaletteBorderColor, upColorProminent: DarkThemeColors.upColorProminent, downColorProminent: DarkThemeColors.downColorProminent, + markerTextColor: DarkThemeColors.markerTextColor, ); } diff --git a/lib/src/theme/chart_default_light_theme.dart b/lib/src/theme/chart_default_light_theme.dart index 273db1bbf..94930b4ef 100644 --- a/lib/src/theme/chart_default_light_theme.dart +++ b/lib/src/theme/chart_default_light_theme.dart @@ -256,5 +256,6 @@ class ChartDefaultLightTheme extends ChartDefaultTheme { lineDefaultColor: LightThemeColors.markerPaletteBorderColor, upColorProminent: LightThemeColors.upColorProminent, downColorProminent: LightThemeColors.downColorProminent, + markerTextColor: LightThemeColors.markerTextColor, ); } diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 4c919aac3..4d38d5162 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -175,6 +175,9 @@ class LightThemeColors { LightThemeDesignTokens.semanticColorEmeraldSolidBorderInverseHigh; static const Color downColorProminent = LightThemeDesignTokens.semanticColorCherrySolidBorderInverseHigh; + + static const Color markerTextColor = + ComponentDesignTokens.componentTextIconNormalProminentLight; } /// Default colors for dark theme. @@ -310,6 +313,9 @@ class DarkThemeColors { DarkThemeDesignTokens.semanticColorEmeraldSolidBorderInverseHigh; static const Color downColorProminent = DarkThemeDesignTokens.semanticColorCherrySolidBorderInverseHigh; + + static const Color markerTextColor = + ComponentDesignTokens.componentTextIconNormalProminentDark; } /// Candle Bullish colors for light, dark diff --git a/lib/src/theme/painting_styles/marker_style.dart b/lib/src/theme/painting_styles/marker_style.dart index 2f7e37c45..7a7758fb7 100644 --- a/lib/src/theme/painting_styles/marker_style.dart +++ b/lib/src/theme/painting_styles/marker_style.dart @@ -17,6 +17,7 @@ class MarkerStyle extends ChartPaintingStyle { this.lineProfitColor = const Color(0xFF008832), this.lineLossColor = const Color(0xFFE6190E), this.lineDefaultColor = const Color(0xFFCED0D6), + this.markerTextColor = const Color(0xFFFFFFFF), this.radius = 12.0, this.activeMarkerText = const TextStyle( color: Colors.black, @@ -63,6 +64,9 @@ class MarkerStyle extends ChartPaintingStyle { /// Color of line for its default state. final Color lineDefaultColor; + /// Color of the text displayed for a marker. + final Color markerTextColor; + /// Radius of a single marker. final double radius; From 4bf33876053cdfdeb01880cfc61acca8818c5d84 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:17:46 +0800 Subject: [PATCH 2/2] chore: move text styles to MarkerStyle class --- .../tick_marker_icon_painter.dart | 24 +++++---------- .../theme/painting_styles/marker_style.dart | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart b/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart index 2b0af0ea4..b9df1b342 100644 --- a/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart @@ -314,7 +314,10 @@ class TickMarkerIconPainter extends MarkerGroupIconPainter { ChartTheme theme, double opacity, ) { + // Distance (in pixels) from the start time marker to the right edge of the text label. const double _textPaddingFromStartLine = 16; + + // Gap (in pixels) between the text label and adjacent dashed line segments. const double _textGap = 4; final Color textColor = @@ -324,25 +327,14 @@ class TickMarkerIconPainter extends MarkerGroupIconPainter { ? makeDelimitedTextPainter( text, delimiter: '/', - primaryStyle: TextStyle( - color: textColor, - fontSize: 20, - fontWeight: FontWeight.w700, - ), - secondaryStyle: TextStyle( - color: textColor, - fontSize: 12, - fontWeight: FontWeight.w400, - ), + primaryStyle: theme.markerStyle.markerCounterPrimaryTextStyle + .copyWith(color: textColor), + secondaryStyle: theme.markerStyle.markerCounterSecondaryTextStyle + .copyWith(color: textColor), ) : makeTextPainter( text, - TextStyle( - color: textColor, - fontSize: 12, - fontWeight: FontWeight.w700, - height: 1, - ), + theme.markerStyle.markerPlainTextStyle.copyWith(color: textColor), ); final double textRight = lineEnd.dx - _textPaddingFromStartLine; diff --git a/lib/src/theme/painting_styles/marker_style.dart b/lib/src/theme/painting_styles/marker_style.dart index 7a7758fb7..fcdc5476b 100644 --- a/lib/src/theme/painting_styles/marker_style.dart +++ b/lib/src/theme/painting_styles/marker_style.dart @@ -36,6 +36,22 @@ class MarkerStyle extends ChartPaintingStyle { fontSize: 12, fontWeight: FontWeight.w700, ), + this.markerPlainTextStyle = const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w700, + height: 1, + ), + this.markerCounterPrimaryTextStyle = const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + this.markerCounterSecondaryTextStyle = const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w400, + ), this.startTimeIcon = QuillIcons.stopwatch, this.endTimeIcon = QuillIcons.flag_checkered, }); @@ -93,4 +109,17 @@ class MarkerStyle extends ChartPaintingStyle { /// Style of the marker label. final TextStyle markerLabelTextStyle; + + /// Text style for plain marker text displayed on the connector line. + final TextStyle markerPlainTextStyle; + + /// Text style for the primary (current value) part of a counter marker text. + /// + /// Applied to the portion before the `/` separator (e.g. `"2"` in `"2/10"`). + final TextStyle markerCounterPrimaryTextStyle; + + /// Text style for the secondary (total value) part of a counter marker text. + /// + /// Applied to the `/` separator and the portion after it (e.g. `"/10"` in `"2/10"`). + final TextStyle markerCounterSecondaryTextStyle; }