From 2d8f7b15b29d16f3f3b2d4dc68bbec8fb2151827 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 29 Apr 2025 15:14:58 +0800 Subject: [PATCH 01/32] add structured docs --- doc/README.md | 129 +++++ doc/advanced_usage/custom_themes.md | 459 ++++++++++++++++++ doc/core_concepts/architecture.md | 362 ++++++++++++++ doc/core_concepts/coordinate_system.md | 325 +++++++++++++ doc/core_concepts/data_models.md | 584 ++++++++++++++++++++++ doc/core_concepts/rendering_pipeline.md | 615 ++++++++++++++++++++++++ doc/features/annotations.md | 402 ++++++++++++++++ doc/features/crosshair.md | 327 +++++++++++++ doc/features/drawing_tools/overview.md | 362 ++++++++++++++ doc/features/indicators/overview.md | 370 ++++++++++++++ doc/features/markers.md | 476 ++++++++++++++++++ doc/getting_started/basic_usage.md | 231 +++++++++ doc/getting_started/chart_types.md | 281 +++++++++++ doc/getting_started/configuration.md | 316 ++++++++++++ doc/getting_started/installation.md | 112 +++++ 15 files changed, 5351 insertions(+) create mode 100644 doc/README.md create mode 100644 doc/advanced_usage/custom_themes.md create mode 100644 doc/core_concepts/architecture.md create mode 100644 doc/core_concepts/coordinate_system.md create mode 100644 doc/core_concepts/data_models.md create mode 100644 doc/core_concepts/rendering_pipeline.md create mode 100644 doc/features/annotations.md create mode 100644 doc/features/crosshair.md create mode 100644 doc/features/drawing_tools/overview.md create mode 100644 doc/features/indicators/overview.md create mode 100644 doc/features/markers.md create mode 100644 doc/getting_started/basic_usage.md create mode 100644 doc/getting_started/chart_types.md create mode 100644 doc/getting_started/configuration.md create mode 100644 doc/getting_started/installation.md diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..f1ef46450 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,129 @@ +# Deriv Chart Documentation + +Welcome to the Deriv Chart documentation! This comprehensive guide will help you understand and use the Deriv Chart library effectively, whether you're a user of the library or a contributor to its development. + +## Table of Contents + +- [Getting Started](#getting-started) +- [Core Concepts](#core-concepts) +- [Features](#features) +- [Advanced Usage](#advanced-usage) +- [API Reference](#api-reference) +- [Contributing](#contributing) + +## Getting Started + +If you're new to the Deriv Chart library, start here to learn the basics: + +- [Installation](getting_started/installation.md) - How to install and set up the library +- [Basic Usage](getting_started/basic_usage.md) - Create your first chart +- [Chart Types](getting_started/chart_types.md) - Learn about different chart types +- [Configuration](getting_started/configuration.md) - Understand configuration options + +## Core Concepts + +Understand the fundamental concepts behind the Deriv Chart library: + +- [Architecture](core_concepts/architecture.md) - Overview of the library's architecture +- [Data Models](core_concepts/data_models.md) - Learn about the data structures +- [Coordinate System](core_concepts/coordinate_system.md) - How coordinates are managed +- [Rendering Pipeline](core_concepts/rendering_pipeline.md) - How data is rendered + +## Features + +Explore the features available in the Deriv Chart library: + +### Chart Elements + +- [Annotations](features/annotations.md) - Add horizontal and vertical barriers +- [Markers](features/markers.md) - Highlight specific points +- [Crosshair](features/crosshair.md) - Inspect data with precision + +### Technical Analysis + +- [Indicators](features/indicators/overview.md) - Add technical indicators + - [Moving Averages](features/indicators/moving_averages.md) + - [Oscillators](features/indicators/oscillators.md) + - [Volatility Indicators](features/indicators/volatility.md) + - [Trend Indicators](features/indicators/trend_indicators.md) + +- [Drawing Tools](features/drawing_tools/overview.md) - Use interactive drawing tools + - [Lines and Channels](features/drawing_tools/lines_and_channels.md) + - [Fibonacci Tools](features/drawing_tools/fibonacci_tools.md) + - [Geometric Shapes](features/drawing_tools/geometric_shapes.md) + +### Interactive Layer + +- [Interactive Layer](interactive_layer.md) - Understand the interactive layer architecture + +## Advanced Usage + +Take your charts to the next level with advanced techniques: + +- [Custom Themes](advanced_usage/custom_themes.md) - Create custom chart themes +- [Performance Optimization](advanced_usage/performance_optimization.md) - Optimize chart performance +- [Real-time Data](advanced_usage/real_time_data.md) - Handle real-time data updates +- [Custom Indicators](advanced_usage/custom_indicators.md) - Create your own indicators +- [Custom Drawing Tools](advanced_usage/custom_drawing_tools.md) - Create your own drawing tools + +## API Reference + +Detailed API documentation for the Deriv Chart library: + +- [Chart Widget](api_reference/chart_widget.md) - The main chart widget +- [Series Classes](api_reference/series_classes.md) - Data series classes +- [Indicator Classes](api_reference/indicator_classes.md) - Technical indicator classes +- [Drawing Tool Classes](api_reference/drawing_tool_classes.md) - Drawing tool classes +- [Theme Classes](api_reference/theme_classes.md) - Theme customization classes + +## Contributing + +Learn how to contribute to the Deriv Chart library: + +- [Contribution Guidelines](contribution.md) - How to contribute +- [Development Setup](development_setup.md) - Set up your development environment +- [Code Style](code_style.md) - Follow the code style guidelines +- [Testing](testing.md) - Write and run tests + +## Examples + +The library includes several examples to help you get started: + +### Basic Examples + +- Line Chart +- Candlestick Chart +- OHLC Chart +- Hollow Candlestick Chart + +### Feature Examples + +- Chart with Indicators +- Chart with Drawing Tools +- Chart with Annotations +- Chart with Markers + +### Advanced Examples + +- Real-time Chart +- Custom Theme +- Custom Indicator +- Custom Drawing Tool + +You can find these examples in the `example` directory of the repository. + +## Showcase App + +The `showcase_app` directory contains a complete Flutter application that demonstrates all the features of the Deriv Chart library. You can use this app as a reference for your own implementation. + +## Support + +If you need help with the Deriv Chart library, you can: + +- Check the [FAQ](faq.md) for common questions +- Open an issue on GitHub +- Contact the maintainers + +## License + +The Deriv Chart library is licensed under the [MIT License](../LICENSE). \ No newline at end of file diff --git a/doc/advanced_usage/custom_themes.md b/doc/advanced_usage/custom_themes.md new file mode 100644 index 000000000..f7ffed87b --- /dev/null +++ b/doc/advanced_usage/custom_themes.md @@ -0,0 +1,459 @@ +# Custom Themes + +This document explains how to create and use custom themes in the Deriv Chart library to customize the appearance of your charts. + +## Introduction to Chart Themes + +The Deriv Chart library uses themes to control the visual appearance of charts. Themes define colors, styles, and other visual properties for various chart elements, such as: + +- Background colors +- Grid lines and labels +- Crosshair appearance +- Default series styles +- Annotation styles + +The library provides default light and dark themes, but you can create custom themes to match your application's design. + +## Default Themes + +The Deriv Chart library includes two default themes: + +1. **ChartDefaultLightTheme**: A light theme with a white background and dark text +2. **ChartDefaultDarkTheme**: A dark theme with a dark background and light text + +By default, the chart automatically uses the appropriate theme based on the application's brightness: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + // Theme is automatically selected based on Theme.of(context).brightness +) +``` + +You can explicitly specify a theme: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + theme: ChartDefaultDarkTheme(), +) +``` + +## Creating a Custom Theme + +There are two approaches to creating a custom theme: + +1. **Extending a default theme**: Override specific properties of a default theme +2. **Implementing the ChartTheme interface**: Create a completely custom theme + +### Extending a Default Theme + +The easiest way to create a custom theme is to extend one of the default themes and override specific properties: + +```dart +class CustomDarkTheme extends ChartDefaultDarkTheme { + @override + GridStyle get gridStyle => GridStyle( + gridLineColor: Colors.yellow, + xLabelStyle: textStyle( + textStyle: caption2, + color: Colors.yellow, + fontSize: 13, + ), + yLabelStyle: textStyle( + textStyle: caption2, + color: Colors.yellow, + fontSize: 13, + ), + ); + + @override + CrosshairStyle get crosshairStyle => CrosshairStyle( + lineColor: Colors.orange, + labelBackgroundColor: Colors.orange.withOpacity(0.8), + labelTextStyle: textStyle( + textStyle: caption1, + color: Colors.white, + ), + ); +} +``` + +### Implementing the ChartTheme Interface + +For complete control, you can implement the `ChartTheme` interface: + +```dart +class CompletelyCustomTheme implements ChartTheme { + @override + Color get backgroundColor => Colors.black; + + @override + GridStyle get gridStyle => GridStyle( + gridLineColor: Colors.grey[800]!, + xLabelStyle: TextStyle( + color: Colors.grey[400], + fontSize: 12, + ), + yLabelStyle: TextStyle( + color: Colors.grey[400], + fontSize: 12, + ), + ); + + @override + CrosshairStyle get crosshairStyle => CrosshairStyle( + lineColor: Colors.white, + labelBackgroundColor: Colors.white.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.black, + fontSize: 12, + ), + ); + + @override + LineStyle get defaultLineStyle => LineStyle( + color: Colors.blue, + thickness: 2, + ); + + @override + CandleStyle get defaultCandleStyle => CandleStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + wickWidth: 1, + bodyWidth: 8, + ); + + @override + OHLCStyle get defaultOHLCStyle => OHLCStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + thickness: 1, + width: 8, + ); + + @override + HollowCandleStyle get defaultHollowCandleStyle => HollowCandleStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + wickWidth: 1, + bodyWidth: 8, + hollowPositiveColor: Colors.transparent, + ); + + @override + HorizontalBarrierStyle get defaultHorizontalBarrierStyle => HorizontalBarrierStyle( + color: Colors.purple, + isDashed: true, + lineThickness: 1, + labelBackgroundColor: Colors.purple.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ); + + @override + VerticalBarrierStyle get defaultVerticalBarrierStyle => VerticalBarrierStyle( + color: Colors.orange, + isDashed: true, + lineThickness: 1, + labelBackgroundColor: Colors.orange.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ); + + // Implement all other required properties... + + @override + TextStyle textStyle({ + required TextStyle textStyle, + required Color color, + double? fontSize, + FontWeight? fontWeight, + }) { + return textStyle.copyWith( + color: color, + fontSize: fontSize, + fontWeight: fontWeight, + ); + } +} +``` + +## Theme Properties + +The `ChartTheme` interface defines numerous properties that control the appearance of different chart elements: + +### Core Properties + +- `backgroundColor`: The background color of the chart +- `gridStyle`: The style of the grid lines and labels +- `crosshairStyle`: The style of the crosshair + +### Series Styles + +- `defaultLineStyle`: The default style for line series +- `defaultCandleStyle`: The default style for candle series +- `defaultOHLCStyle`: The default style for OHLC series +- `defaultHollowCandleStyle`: The default style for hollow candle series + +### Annotation Styles + +- `defaultHorizontalBarrierStyle`: The default style for horizontal barriers +- `defaultVerticalBarrierStyle`: The default style for vertical barriers +- `defaultTickIndicatorStyle`: The default style for tick indicators + +### Marker Styles + +- `defaultMarkerStyle`: The default style for markers +- `defaultActiveMarkerStyle`: The default style for active markers +- `defaultEntryTickStyle`: The default style for entry tick markers +- `defaultExitTickStyle`: The default style for exit tick markers + +## Style Classes + +The Deriv Chart library provides several style classes that define the appearance of specific chart elements: + +### GridStyle + +Controls the appearance of grid lines and labels: + +```dart +GridStyle( + gridLineColor: Colors.grey[300]!, + xLabelStyle: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + yLabelStyle: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), +) +``` + +### CrosshairStyle + +Controls the appearance of the crosshair: + +```dart +CrosshairStyle( + lineColor: Colors.blue, + lineThickness: 1, + lineDashPattern: [5, 5], // Dashed line + labelBackgroundColor: Colors.blue.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + ), + labelPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + labelBorderRadius: 4, +) +``` + +### LineStyle + +Controls the appearance of line series: + +```dart +LineStyle( + color: Colors.blue, + thickness: 2, + isDashed: false, + dashPattern: [5, 5], +) +``` + +### CandleStyle + +Controls the appearance of candle series: + +```dart +CandleStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + wickWidth: 1, + bodyWidth: 8, +) +``` + +### OHLCStyle + +Controls the appearance of OHLC series: + +```dart +OHLCStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + thickness: 1, + width: 8, +) +``` + +### HorizontalBarrierStyle + +Controls the appearance of horizontal barriers: + +```dart +HorizontalBarrierStyle( + color: Colors.purple, + isDashed: true, + lineThickness: 1, + labelBackgroundColor: Colors.purple.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + ), +) +``` + +## Using Custom Themes + +To use a custom theme, pass it to the `theme` parameter of the `Chart` widget: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + theme: CustomDarkTheme(), +) +``` + +## Dynamic Theme Switching + +You can dynamically switch between themes based on user preferences or system settings: + +```dart +class ThemeSwitcherExample extends StatefulWidget { + final List ticks; + + const ThemeSwitcherExample({ + Key? key, + required this.ticks, + }) : super(key: key); + + @override + State createState() => _ThemeSwitcherExampleState(); +} + +class _ThemeSwitcherExampleState extends State { + bool _isDarkMode = true; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Light'), + Switch( + value: _isDarkMode, + onChanged: (value) { + setState(() { + _isDarkMode = value; + }); + }, + ), + Text('Dark'), + ], + ), + Expanded( + child: Chart( + mainSeries: LineSeries(widget.ticks), + pipSize: 2, + theme: _isDarkMode ? ChartDefaultDarkTheme() : ChartDefaultLightTheme(), + ), + ), + ], + ); + } +} +``` + +## Theme Presets + +You can create multiple theme presets for users to choose from: + +```dart +enum ChartThemePreset { + defaultLight, + defaultDark, + blueTheme, + greenTheme, + highContrast, +} + +ChartTheme getThemeForPreset(ChartThemePreset preset) { + switch (preset) { + case ChartThemePreset.defaultLight: + return ChartDefaultLightTheme(); + case ChartThemePreset.defaultDark: + return ChartDefaultDarkTheme(); + case ChartThemePreset.blueTheme: + return BlueTheme(); + case ChartThemePreset.greenTheme: + return GreenTheme(); + case ChartThemePreset.highContrast: + return HighContrastTheme(); + } +} + +class BlueTheme extends ChartDefaultDarkTheme { + @override + Color get backgroundColor => Color(0xFF0A1929); + + @override + GridStyle get gridStyle => GridStyle( + gridLineColor: Color(0xFF1E3A5F), + xLabelStyle: textStyle( + textStyle: caption2, + color: Color(0xFF90CAF9), + fontSize: 13, + ), + yLabelStyle: textStyle( + textStyle: caption2, + color: Color(0xFF90CAF9), + fontSize: 13, + ), + ); + + @override + LineStyle get defaultLineStyle => LineStyle( + color: Color(0xFF2196F3), + thickness: 2, + ); + + @override + CandleStyle get defaultCandleStyle => CandleStyle( + positiveColor: Color(0xFF4CAF50), + negativeColor: Color(0xFFF44336), + wickWidth: 1, + bodyWidth: 8, + ); +} +``` + +## Best Practices + +When creating custom themes, consider the following best practices: + +1. **Maintain consistency**: Use a consistent color palette and style across all chart elements +2. **Consider accessibility**: Ensure sufficient contrast between text and background colors +3. **Test with different data**: Test your theme with different types of data to ensure it works well in all scenarios +4. **Provide theme options**: Give users the ability to choose between different themes +5. **Consider dark mode**: Provide both light and dark themes for your charts + +## Next Steps + +Now that you understand how to create and use custom themes in the Deriv Chart library, you can explore: + +- [Performance Optimization](performance_optimization.md) - Learn how to optimize chart performance +- [Real-time Data](real_time_data.md) - Understand how to handle real-time data updates +- [API Reference](../api_reference/chart_widget.md) - Explore the complete API \ No newline at end of file diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md new file mode 100644 index 000000000..a705f5e96 --- /dev/null +++ b/doc/core_concepts/architecture.md @@ -0,0 +1,362 @@ +# Chart Architecture + +This document provides a comprehensive overview of the Deriv Chart library's architecture, explaining the key components and how they interact with each other. + +## High-Level Architecture + +The Deriv Chart library follows a layered architecture with clear separation of concerns: + +``` +┌─────────────────────────┐ +│ XAxisWrapper │ +│ ┌───────────────────┐ │ +│ │ GestureManager │ │ +│ │ ┌─────────────┐ │ │ +│ │ │ Chart │ │ │ +│ │ │ │ │ │ +│ │ └─────────────┘ │ │ +│ └───────────────────┘ │ +└─────────────────────────┘ +``` + +### Key Components + +1. **XAxisWrapper**: The outermost layer that: + - Provides platform-specific X-axis implementations (web/mobile) + - Manages chart data entries and live data state + - Handles data fit mode and zoom level (msPerPx) + - Controls scroll animation and visible area changes + +2. **GestureManager**: The middle layer that: + - Handles user interactions (pan, zoom, tap) + - Manages gesture states and animations + - Controls chart navigation and interaction behavior + +3. **Chart**: The core component that: + - Contains MainChart and optional BottomCharts + - Coordinates shared X-axis between charts + - Manages Y-axis for each chart section + - Renders data visualization + +This layered structure ensures: +- Clear separation of concerns +- Platform-specific adaptations +- Consistent user interaction handling +- Coordinated data visualization + +## Chart Structure + +The Chart widget has a vertical structure with multiple chart areas: + +``` +┌─────────────────────────┐ +│ Chart │ +│ │ +│ ┌───────────────────┐ │ +│ │ MainChart │ │ +│ │ (Main Data) │ │ +│ └───────────────────┘ │ +│ │ +│ ┌───────────────────┐ │ +│ │ BottomCharts │ │ +│ │(Bottom Indicators)│ │ +│ └───────────────────┘ │ +│ ... │ +│ ... │ +│ ... │ +└─────────────────────────┘ +``` + +### MainChart + +The MainChart is the primary chart area that: +- Displays market data (line, candlestick charts) +- Shows overlay indicators (like Moving Average) +- Supports drawing tools for technical analysis +- Displays crosshair for price/time inspection +- Renders visual elements like barriers and markers + +### BottomCharts + +BottomCharts are additional chart areas that: +- Display separate indicator charts (like RSI, MACD) +- Have independent Y-axis scaling +- Share the same X-axis viewport with MainChart +- Can be added/removed dynamically + +## Widget Hierarchy + +The chart library implements a hierarchical structure of chart widgets: + +``` +┌─────────────────────┐ +│ BasicChart │ +│ (Base Features) │ +└─────────┬─────────┬─┘ + │ │ + ▼ ▼ + ┌─────────┐ ┌─────────┐ + │MainChart│ │BottomChart + └─────────┘ └─────────┘ +``` + +### BasicChart + +BasicChart serves as the foundation for all chart widgets, providing: +- Single MainSeries for data visualization +- Y-axis range management +- Coordinate conversion functions +- Y-axis scaling and animations +- Grid lines and labels +- User interactions for Y-axis scaling + +### MainChart + +MainChart extends BasicChart to create the primary chart area by adding: +- Support for multiple ChartData types +- Crosshair functionality +- Drawing tools +- Overlay indicators + +### BottomChart + +BottomChart extends BasicChart to create secondary chart areas that: +- Display technical indicators with independent Y-axis scaling +- Maintain separate Y-axis ranges while sharing X-axis viewport +- Support dynamic addition/removal of indicators +- Sync zooming and scrolling with MainChart + +## Coordinate System + +The chart uses a coordinate system based on time (X-axis) and price (Y-axis): + +### X-Axis Coordinates + +The X-axis is managed by the XAxisWrapper and uses: +- **rightBoundEpoch**: The timestamp at the right edge of the chart +- **msPerPx**: Milliseconds per pixel (zoom level) +- **leftBoundEpoch**: Calculated as `rightBoundEpoch - screenWidth * msPerPx` + +### Y-Axis Coordinates + +The Y-axis is managed by each chart (MainChart and BottomCharts) and uses: +- **topBoundQuote**: The maximum price in the visible area +- **bottomBoundQuote**: The minimum price in the visible area +- **quotePerPx**: Price units per pixel + +### Coordinate Conversion + +The chart provides conversion functions: +- `xFromEpoch`: Converts timestamp to X-coordinate +- `yFromQuote`: Converts price to Y-coordinate +- `epochFromX`: Converts X-coordinate to timestamp +- `quoteFromY`: Converts Y-coordinate to price + +These functions enable plotting any data point on the canvas and handling user interactions. + +## Data Visualization + +The chart library uses a flexible data visualization system: + +``` +┌─────────────┐ +│ ChartData │ +└──────┬──────┘ + │ + ▼ +┌─────────────┐ +│ Series │ +└──────┬──────┘ + │ + ▼ +┌─────────────┐ +│ DataSeries │ +└──────┬──────┘ + │ + ├─────────────┬─────────────┬─────────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────┐┌─────────────┐┌─────────────┐┌─────────────┐ +│ LineSeries ││CandleSeries ││ OHLCSeries ││ Indicators │ +└─────────────┘└─────────────┘└─────────────┘└─────────────┘ +``` + +### ChartData + +ChartData is an abstract class representing any data that can be displayed on the chart, including: +- Series data (lines, candles) +- Annotations (barriers) +- Markers + +### Series + +Series is the base class for all chart series, handling: +- Data management +- Range calculation +- Painter creation + +### DataSeries + +DataSeries extends Series to handle sequential data with: +- Sorted data management +- Visible data calculation +- Min/max value determination + +### Specific Series Types + +- **LineSeries**: Displays line charts from tick data +- **CandleSeries**: Displays candlestick charts from OHLC data +- **OHLCSeries**: Displays OHLC charts from OHLC data +- **Indicator Series**: Displays technical indicators + +## Painter System + +The chart uses a custom painting system to render data: + +``` +┌─────────────────┐ +│ SeriesPainter │ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ DataPainter │ +└────────┬────────┘ + │ + ├─────────────┬─────────────┬─────────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────┐┌─────────────┐┌─────────────┐┌─────────────┐ +│ LinePainter ││CandlePainter││OHLCPainter ││ScatterPainter +└─────────────┘└─────────────┘└─────────────┘└─────────────┘ +``` + +### SeriesPainter + +SeriesPainter is an abstract class responsible for painting Series data on the canvas. + +### DataPainter + +DataPainter extends SeriesPainter to provide common painting functionality for DataSeries. + +### Specific Painters + +- **LinePainter**: Paints line data +- **CandlePainter**: Paints candlestick data +- **OHLCPainter**: Paints OHLC data +- **ScatterPainter**: Paints scatter plot data + +## Interactive Layer + +The Interactive Layer manages user interactions with drawing tools: + +``` +┌─────────────────────────┐ +│ Interactive Layer │ +└─────────────┬───────────┘ + │ + ▼ +┌─────────────────────────┐ +│ InteractiveState │ +└─────────────┬───────────┘ + │ + ├─────────────────┬─────────────────┬─────────────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────────┐┌─────────────────┐┌─────────────────┐┌─────────────────┐ +│ NormalState ││SelectedToolState││ AddingToolState ││ HoverState │ +└─────────────────┘└─────────────────┘└─────────────────┘└─────────────────┘ +``` + +### InteractiveState + +InteractiveState defines the current mode of interaction with the chart: + +- **NormalState**: Default state when no tools are selected +- **SelectedToolState**: Active when a drawing tool is selected +- **AddingToolState**: Active when a new drawing tool is being created +- **HoverState**: A mixin that provides hover functionality + +### Drawing Tool States + +Each drawing tool has its own state: + +- **normal**: Default state +- **selected**: Tool is selected +- **hovered**: Pointer is hovering over the tool +- **adding**: Tool is being created +- **dragging**: Tool is being moved + +## Theme System + +The chart library includes a theming system: + +``` +┌─────────────────┐ +│ ChartTheme │ +└────────┬────────┘ + │ + ├─────────────────┬─────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐┌─────────────────┐┌─────────────────┐ +│DefaultDarkTheme ││DefaultLightTheme││ CustomTheme │ +└─────────────────┘└─────────────────┘└─────────────────┘ +``` + +### ChartTheme + +ChartTheme is an interface defining all themeable aspects of the chart: + +- Grid style +- Axis style +- Crosshair style +- Series styles +- Annotation styles +- Background colors + +### Default Themes + +- **ChartDefaultDarkTheme**: Default dark theme +- **ChartDefaultLightTheme**: Default light theme + +### Custom Themes + +Users can create custom themes by: +- Implementing the ChartTheme interface +- Extending one of the default themes and overriding specific properties + +## DerivChart + +DerivChart is a wrapper around the Chart widget that: +- Provides UI for adding/removing indicators and drawing tools +- Manages saving/restoring selected indicators and tools +- Handles indicator and drawing tool configurations + +``` +┌─────────────────────────┐ +│ DerivChart │ +│ ┌───────────────────┐ │ +│ │ Chart │ │ +│ └───────────────────┘ │ +│ │ +│ ┌───────────────────┐ │ +│ │ AddOnsRepository │ │ +│ └───────────────────┘ │ +└─────────────────────────┘ +``` + +### AddOnsRepository + +AddOnsRepository is a ChangeNotifier that: +- Manages a list of add-ons (indicators or drawing tools) +- Handles saving/loading from SharedPreferences +- Provides methods for adding, removing, and updating add-ons + +## Next Steps + +Now that you understand the architecture of the Deriv Chart library, you can explore: + +- [Data Models](data_models.md) - Learn about the data structures used in the chart +- [Coordinate System](coordinate_system.md) - Understand how coordinates are managed +- [Rendering Pipeline](rendering_pipeline.md) - Explore how data is rendered on the canvas \ No newline at end of file diff --git a/doc/core_concepts/coordinate_system.md b/doc/core_concepts/coordinate_system.md new file mode 100644 index 000000000..c77fbd211 --- /dev/null +++ b/doc/core_concepts/coordinate_system.md @@ -0,0 +1,325 @@ +# Coordinate System + +This document explains the coordinate system used in the Deriv Chart library, how it maps data points to screen coordinates, and how it handles scrolling and zooming. + +## Overview + +The Deriv Chart library uses a coordinate system that maps: +- Time (epochs) to X-coordinates on the screen +- Price (quotes) to Y-coordinates on the screen + +This mapping is essential for: +- Rendering data points at the correct positions +- Handling user interactions (taps, drags) +- Implementing scrolling and zooming +- Supporting crosshair functionality + +## X-Axis Coordinate System + +The X-axis represents time and is managed by the `XAxisWrapper` component. + +### Key Concepts + +1. **rightBoundEpoch**: The timestamp at the right edge of the chart +2. **msPerPx**: Milliseconds per pixel (zoom level) +3. **leftBoundEpoch**: The timestamp at the left edge of the chart + +### Calculations + +The relationship between these values is: + +``` +leftBoundEpoch = rightBoundEpoch - screenWidth * msPerPx +``` + +Where: +- `screenWidth` is the width of the chart in pixels +- `msPerPx` determines how many milliseconds each pixel represents (zoom level) + +### Coordinate Conversion + +The `XAxisModel` provides functions to convert between epochs and X-coordinates: + +```dart +// Convert epoch to X-coordinate +double xFromEpoch(DateTime epoch) { + return width - (rightBoundEpoch - epoch.millisecondsSinceEpoch) / msPerPx; +} + +// Convert X-coordinate to epoch +DateTime epochFromX(double x) { + return DateTime.fromMillisecondsSinceEpoch( + rightBoundEpoch - ((width - x) * msPerPx).toInt(), + ); +} +``` + +### Scrolling + +Scrolling is implemented by changing the `rightBoundEpoch`: +- Scrolling right (into the past): Decrease `rightBoundEpoch` +- Scrolling left (into the future): Increase `rightBoundEpoch` + +```dart +void scrollBy(double pixels) { + rightBoundEpoch -= (pixels * msPerPx).toInt(); + notifyListeners(); +} +``` + +### Zooming + +Zooming is implemented by changing the `msPerPx`: +- Zooming in: Decrease `msPerPx` (fewer milliseconds per pixel) +- Zooming out: Increase `msPerPx` (more milliseconds per pixel) + +```dart +void scale(double newMsPerPx) { + // Calculate the center point of the visible area + final centerEpoch = (rightBoundEpoch + leftBoundEpoch) ~/ 2; + + // Update msPerPx + msPerPx = newMsPerPx; + + // Recalculate rightBoundEpoch to keep the center point fixed + rightBoundEpoch = centerEpoch + (width * msPerPx / 2).toInt(); + + notifyListeners(); +} +``` + +## Y-Axis Coordinate System + +The Y-axis represents price and is managed by each chart component (MainChart and BottomCharts) independently. + +### Key Concepts + +1. **topBoundQuote**: The maximum price in the visible area +2. **bottomBoundQuote**: The minimum price in the visible area +3. **topPadding** and **bottomPadding**: Padding to add above and below the data +4. **quotePerPx**: Price units per pixel + +### Calculations + +The relationship between these values is: + +``` +quotePerPx = (topBoundQuote - bottomBoundQuote) / (height - topPadding - bottomPadding) +``` + +Where: +- `height` is the height of the chart in pixels +- `topPadding` and `bottomPadding` are the padding values in pixels + +### Coordinate Conversion + +The `BasicChart` provides functions to convert between quotes and Y-coordinates: + +```dart +// Convert quote to Y-coordinate +double yFromQuote(double quote) { + return height - bottomPadding - (quote - bottomBoundQuote) / quotePerPx; +} + +// Convert Y-coordinate to quote +double quoteFromY(double y) { + return bottomBoundQuote + (height - bottomPadding - y) * quotePerPx; +} +``` + +### Y-Axis Scaling + +The Y-axis scale is determined by: +1. Finding the minimum and maximum values in the visible data +2. Adding padding to ensure data doesn't touch the edges +3. Adjusting for a consistent scale when animating + +```dart +void updateYAxisBounds() { + // Find min and max values in visible data + double minValue = double.infinity; + double maxValue = -double.infinity; + + for (final series in allSeries) { + if (!series.minValue.isNaN && series.minValue < minValue) { + minValue = series.minValue; + } + if (!series.maxValue.isNaN && series.maxValue > maxValue) { + maxValue = series.maxValue; + } + } + + // Add padding + final range = maxValue - minValue; + final paddingAmount = range * 0.1; // 10% padding + + topBoundQuote = maxValue + paddingAmount; + bottomBoundQuote = minValue - paddingAmount; + + // Update quotePerPx + quotePerPx = (topBoundQuote - bottomBoundQuote) / (height - topPadding - bottomPadding); +} +``` + +## Grid System + +The grid system uses the coordinate system to place grid lines and labels at appropriate intervals. + +### X-Axis Grid + +The X-axis grid is determined by: +1. Calculating the appropriate time interval based on the zoom level +2. Generating timestamps at regular intervals between `leftBoundEpoch` and `rightBoundEpoch` +3. Converting these timestamps to X-coordinates + +```dart +List gridTimestamps() { + final interval = timeGridInterval(); + final result = []; + + // Start from the leftmost visible timestamp aligned to the interval + DateTime current = alignToInterval( + DateTime.fromMillisecondsSinceEpoch(leftBoundEpoch), + interval, + ); + + // Generate timestamps until we reach the right edge + while (current.millisecondsSinceEpoch <= rightBoundEpoch) { + result.add(current); + current = addInterval(current, interval); + } + + return result; +} +``` + +### Y-Axis Grid + +The Y-axis grid is determined by: +1. Calculating the appropriate price interval based on the visible range +2. Generating price values at regular intervals between `bottomBoundQuote` and `topBoundQuote` +3. Converting these price values to Y-coordinates + +```dart +List gridQuotes() { + final interval = quoteGridInterval(); + final result = []; + + // Start from the bottom aligned to the interval + double current = (bottomBoundQuote / interval).floor() * interval; + + // Generate quotes until we reach the top + while (current <= topBoundQuote) { + result.add(current); + current += interval; + } + + return result; +} +``` + +## Coordinate System in Action + +### Plotting Data Points + +To plot a data point (epoch, quote) on the canvas: + +```dart +void plotPoint(Canvas canvas, DateTime epoch, double quote) { + final x = xFromEpoch(epoch); + final y = yFromQuote(quote); + + canvas.drawCircle(Offset(x, y), 3, Paint()..color = Colors.blue); +} +``` + +### Drawing Lines + +To draw a line between two data points: + +```dart +void drawLine(Canvas canvas, DateTime epoch1, double quote1, DateTime epoch2, double quote2) { + final x1 = xFromEpoch(epoch1); + final y1 = yFromQuote(quote1); + final x2 = xFromEpoch(epoch2); + final y2 = yFromQuote(quote2); + + canvas.drawLine( + Offset(x1, y1), + Offset(x2, y2), + Paint() + ..color = Colors.blue + ..strokeWidth = 2, + ); +} +``` + +### Drawing Candles + +To draw a candle: + +```dart +void drawCandle(Canvas canvas, Candle candle) { + final x = xFromEpoch(candle.epoch); + final yOpen = yFromQuote(candle.open); + final yHigh = yFromQuote(candle.high); + final yLow = yFromQuote(candle.low); + final yClose = yFromQuote(candle.close); + + // Draw wick + canvas.drawLine( + Offset(x, yHigh), + Offset(x, yLow), + Paint() + ..color = Colors.black + ..strokeWidth = 1, + ); + + // Draw body + final bodyPaint = Paint() + ..color = candle.isBullish ? Colors.green : Colors.red; + + canvas.drawRect( + Rect.fromPoints( + Offset(x - 4, yOpen), + Offset(x + 4, yClose), + ), + bodyPaint, + ); +} +``` + +### Handling User Interactions + +To convert a user tap to a data point: + +```dart +void onTap(TapDownDetails details) { + final x = details.localPosition.dx; + final y = details.localPosition.dy; + + final epoch = epochFromX(x); + final quote = quoteFromY(y); + + print('Tapped at epoch: $epoch, quote: $quote'); +} +``` + +## Shared X-Axis, Independent Y-Axes + +The Deriv Chart library uses a shared X-axis for all charts (MainChart and BottomCharts) but independent Y-axes: + +1. The `XAxisWrapper` provides a single `XAxisModel` that is shared by all charts +2. Each chart (MainChart and BottomCharts) has its own Y-axis calculations + +This allows: +- Synchronized scrolling and zooming across all charts +- Independent Y-axis scaling for each chart based on its data range + +## Next Steps + +Now that you understand the coordinate system used in the Deriv Chart library, you can explore: + +- [Rendering Pipeline](rendering_pipeline.md) - Learn how data is rendered on the canvas +- [Architecture](architecture.md) - Understand the overall architecture of the library +- [API Reference](../api_reference/chart_widget.md) - Explore the complete API \ No newline at end of file diff --git a/doc/core_concepts/data_models.md b/doc/core_concepts/data_models.md new file mode 100644 index 000000000..5181ab670 --- /dev/null +++ b/doc/core_concepts/data_models.md @@ -0,0 +1,584 @@ +# Data Models + +This document explains the key data models used in the Deriv Chart library, their properties, and how they relate to each other. + +## Overview + +The Deriv Chart library uses several data models to represent financial data and chart elements: + +1. **Market Data Models**: Represent financial market data (Tick, Candle) +2. **Series Models**: Represent data series for visualization (LineSeries, CandleSeries) +3. **Annotation Models**: Represent chart annotations (Barriers, TickIndicator) +4. **Marker Models**: Represent point markers on the chart +5. **Indicator Models**: Represent technical indicators + +## Market Data Models + +### Tick + +The `Tick` class represents a single price point at a specific time: + +```dart +class Tick { + /// The timestamp of the tick + final DateTime epoch; + + /// The price value + final double quote; + + Tick({ + required this.epoch, + required this.quote, + }); +} +``` + +Ticks are used for: +- Line charts +- Real-time price updates +- Entry/exit points +- Tick indicators + +### Candle (OHLC) + +The `Candle` class represents price movement over a time period with open, high, low, and close values: + +```dart +class Candle { + /// The timestamp of the candle (typically the opening time) + final DateTime epoch; + + /// The opening price + final double open; + + /// The highest price during the period + final double high; + + /// The lowest price during the period + final double low; + + /// The closing price + final double close; + + Candle({ + required this.epoch, + required this.open, + required this.high, + required this.low, + required this.close, + }); + + /// Whether the candle is bullish (close > open) + bool get isBullish => close > open; + + /// Whether the candle is bearish (close < open) + bool get isBearish => close < open; +} +``` + +Candles are used for: +- Candlestick charts +- OHLC charts +- Hollow candlestick charts +- Technical indicators + +## Series Models + +### Series + +`Series` is the base class for all chart series: + +```dart +abstract class Series extends ChartData { + /// Creates a painter for this series + SeriesPainter createPainter(); + + /// The minimum value in the visible range + double get minValue; + + /// The maximum value in the visible range + double get maxValue; + + /// Updates the visible range + void updateVisibleRange(int leftEpoch, int rightEpoch); +} +``` + +### DataSeries + +`DataSeries` extends `Series` to handle sequential data: + +```dart +abstract class DataSeries extends Series { + /// The list of data points + final List data; + + /// The visible data points + List visibleData = []; + + DataSeries(this.data); + + /// Updates the visible data based on the visible range + @override + void updateVisibleRange(int leftEpoch, int rightEpoch) { + visibleData = _getVisibleData(leftEpoch, rightEpoch); + } + + /// Gets the data points within the visible range + List _getVisibleData(int leftEpoch, int rightEpoch); +} +``` + +### LineSeries + +`LineSeries` represents a line chart: + +```dart +class LineSeries extends DataSeries { + /// The style of the line + final LineStyle style; + + LineSeries( + List ticks, { + this.style = const LineStyle(), + }) : super(ticks); + + @override + SeriesPainter createPainter() => LinePainter(this); +} +``` + +### CandleSeries + +`CandleSeries` represents a candlestick chart: + +```dart +class CandleSeries extends DataSeries { + /// The style of the candles + final CandleStyle style; + + CandleSeries( + List candles, { + this.style = const CandleStyle(), + }) : super(candles); + + @override + SeriesPainter createPainter() => CandlePainter(this); +} +``` + +### OHLCSeries + +`OHLCSeries` represents an OHLC chart: + +```dart +class OHLCSeries extends DataSeries { + /// The style of the OHLC bars + final OHLCStyle style; + + OHLCSeries( + List candles, { + this.style = const OHLCStyle(), + }) : super(candles); + + @override + SeriesPainter createPainter() => OHLCPainter(this); +} +``` + +### HollowCandleSeries + +`HollowCandleSeries` represents a hollow candlestick chart: + +```dart +class HollowCandleSeries extends DataSeries { + /// The style of the hollow candles + final HollowCandleStyle style; + + HollowCandleSeries( + List candles, { + this.style = const HollowCandleStyle(), + }) : super(candles); + + @override + SeriesPainter createPainter() => HollowCandlePainter(this); +} +``` + +## Annotation Models + +### ChartAnnotation + +`ChartAnnotation` is the base class for all chart annotations: + +```dart +abstract class ChartAnnotation extends Series { + /// The unique identifier of the annotation + final String id; + + ChartAnnotation({String? id}) : id = id ?? uuid.v4(); + + /// Creates a chart object for this annotation + ChartObject createObject(); +} +``` + +### Barrier + +`Barrier` is the base class for horizontal and vertical barriers: + +```dart +abstract class Barrier extends ChartAnnotation { + /// The title of the barrier + final String? title; + + Barrier({ + this.title, + super.id, + }); +} +``` + +### HorizontalBarrier + +`HorizontalBarrier` represents a horizontal line at a specific price level: + +```dart +class HorizontalBarrier extends Barrier { + /// The price level of the barrier + final double value; + + /// The style of the barrier + final HorizontalBarrierStyle style; + + /// The visibility behavior of the barrier + final HorizontalBarrierVisibility visibility; + + HorizontalBarrier( + this.value, { + super.title, + super.id, + this.style = const HorizontalBarrierStyle(), + this.visibility = HorizontalBarrierVisibility.normal, + }); + + @override + ChartObject createObject() => BarrierObject( + value: value, + epoch: null, + title: title, + ); + + @override + SeriesPainter createPainter() => HorizontalBarrierPainter(this); +} +``` + +### VerticalBarrier + +`VerticalBarrier` represents a vertical line at a specific timestamp: + +```dart +class VerticalBarrier extends Barrier { + /// The timestamp of the barrier + final DateTime epoch; + + /// The style of the barrier + final VerticalBarrierStyle style; + + VerticalBarrier( + this.epoch, { + super.title, + super.id, + this.style = const VerticalBarrierStyle(), + }); + + @override + ChartObject createObject() => BarrierObject( + value: null, + epoch: epoch, + title: title, + ); + + @override + SeriesPainter createPainter() => VerticalBarrierPainter(this); +} +``` + +### TickIndicator + +`TickIndicator` is a special type of horizontal barrier that represents a specific tick: + +```dart +class TickIndicator extends HorizontalBarrier { + /// The tick being indicated + final Tick tick; + + TickIndicator( + this.tick, { + super.id, + super.style = const HorizontalBarrierStyle(), + super.visibility = HorizontalBarrierVisibility.forceToStayOnRange, + }) : super( + tick.quote, + title: tick.quote.toString(), + ); +} +``` + +## Marker Models + +### MarkerSeries + +`MarkerSeries` represents a collection of markers on the chart: + +```dart +class MarkerSeries extends Series { + /// The list of markers + final List markers; + + /// The active marker (highlighted) + final ActiveMarker? activeMarker; + + /// The entry tick marker + final Tick? entryTick; + + /// The exit tick marker + final Tick? exitTick; + + MarkerSeries( + this.markers, { + this.activeMarker, + this.entryTick, + this.exitTick, + }); + + @override + SeriesPainter createPainter() => MarkerPainter(this); +} +``` + +### Marker + +`Marker` represents a point marker on the chart: + +```dart +class Marker { + /// The timestamp of the marker + final DateTime epoch; + + /// The price level of the marker + final double quote; + + /// The type of marker (up, down, neutral) + final MarkerType type; + + /// Callback when the marker is tapped + final VoidCallback? onTap; + + Marker({ + required this.epoch, + required this.quote, + required this.type, + this.onTap, + }); + + /// Creates an up marker + factory Marker.up({ + required DateTime epoch, + required double quote, + VoidCallback? onTap, + }) => Marker( + epoch: epoch, + quote: quote, + type: MarkerType.up, + onTap: onTap, + ); + + /// Creates a down marker + factory Marker.down({ + required DateTime epoch, + required double quote, + VoidCallback? onTap, + }) => Marker( + epoch: epoch, + quote: quote, + type: MarkerType.down, + onTap: onTap, + ); +} +``` + +### ActiveMarker + +`ActiveMarker` represents a highlighted marker on the chart: + +```dart +class ActiveMarker { + /// The timestamp of the marker + final DateTime epoch; + + /// The price level of the marker + final double quote; + + /// Callback when the marker is tapped + final VoidCallback? onTap; + + /// Callback when the user taps outside the marker + final VoidCallback? onOutsideTap; + + ActiveMarker({ + required this.epoch, + required this.quote, + this.onTap, + this.onOutsideTap, + }); +} +``` + +## Indicator Models + +### IndicatorConfig + +`IndicatorConfig` is the base class for all indicator configurations: + +```dart +abstract class IndicatorConfig { + /// The unique identifier of the indicator + final String id; + + /// The name of the indicator + String get name; + + /// Whether the indicator is displayed on the main chart or in a separate chart + bool get isOverlay; + + IndicatorConfig({String? id}) : id = id ?? uuid.v4(); + + /// Creates a series for this indicator + Series createSeries(List candles); + + /// Converts the config to JSON for storage + Map toJson(); + + /// Creates a config from JSON + factory IndicatorConfig.fromJson(Map json); +} +``` + +### Moving Average Indicator + +Example of a specific indicator configuration: + +```dart +class MAIndicatorConfig extends IndicatorConfig { + /// The period of the moving average + final int period; + + /// The type of moving average + final MovingAverageType type; + + /// The style of the line + final LineStyle lineStyle; + + MAIndicatorConfig({ + required this.period, + this.type = MovingAverageType.simple, + this.lineStyle = const LineStyle(), + super.id, + }); + + @override + String get name => '$type MA ($period)'; + + @override + bool get isOverlay => true; + + @override + Series createSeries(List candles) => MASeries( + candles, + period: period, + type: type, + lineStyle: lineStyle, + ); + + @override + Map toJson() => { + 'id': id, + 'type': 'MA', + 'period': period, + 'maType': type.index, + 'lineStyle': lineStyle.toJson(), + }; + + factory MAIndicatorConfig.fromJson(Map json) => MAIndicatorConfig( + id: json['id'], + period: json['period'], + type: MovingAverageType.values[json['maType']], + lineStyle: LineStyle.fromJson(json['lineStyle']), + ); +} +``` + +## Drawing Tool Models + +### DrawingToolConfig + +`DrawingToolConfig` is the base class for all drawing tool configurations: + +```dart +abstract class DrawingToolConfig { + /// The unique identifier of the drawing tool + final String id; + + /// The name of the drawing tool + String get name; + + DrawingToolConfig({String? id}) : id = id ?? uuid.v4(); + + /// Creates a drawing for this tool + Drawing createDrawing(); + + /// Converts the config to JSON for storage + Map toJson(); + + /// Creates a config from JSON + factory DrawingToolConfig.fromJson(Map json); +} +``` + +### InteractableDrawing + +`InteractableDrawing` is the base class for all interactive drawing tools: + +```dart +abstract class InteractableDrawing { + /// The current state of the drawing + DrawingToolState state; + + /// The style of the drawing + final DrawingPaintStyle style; + + InteractableDrawing({ + this.state = DrawingToolState.normal, + required this.style, + }); + + /// Tests if the point is inside the drawing + bool hitTest(Offset point); + + /// Paints the drawing on the canvas + void paint(Canvas canvas, Size size); + + /// Handles drag operations + void onDrag(Offset delta); +} +``` + +## Next Steps + +Now that you understand the data models used in the Deriv Chart library, you can explore: + +- [Coordinate System](coordinate_system.md) - Learn how coordinates are managed +- [Rendering Pipeline](rendering_pipeline.md) - Understand how data is rendered +- [API Reference](../api_reference/series_classes.md) - Explore the complete API \ No newline at end of file diff --git a/doc/core_concepts/rendering_pipeline.md b/doc/core_concepts/rendering_pipeline.md new file mode 100644 index 000000000..779ba9024 --- /dev/null +++ b/doc/core_concepts/rendering_pipeline.md @@ -0,0 +1,615 @@ +# Rendering Pipeline + +This document explains the rendering pipeline used in the Deriv Chart library, detailing how data flows from raw market data to visual elements on the screen. + +## Overview + +The rendering pipeline in the Deriv Chart library consists of several stages: + +1. **Data Processing**: Preparing and filtering data for visualization +2. **Coordinate Mapping**: Converting data points to screen coordinates +3. **Painting**: Drawing the visual elements on the canvas +4. **Composition**: Combining multiple layers into the final chart + +This pipeline ensures efficient rendering and a responsive user experience, even with large datasets. + +## Data Processing Stage + +The data processing stage prepares the raw market data for visualization: + +### Data Sorting + +All data is sorted chronologically by epoch (timestamp): + +```dart +void sortData() { + data.sort((a, b) => a.epoch.compareTo(b.epoch)); +} +``` + +### Visible Data Calculation + +Only data within the visible range is processed for rendering: + +```dart +void updateVisibleRange(int leftEpoch, int rightEpoch) { + visibleData = _getVisibleData(leftEpoch, rightEpoch); + _calculateMinMaxValues(); +} + +List _getVisibleData(int leftEpoch, int rightEpoch) { + // Use binary search to find the start and end indices + final startIndex = _binarySearchStartIndex(leftEpoch); + final endIndex = _binarySearchEndIndex(rightEpoch); + + // Return the slice of data within the visible range + return data.sublist(startIndex, endIndex + 1); +} +``` + +### Min/Max Value Calculation + +The minimum and maximum values in the visible range are calculated: + +```dart +void _calculateMinMaxValues() { + if (visibleData.isEmpty) { + _minValue = double.nan; + _maxValue = double.nan; + return; + } + + _minValue = double.infinity; + _maxValue = -double.infinity; + + for (final item in visibleData) { + final value = _getValue(item); + if (value < _minValue) _minValue = value; + if (value > _maxValue) _maxValue = value; + } +} +``` + +### Data Transformation + +Some series types (like indicators) transform the raw data: + +```dart +List _calculateIndicatorValues() { + final result = []; + + // Apply the indicator calculation to the data + for (int i = period - 1; i < data.length; i++) { + final value = _calculateIndicatorValue(i); + result.add(Tick( + epoch: data[i].epoch, + quote: value, + )); + } + + return result; +} +``` + +## Coordinate Mapping Stage + +The coordinate mapping stage converts data points to screen coordinates: + +### X-Coordinate Mapping + +Time values (epochs) are mapped to X-coordinates: + +```dart +double xFromEpoch(DateTime epoch) { + return width - (rightBoundEpoch - epoch.millisecondsSinceEpoch) / msPerPx; +} +``` + +### Y-Coordinate Mapping + +Price values (quotes) are mapped to Y-coordinates: + +```dart +double yFromQuote(double quote) { + return height - bottomPadding - (quote - bottomBoundQuote) / quotePerPx; +} +``` + +### Viewport Clipping + +Data points outside the viewport are clipped: + +```dart +bool isInViewport(double x, double y) { + return x >= 0 && x <= width && y >= 0 && y <= height; +} +``` + +## Painting Stage + +The painting stage draws the visual elements on the canvas: + +### SeriesPainter + +The `SeriesPainter` is the base class for all painters: + +```dart +abstract class SeriesPainter { + final Series series; + + SeriesPainter(this.series); + + void onPaint( + Canvas canvas, + Size size, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ); +} +``` + +### DataPainter + +The `DataPainter` extends `SeriesPainter` to handle sequential data: + +```dart +abstract class DataPainter extends SeriesPainter { + final DataSeries dataSeries; + + DataPainter(this.dataSeries) : super(dataSeries); + + @override + void onPaint( + Canvas canvas, + Size size, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ) { + // Common setup for all data painters + + // Call the specific painting implementation + onPaintData( + canvas, + size, + dataSeries.visibleData, + xFromEpoch, + yFromQuote, + ); + } + + void onPaintData( + Canvas canvas, + Size size, + List data, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ); +} +``` + +### Specific Painters + +Each chart type has its own painter implementation: + +#### LinePainter + +```dart +class LinePainter extends DataPainter { + final LineSeries lineSeries; + + LinePainter(this.lineSeries) : super(lineSeries); + + @override + void onPaintData( + Canvas canvas, + Size size, + List data, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ) { + if (data.isEmpty) return; + + final path = Path(); + final paint = Paint() + ..color = lineSeries.style.color + ..strokeWidth = lineSeries.style.thickness + ..style = PaintingStyle.stroke; + + // Move to the first point + final firstX = xFromEpoch(data.first.epoch); + final firstY = yFromQuote(data.first.quote); + path.moveTo(firstX, firstY); + + // Add line segments to each subsequent point + for (int i = 1; i < data.length; i++) { + final x = xFromEpoch(data[i].epoch); + final y = yFromQuote(data[i].quote); + path.lineTo(x, y); + } + + // Draw the path + canvas.drawPath(path, paint); + } +} +``` + +#### CandlePainter + +```dart +class CandlePainter extends DataPainter { + final CandleSeries candleSeries; + + CandlePainter(this.candleSeries) : super(candleSeries); + + @override + void onPaintData( + Canvas canvas, + Size size, + List data, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ) { + if (data.isEmpty) return; + + final style = candleSeries.style; + final wickPaint = Paint() + ..strokeWidth = style.wickWidth; + final bodyPaint = Paint(); + + for (final candle in data) { + final x = xFromEpoch(candle.epoch); + final yOpen = yFromQuote(candle.open); + final yHigh = yFromQuote(candle.high); + final yLow = yFromQuote(candle.low); + final yClose = yFromQuote(candle.close); + + // Determine colors based on candle direction + final isPositive = candle.close >= candle.open; + wickPaint.color = isPositive ? style.positiveColor : style.negativeColor; + bodyPaint.color = isPositive ? style.positiveColor : style.negativeColor; + + // Draw wick + canvas.drawLine( + Offset(x, yHigh), + Offset(x, yLow), + wickPaint, + ); + + // Draw body + final halfBodyWidth = style.bodyWidth / 2; + canvas.drawRect( + Rect.fromPoints( + Offset(x - halfBodyWidth, yOpen), + Offset(x + halfBodyWidth, yClose), + ), + bodyPaint, + ); + } + } +} +``` + +### Barrier Painters + +Barriers have their own painters: + +```dart +class HorizontalBarrierPainter extends SeriesPainter { + final HorizontalBarrier barrier; + + HorizontalBarrierPainter(this.barrier) : super(barrier); + + @override + void onPaint( + Canvas canvas, + Size size, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ) { + final y = yFromQuote(barrier.value); + + // Draw the horizontal line + final paint = Paint() + ..color = barrier.style.color + ..strokeWidth = barrier.style.lineThickness; + + if (barrier.style.isDashed) { + // Draw dashed line + drawDashedLine( + canvas, + Offset(0, y), + Offset(size.width, y), + paint, + ); + } else { + // Draw solid line + canvas.drawLine( + Offset(0, y), + Offset(size.width, y), + paint, + ); + } + + // Draw the label if provided + if (barrier.title != null) { + drawLabel( + canvas, + barrier.title!, + Offset(10, y), + barrier.style.labelBackgroundColor, + barrier.style.labelTextStyle, + ); + } + } +} +``` + +## Composition Stage + +The composition stage combines multiple layers into the final chart: + +### Layer Management + +The chart manages multiple layers: + +1. **Grid Layer**: Background grid lines and labels +2. **Data Layer**: Main data series and overlay indicators +3. **Annotation Layer**: Barriers and other annotations +4. **Marker Layer**: Markers and active markers +5. **Crosshair Layer**: Crosshair lines and labels +6. **Interactive Layer**: Drawing tools and user interactions + +### Layer Ordering + +Layers are painted in a specific order to ensure proper visual hierarchy: + +```dart +@override +Widget build(BuildContext context) { + return Stack( + children: [ + // Grid layer (bottom) + GridLayer( + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + theme: theme, + ), + + // Data layer + DataLayer( + mainSeries: mainSeries, + overlaySeries: overlaySeries, + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + ), + + // Annotation layer + AnnotationLayer( + annotations: annotations, + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + ), + + // Marker layer + MarkerLayer( + markerSeries: markerSeries, + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + ), + + // Interactive layer + InteractiveLayer( + drawingTools: drawingTools, + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + ), + + // Crosshair layer (top) + CrosshairLayer( + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + theme: theme, + ), + ], + ); +} +``` + +### CustomPaint + +Each layer uses `CustomPaint` to render its content: + +```dart +class DataLayer extends StatelessWidget { + final Series mainSeries; + final List overlaySeries; + final XAxisModel xAxisModel; + final YAxisModel yAxisModel; + + const DataLayer({ + required this.mainSeries, + required this.overlaySeries, + required this.xAxisModel, + required this.yAxisModel, + }); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: DataLayerPainter( + mainSeries: mainSeries, + overlaySeries: overlaySeries, + xAxisModel: xAxisModel, + yAxisModel: yAxisModel, + ), + size: Size.infinite, + ); + } +} + +class DataLayerPainter extends CustomPainter { + final Series mainSeries; + final List overlaySeries; + final XAxisModel xAxisModel; + final YAxisModel yAxisModel; + + DataLayerPainter({ + required this.mainSeries, + required this.overlaySeries, + required this.xAxisModel, + required this.yAxisModel, + }); + + @override + void paint(Canvas canvas, Size size) { + // Paint the main series + final mainPainter = mainSeries.createPainter(); + mainPainter.onPaint( + canvas, + size, + xAxisModel.xFromEpoch, + yAxisModel.yFromQuote, + ); + + // Paint the overlay series + for (final series in overlaySeries) { + final painter = series.createPainter(); + painter.onPaint( + canvas, + size, + xAxisModel.xFromEpoch, + yAxisModel.yFromQuote, + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} +``` + +## Optimization Techniques + +The rendering pipeline includes several optimization techniques: + +### Binary Search for Visible Data + +Binary search is used to efficiently find the visible data range: + +```dart +int _binarySearchStartIndex(int leftEpoch) { + int low = 0; + int high = data.length - 1; + + while (low <= high) { + final mid = (low + high) ~/ 2; + final midEpoch = data[mid].epoch.millisecondsSinceEpoch; + + if (midEpoch < leftEpoch) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return max(0, min(low, data.length - 1)); +} +``` + +### Path Optimization + +For line charts, paths are used instead of individual line segments: + +```dart +// Inefficient approach (drawing individual lines) +for (int i = 1; i < data.length; i++) { + canvas.drawLine( + Offset(xFromEpoch(data[i-1].epoch), yFromQuote(data[i-1].quote)), + Offset(xFromEpoch(data[i].epoch), yFromQuote(data[i].quote)), + paint, + ); +} + +// Efficient approach (using a path) +final path = Path(); +path.moveTo(xFromEpoch(data.first.epoch), yFromQuote(data.first.quote)); +for (int i = 1; i < data.length; i++) { + path.lineTo(xFromEpoch(data[i].epoch), yFromQuote(data[i].quote)); +} +canvas.drawPath(path, paint); +``` + +### Caching Indicator Values + +Indicator values are cached to avoid recalculation: + +```dart +List _getIndicatorValues() { + if (_cachedValues != null) return _cachedValues!; + + _cachedValues = _calculateIndicatorValues(); + return _cachedValues!; +} +``` + +### Viewport Clipping + +Only elements within the viewport are rendered: + +```dart +void onPaintData( + Canvas canvas, + Size size, + List data, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, +) { + // Save the canvas state + canvas.save(); + + // Clip to the viewport + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + + // Paint the data + // ... + + // Restore the canvas state + canvas.restore(); +} +``` + +## Rendering Flow + +The complete rendering flow is as follows: + +1. **Data Update**: + - New data is received + - Data is sorted chronologically + - `updateVisibleRange` is called with the current viewport + +2. **Viewport Update**: + - User scrolls or zooms + - `XAxisModel` updates `rightBoundEpoch` and `msPerPx` + - `updateVisibleRange` is called with the new viewport + +3. **Y-Axis Update**: + - Visible data min/max values are calculated + - Y-axis bounds are updated with padding + - `quotePerPx` is recalculated + +4. **Painting**: + - Each layer's `CustomPaint` is triggered to repaint + - Each layer's painter calls the appropriate `SeriesPainter` + - Each `SeriesPainter` renders its content using the coordinate conversion functions + +5. **Composition**: + - Flutter composites all layers into the final chart + - The chart is displayed on the screen + +## Next Steps + +Now that you understand the rendering pipeline used in the Deriv Chart library, you can explore: + +- [Architecture](architecture.md) - Learn about the overall architecture of the library +- [Coordinate System](coordinate_system.md) - Understand how coordinates are managed +- [API Reference](../api_reference/chart_widget.md) - Explore the complete API \ No newline at end of file diff --git a/doc/features/annotations.md b/doc/features/annotations.md new file mode 100644 index 000000000..1d0722c6b --- /dev/null +++ b/doc/features/annotations.md @@ -0,0 +1,402 @@ +# Chart Annotations + +This document explains how to use annotations in the Deriv Chart library to highlight specific price levels, time points, and other significant areas on the chart. + +## Introduction to Annotations + +Annotations are visual elements added to a chart to highlight important information. Unlike drawing tools, which are interactive and can be manipulated by users, annotations are typically static elements that are programmatically added to the chart. + +The Deriv Chart library provides several types of annotations: +- Horizontal barriers (price levels) +- Vertical barriers (time points) +- Tick indicators (specific data points) + +## Horizontal Barriers + +Horizontal barriers are horizontal lines that highlight specific price levels on the chart. They are commonly used to mark support and resistance levels, target prices, or entry and exit points. + +### Basic Usage + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + HorizontalBarrier( + 125.50, // Price level + title: 'Resistance', // Optional label + ), + ], +) +``` + +### Customizing Horizontal Barriers + +You can customize the appearance of horizontal barriers using the `HorizontalBarrierStyle` class: + +```dart +HorizontalBarrier( + 125.50, + title: 'Resistance', + style: HorizontalBarrierStyle( + color: Colors.red, + isDashed: true, + lineThickness: 1.5, + labelBackgroundColor: Colors.red.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), +) +``` + +### Controlling Visibility Behavior + +You can control how horizontal barriers behave when they are outside the visible price range using the `visibility` parameter: + +```dart +HorizontalBarrier( + 125.50, + title: 'Resistance', + visibility: HorizontalBarrierVisibility.forceToStayOnRange, +) +``` + +Available visibility options: +- `HorizontalBarrierVisibility.normal`: The barrier is only visible when its price level is within the visible range +- `HorizontalBarrierVisibility.forceToStayOnRange`: The barrier is always visible, even if its price level is outside the visible range (it will be shown at the edge of the chart) + +## Vertical Barriers + +Vertical barriers are vertical lines that highlight specific time points on the chart. They are commonly used to mark significant events, announcements, or trading sessions. + +### Basic Usage + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + VerticalBarrier( + DateTime(2023, 1, 15, 12, 0), // Time point + title: 'Market Open', // Optional label + ), + ], +) +``` + +### Customizing Vertical Barriers + +You can customize the appearance of vertical barriers using the `VerticalBarrierStyle` class: + +```dart +VerticalBarrier( + DateTime(2023, 1, 15, 12, 0), + title: 'Market Open', + style: VerticalBarrierStyle( + color: Colors.blue, + isDashed: true, + lineThickness: 1.5, + labelBackgroundColor: Colors.blue.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), +) +``` + +## Tick Indicators + +Tick indicators are special annotations that highlight specific data points on the chart. They combine a horizontal barrier with a marker to show a specific tick's price and time. + +### Basic Usage + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + TickIndicator( + ticks.last, // The tick to highlight + ), + ], +) +``` + +### Customizing Tick Indicators + +Tick indicators are a subclass of `HorizontalBarrier`, so they can be customized in the same way: + +```dart +TickIndicator( + ticks.last, + style: HorizontalBarrierStyle( + color: Colors.green, + isDashed: false, + lineThickness: 1.5, + labelBackgroundColor: Colors.green.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), +) +``` + +By default, tick indicators use `HorizontalBarrierVisibility.forceToStayOnRange` to ensure they are always visible. + +## Multiple Annotations + +You can add multiple annotations to a chart by including them in the `annotations` list: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + // Horizontal barriers + HorizontalBarrier(125.50, title: 'Resistance'), + HorizontalBarrier(115.75, title: 'Support'), + + // Vertical barriers + VerticalBarrier(DateTime(2023, 1, 15, 12, 0), title: 'Market Open'), + VerticalBarrier(DateTime(2023, 1, 15, 20, 0), title: 'Market Close'), + + // Tick indicator + TickIndicator(ticks.last), + ], +) +``` + +## Dynamic Annotations + +You can dynamically update annotations based on data changes or user interactions: + +```dart +class DynamicAnnotationsExample extends StatefulWidget { + final List ticks; + + const DynamicAnnotationsExample({ + Key? key, + required this.ticks, + }) : super(key: key); + + @override + State createState() => _DynamicAnnotationsExampleState(); +} + +class _DynamicAnnotationsExampleState extends State { + late List _annotations; + + @override + void initState() { + super.initState(); + _updateAnnotations(); + } + + void _updateAnnotations() { + // Calculate average price + double sum = 0; + for (final tick in widget.ticks) { + sum += tick.quote; + } + final averagePrice = sum / widget.ticks.length; + + // Create annotations + _annotations = [ + HorizontalBarrier( + averagePrice, + title: 'Average: ${averagePrice.toStringAsFixed(2)}', + style: HorizontalBarrierStyle( + color: Colors.purple, + isDashed: true, + ), + ), + TickIndicator(widget.ticks.last), + ]; + } + + @override + void didUpdateWidget(DynamicAnnotationsExample oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.ticks != oldWidget.ticks) { + _updateAnnotations(); + } + } + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(widget.ticks), + pipSize: 2, + annotations: _annotations, + ); + } +} +``` + +## Custom Annotations + +You can create custom annotations by extending the `ChartAnnotation` class: + +```dart +class RangeAnnotation extends ChartAnnotation { + final double upperValue; + final double lowerValue; + final String? title; + final Color color; + + RangeAnnotation({ + required this.upperValue, + required this.lowerValue, + this.title, + this.color = Colors.blue, + super.id, + }); + + @override + ChartObject createObject() { + return RangeObject( + upperValue: upperValue, + lowerValue: lowerValue, + title: title, + ); + } + + @override + SeriesPainter createPainter() { + return RangeAnnotationPainter(this); + } +} + +class RangeObject extends ChartObject { + final double upperValue; + final double lowerValue; + final String? title; + + RangeObject({ + required this.upperValue, + required this.lowerValue, + this.title, + }); + + @override + bool isOnValueRange(double minValue, double maxValue) { + return upperValue >= minValue || lowerValue <= maxValue; + } + + @override + bool isOnEpochRange(int minEpoch, int maxEpoch) { + return true; // Range spans the entire x-axis + } +} + +class RangeAnnotationPainter extends SeriesPainter { + final RangeAnnotation annotation; + + RangeAnnotationPainter(this.annotation) : super(annotation); + + @override + void onPaint( + Canvas canvas, + Size size, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ) { + final upperY = yFromQuote(annotation.upperValue); + final lowerY = yFromQuote(annotation.lowerValue); + + // Draw the range rectangle + final paint = Paint() + ..color = annotation.color.withOpacity(0.2) + ..style = PaintingStyle.fill; + + canvas.drawRect( + Rect.fromPoints( + Offset(0, upperY), + Offset(size.width, lowerY), + ), + paint, + ); + + // Draw the range borders + final borderPaint = Paint() + ..color = annotation.color + ..strokeWidth = 1 + ..style = PaintingStyle.stroke; + + canvas.drawLine( + Offset(0, upperY), + Offset(size.width, upperY), + borderPaint, + ); + + canvas.drawLine( + Offset(0, lowerY), + Offset(size.width, lowerY), + borderPaint, + ); + + // Draw the title if provided + if (annotation.title != null) { + final textPainter = TextPainter( + text: TextSpan( + text: annotation.title, + style: TextStyle( + color: annotation.color, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + textDirection: TextDirection.ltr, + ); + + textPainter.layout(); + textPainter.paint( + canvas, + Offset(10, upperY + 5), + ); + } + } +} +``` + +Usage of the custom annotation: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + RangeAnnotation( + upperValue: 130.0, + lowerValue: 120.0, + title: 'Trading Range', + color: Colors.orange, + ), + ], +) +``` + +## Best Practices + +When using annotations, consider the following best practices: + +1. **Use sparingly**: Too many annotations can clutter the chart and make it difficult to read +2. **Use consistent styling**: Use consistent colors and styles for similar types of annotations +3. **Provide clear labels**: Use descriptive labels to explain the significance of each annotation +4. **Update dynamically**: Update annotations when relevant data changes +5. **Consider visibility**: Ensure annotations are visible against the chart background + +## Next Steps + +Now that you understand how to use annotations in the Deriv Chart library, you can explore: + +- [Markers](markers.md) - Learn how to use markers to highlight specific points +- [Crosshair](crosshair.md) - Understand how to use the crosshair for precise data inspection +- [Drawing Tools](drawing_tools/overview.md) - Explore interactive drawing tools for technical analysis \ No newline at end of file diff --git a/doc/features/crosshair.md b/doc/features/crosshair.md new file mode 100644 index 000000000..102e8bebc --- /dev/null +++ b/doc/features/crosshair.md @@ -0,0 +1,327 @@ +# Crosshair + +This document explains how to use and customize the crosshair feature in the Deriv Chart library, which provides precise data inspection capabilities. + +## Introduction to Crosshair + +The crosshair is a tool that helps users inspect specific data points on a chart. It consists of horizontal and vertical lines that intersect at the point of interest, along with labels showing the exact time and price values at that point. + +In the Deriv Chart library, the crosshair is activated by a long press on the chart and can be customized to suit different needs. + +## Basic Usage + +By default, the crosshair is enabled in the Chart widget: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + // Crosshair is enabled by default +) +``` + +You can explicitly enable or disable the crosshair: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + showCrosshair: true, // or false to disable +) +``` + +## Crosshair Behavior + +The crosshair in the Deriv Chart library has the following behavior: + +1. **Activation**: The crosshair is activated by a long press on the chart +2. **Movement**: Once activated, the crosshair follows the user's finger or cursor as they move across the chart +3. **Deactivation**: The crosshair is deactivated when the user releases their finger or cursor +4. **Data Display**: The crosshair shows the exact time and price values at the intersection point +5. **Snap to Data**: The crosshair can snap to the nearest data point for more precise inspection + +## Customizing Crosshair Appearance + +You can customize the appearance of the crosshair by providing a custom theme with a `CrosshairStyle`: + +```dart +class CustomTheme extends ChartDefaultDarkTheme { + @override + CrosshairStyle get crosshairStyle => CrosshairStyle( + lineColor: Colors.orange, + lineThickness: 1, + lineDashPattern: [5, 5], // Dashed line + labelBackgroundColor: Colors.orange.withOpacity(0.8), + labelTextStyle: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + labelPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + labelBorderRadius: 4, + ); +} + +// Using the custom theme +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + theme: CustomTheme(), +) +``` + +## Crosshair Callbacks + +The Chart widget provides callbacks to respond to crosshair events: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + onCrosshairAppeared: () { + print('Crosshair appeared'); + // Provide haptic feedback or update UI + HapticFeedback.lightImpact(); + }, + onCrosshairDisappeared: () { + print('Crosshair disappeared'); + // Update UI or perform cleanup + }, +) +``` + +These callbacks are useful for: +- Providing haptic feedback when the crosshair appears +- Updating other UI elements based on crosshair state +- Logging user interactions for analytics + +## Crosshair with Multiple Charts + +When using multiple charts (main chart and bottom charts), the crosshair is synchronized across all charts: + +```dart +Chart( + mainSeries: CandleSeries(candles), + bottomConfigs: [ + RSIIndicatorConfig(period: 14), + MACDIndicatorConfig(), + ], + pipSize: 2, + // Crosshair will be synchronized across all charts +) +``` + +This synchronization ensures that: +1. The vertical line of the crosshair aligns across all charts +2. Each chart shows its own horizontal line and price label +3. Only one time label is shown (typically at the bottom) + +## Crosshair Data Inspection + +The crosshair helps users inspect data in several ways: + +### Price Inspection + +The horizontal line and label show the exact price at the crosshair position: + +``` +Price: 125.50 +``` + +### Time Inspection + +The vertical line and label show the exact time at the crosshair position: + +``` +Time: 2023-01-15 12:30:45 +``` + +### Data Point Inspection + +When the crosshair snaps to a data point, it can show additional information about that point: + +For candlestick charts: +``` +O: 125.00 H: 126.50 +L: 124.75 C: 125.50 +``` + +For indicator charts: +``` +RSI: 65.75 +``` + +## Implementing Custom Crosshair Behavior + +You can implement custom crosshair behavior by combining the crosshair with other features: + +### Example: Displaying Detailed Information in a Tooltip + +```dart +class CrosshairTooltipExample extends StatefulWidget { + final List candles; + + const CrosshairTooltipExample({ + Key? key, + required this.candles, + }) : super(key: key); + + @override + State createState() => _CrosshairTooltipExampleState(); +} + +class _CrosshairTooltipExampleState extends State { + Candle? _selectedCandle; + Offset? _tooltipPosition; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Chart( + mainSeries: CandleSeries(widget.candles), + pipSize: 2, + onLongPressStart: (details) { + _updateSelectedCandle(details.localPosition); + }, + onLongPressMoveUpdate: (details) { + _updateSelectedCandle(details.localPosition); + }, + onLongPressEnd: (details) { + setState(() { + _selectedCandle = null; + _tooltipPosition = null; + }); + }, + ), + if (_selectedCandle != null && _tooltipPosition != null) + Positioned( + left: _tooltipPosition!.dx + 10, + top: _tooltipPosition!.dy - 70, + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.8), + borderRadius: BorderRadius.circular(4), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Date: ${_formatDate(_selectedCandle!.epoch)}', + style: TextStyle(color: Colors.white), + ), + Text( + 'Open: ${_selectedCandle!.open.toStringAsFixed(2)}', + style: TextStyle(color: Colors.white), + ), + Text( + 'High: ${_selectedCandle!.high.toStringAsFixed(2)}', + style: TextStyle(color: Colors.white), + ), + Text( + 'Low: ${_selectedCandle!.low.toStringAsFixed(2)}', + style: TextStyle(color: Colors.white), + ), + Text( + 'Close: ${_selectedCandle!.close.toStringAsFixed(2)}', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), + ), + ], + ); + } + + void _updateSelectedCandle(Offset position) { + // Find the candle at the position + // This is a simplified example - in a real app, you would use the chart's + // coordinate conversion functions to find the exact candle + final index = (position.dx / context.size!.width * widget.candles.length).floor(); + if (index >= 0 && index < widget.candles.length) { + setState(() { + _selectedCandle = widget.candles[index]; + _tooltipPosition = position; + }); + } + } + + String _formatDate(DateTime date) { + return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; + } +} +``` + +### Example: Synchronizing Crosshair Between Multiple Charts + +```dart +class SynchronizedCrosshairExample extends StatefulWidget { + final List candles; + + const SynchronizedCrosshairExample({ + Key? key, + required this.candles, + }) : super(key: key); + + @override + State createState() => _SynchronizedCrosshairExampleState(); +} + +class _SynchronizedCrosshairExampleState extends State { + final _controller1 = ChartController(); + final _controller2 = ChartController(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: Chart( + mainSeries: CandleSeries(widget.candles), + pipSize: 2, + controller: _controller1, + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + // Synchronize the visible area of the second chart + _controller2.scrollTo(DateTime.fromMillisecondsSinceEpoch(leftEpoch)); + _controller2.scale(_controller1.msPerPx); + }, + ), + ), + Expanded( + child: Chart( + mainSeries: LineSeries(widget.candles.map((c) => Tick(epoch: c.epoch, quote: c.close)).toList()), + pipSize: 2, + controller: _controller2, + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + // Synchronize the visible area of the first chart + _controller1.scrollTo(DateTime.fromMillisecondsSinceEpoch(leftEpoch)); + _controller1.scale(_controller2.msPerPx); + }, + ), + ), + ], + ); + } +} +``` + +## Best Practices + +When using the crosshair feature, consider the following best practices: + +1. **Provide feedback**: Use the `onCrosshairAppeared` callback to provide haptic feedback when the crosshair appears +2. **Customize appearance**: Customize the crosshair appearance to match your app's theme +3. **Consider visibility**: Ensure the crosshair is visible against the chart background +4. **Enhance with tooltips**: Consider adding custom tooltips to provide more detailed information +5. **Test on different devices**: Test the crosshair behavior on different devices and screen sizes + +## Next Steps + +Now that you understand how to use the crosshair feature in the Deriv Chart library, you can explore: + +- [Annotations](annotations.md) - Learn how to use annotations to highlight specific areas +- [Markers](markers.md) - Learn how to use markers to highlight specific points +- [Drawing Tools](drawing_tools/overview.md) - Explore interactive drawing tools for technical analysis \ No newline at end of file diff --git a/doc/features/drawing_tools/overview.md b/doc/features/drawing_tools/overview.md new file mode 100644 index 000000000..b3f217002 --- /dev/null +++ b/doc/features/drawing_tools/overview.md @@ -0,0 +1,362 @@ +# Drawing Tools Overview + +This document provides an overview of the drawing tools available in the Deriv Chart library, explaining their purpose, categories, and how to use them. + +## Introduction to Drawing Tools + +Drawing tools allow traders to add visual elements to charts for technical analysis. These tools help identify patterns, trends, support and resistance levels, and other significant price points. The Deriv Chart library provides a comprehensive set of drawing tools to enhance chart analysis. + +## Drawing Tool Categories + +The Deriv Chart library organizes drawing tools into several categories: + +### Lines and Channels + +Lines and channels help identify trends, support and resistance levels, and price patterns. + +Available line and channel tools: +- Horizontal Line +- Vertical Line +- Trend Line +- Ray Line +- Extended Line +- Parallel Channel +- Regression Channel + +[Learn more about Lines and Channels](lines_and_channels.md) + +### Fibonacci Tools + +Fibonacci tools are based on the Fibonacci sequence and ratios, which are believed to have significance in financial markets. + +Available Fibonacci tools: +- Fibonacci Retracement +- Fibonacci Extension +- Fibonacci Fan +- Fibonacci Arc +- Fibonacci Time Zones + +[Learn more about Fibonacci Tools](fibonacci_tools.md) + +### Geometric Shapes + +Geometric shapes help identify chart patterns and areas of interest. + +Available geometric shapes: +- Rectangle +- Triangle +- Ellipse +- Polygon +- Arc + +### Text and Annotations + +Text and annotations allow adding notes and labels to the chart. + +Available text and annotation tools: +- Text +- Callout +- Arrow +- Label + +## Interactive Layer Architecture + +The Deriv Chart library implements drawing tools using an Interactive Layer that manages user interactions: + +``` +┌─────────────────────────┐ +│ Interactive Layer │ +└─────────────┬───────────┘ + │ + ▼ +┌─────────────────────────┐ +│ InteractiveState │ +└─────────────┬───────────┘ + │ + ├─────────────────┬─────────────────┬─────────────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────────┐┌─────────────────┐┌─────────────────┐┌─────────────────┐ +│ NormalState ││SelectedToolState││ AddingToolState ││ HoverState │ +└─────────────────┘└─────────────────┘└─────────────────┘└─────────────────┘ +``` + +The Interactive Layer: +- Handles user gestures (taps, drags, hovers) +- Manages the lifecycle of drawing tools +- Implements a state pattern for different interaction modes +- Controls the visual appearance of drawing tools + +## Using Drawing Tools in the Chart + +### Basic Usage with DerivChart + +The `DerivChart` widget provides built-in UI for adding and managing drawing tools: + +```dart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60, + activeSymbol: 'R_100', + pipSize: 2, +) +``` + +This includes: +- A drawing tools button in the chart toolbar +- A drawing tools menu for selecting tools +- Tool-specific configuration options +- Persistent storage of drawing tools + +### Custom Drawing Tools Management + +For more control, you can create your own `AddOnsRepository` for drawing tools: + +```dart +final drawingToolsRepo = AddOnsRepository( + createAddOn: (Map map) => DrawingToolConfig.fromJson(map), + onEditCallback: (int index) { + // Handle editing of drawing tool at index + }, + sharedPrefKey: 'R_100_drawing_tools', +); + +// Load saved drawing tools +await drawingToolsRepo.loadFromPrefs( + await SharedPreferences.getInstance(), + 'R_100_drawing_tools', +); + +// Use the repository with DerivChart +DerivChart( + mainSeries: CandleSeries(candles), + drawingToolsRepo: drawingToolsRepo, + granularity: 60, + pipSize: 2, +) +``` + +## Drawing Tool States + +Each drawing tool can be in one of the following states: + +- **normal**: Default state when the tool is displayed but not being interacted with +- **selected**: The tool is selected and can be manipulated +- **hovered**: The user's pointer is hovering over the tool +- **adding**: The tool is in the process of being created +- **dragging**: The tool is being moved or resized + +These states determine how the tool is rendered and how it responds to user interactions. + +## Drawing Tool Interaction Flow + +The typical flow for adding and interacting with drawing tools is: + +1. **Tool Selection**: User selects a drawing tool from the menu +2. **Tool Creation**: User taps on the chart to define the tool's points + - For a line: tap for start point, tap for end point + - For a rectangle: tap for one corner, tap for opposite corner + - For more complex tools: multiple taps to define all required points +3. **Tool Manipulation**: After creation, the tool can be: + - Selected by tapping on it + - Moved by dragging it + - Resized by dragging its control points + - Configured through the properties panel + - Deleted using the delete button or keyboard + +## Drawing Tool Configuration + +Each drawing tool has its own configuration options: + +### Style Properties + +Common style properties include: +- Line color +- Line thickness +- Line style (solid, dashed, etc.) +- Fill color and opacity +- Text properties (for tools with labels) + +### Tool-Specific Properties + +Each tool type has specific properties: + +- **Fibonacci Retracement**: Levels, labels, colors +- **Trend Line**: Extension options, label options +- **Rectangle**: Border style, fill options +- **Text**: Font, size, alignment + +## Example: Adding a Trend Line + +```dart +// Create a trend line configuration +final trendLineConfig = TrendLineConfig( + style: DrawingPaintStyle( + color: Colors.blue, + thickness: 2, + isDashed: false, + ), +); + +// Add it to the repository +drawingToolsRepo.add(trendLineConfig); +``` + +## Example: Customizing a Drawing Tool + +```dart +// Get the existing tool +final existingTool = drawingToolsRepo.items[0] as TrendLineConfig; + +// Create an updated version +final updatedTool = TrendLineConfig( + id: existingTool.id, // Keep the same ID + style: DrawingPaintStyle( + color: Colors.red, + thickness: 3, + isDashed: true, + ), +); + +// Update it in the repository +drawingToolsRepo.updateAt(0, updatedTool); +``` + +## Implementing Custom Drawing Tools + +You can create custom drawing tools by implementing the required classes: + +1. **DrawingToolConfig**: Configuration for the tool +2. **InteractableDrawing**: The drawing implementation +3. **DrawingCreator**: Logic for creating the drawing + +### Example: Custom Drawing Tool + +```dart +// Configuration +class CustomToolConfig extends DrawingToolConfig { + final DrawingPaintStyle style; + + CustomToolConfig({ + required this.style, + super.id, + }); + + @override + String get name => 'Custom Tool'; + + @override + InteractableDrawing createDrawing() => CustomInteractableDrawing( + style: style, + ); + + @override + Map toJson() => { + 'id': id, + 'type': 'CustomTool', + 'style': style.toJson(), + }; + + factory CustomToolConfig.fromJson(Map json) => CustomToolConfig( + id: json['id'], + style: DrawingPaintStyle.fromJson(json['style']), + ); +} + +// Drawing Implementation +class CustomInteractableDrawing extends InteractableDrawing { + final List points = []; + + CustomInteractableDrawing({ + required DrawingPaintStyle style, + }) : super(style: style); + + @override + bool hitTest(Offset point) { + // Implement hit testing logic + if (points.length < 2) return false; + + for (int i = 0; i < points.length - 1; i++) { + if (isPointNearLine(point, points[i], points[i + 1])) { + return true; + } + } + + return false; + } + + @override + void paint(Canvas canvas, Size size) { + if (points.length < 2) return; + + final paint = Paint() + ..color = style.color + ..strokeWidth = style.thickness + ..style = PaintingStyle.stroke; + + final path = Path(); + path.moveTo(points.first.dx, points.first.dy); + + for (int i = 1; i < points.length; i++) { + path.lineTo(points[i].dx, points[i].dy); + } + + canvas.drawPath(path, paint); + } + + @override + void onDrag(Offset delta) { + for (int i = 0; i < points.length; i++) { + points[i] = points[i] + delta; + } + } + + bool isPointNearLine(Offset point, Offset lineStart, Offset lineEnd) { + // Calculate distance from point to line + final double lineLength = (lineEnd - lineStart).distance; + if (lineLength == 0) return false; + + final double t = ((point.dx - lineStart.dx) * (lineEnd.dx - lineStart.dx) + + (point.dy - lineStart.dy) * (lineEnd.dy - lineStart.dy)) / + (lineLength * lineLength); + + if (t < 0) { + return (point - lineStart).distance < 10; + } else if (t > 1) { + return (point - lineEnd).distance < 10; + } else { + final Offset projection = lineStart + (lineEnd - lineStart) * t; + return (point - projection).distance < 10; + } + } +} + +// Drawing Creator +class CustomToolCreator extends DrawingCreator { + @override + void onTap(Offset position) { + if (drawing == null) { + drawing = CustomInteractableDrawing( + style: config.style, + ); + (drawing as CustomInteractableDrawing).points.add(position); + } else { + (drawing as CustomInteractableDrawing).points.add(position); + + // If we have enough points, complete the drawing + if ((drawing as CustomInteractableDrawing).points.length >= 3) { + onAddDrawing(); + } + } + } +} +``` + +## Next Steps + +Now that you understand the drawing tools available in the Deriv Chart library, you can explore: + +- [Lines and Channels](lines_and_channels.md) - Learn about line and channel tools +- [Fibonacci Tools](fibonacci_tools.md) - Learn about Fibonacci tools +- [Custom Tools](custom_tools.md) - Learn how to create custom drawing tools +- [Interactive Layer](../interactive_layer.md) - Understand the interactive layer architecture \ No newline at end of file diff --git a/doc/features/indicators/overview.md b/doc/features/indicators/overview.md new file mode 100644 index 000000000..02045522c --- /dev/null +++ b/doc/features/indicators/overview.md @@ -0,0 +1,370 @@ +# Technical Indicators Overview + +This document provides an overview of the technical indicators available in the Deriv Chart library, explaining their purpose, categories, and how to use them. + +## Introduction to Technical Indicators + +Technical indicators are mathematical calculations based on price, volume, or open interest of a security or contract. They are used to forecast price direction and to generate trading signals. The Deriv Chart library provides a comprehensive set of technical indicators to help traders analyze market data and make informed decisions. + +## Indicator Categories + +The Deriv Chart library organizes indicators into several categories: + +### Moving Averages + +Moving averages smooth out price data to create a single flowing line, making it easier to identify trends. They are the building blocks for many other technical indicators. + +Available moving averages: +- Simple Moving Average (SMA) +- Exponential Moving Average (EMA) +- Double Exponential Moving Average (DEMA) +- Triple Exponential Moving Average (TEMA) +- Triangular Moving Average (TRIMA) +- Weighted Moving Average (WMA) +- Modified Moving Average (MMA) +- Least Squares Moving Average (LSMA) +- Hull Moving Average (HMA) +- Variable Moving Average (VMA) +- Welles Wilder Smoothing Moving Average (WWSMA) +- Zero-Lag Exponential Moving Average (ZELMA) + +[Learn more about Moving Averages](moving_averages.md) + +### Oscillators + +Oscillators are indicators that fluctuate above and below a centerline or between upper and lower bounds. They help identify overbought or oversold conditions and potential trend reversals. + +Available oscillators: +- Relative Strength Index (RSI) +- Stochastic Momentum Index (SMI) +- Moving Average Convergence Divergence (MACD) +- Awesome Oscillator +- Williams %R +- Rate of Change (ROC) +- Chande Momentum Oscillator (CMO) +- Gator Oscillator + +[Learn more about Oscillators](oscillators.md) + +### Trend Indicators + +Trend indicators help identify the direction of the market trend and potential changes in that direction. + +Available trend indicators: +- Average Directional Index (ADX) +- Parabolic SAR +- Ichimoku Cloud + +[Learn more about Trend Indicators](trend_indicators.md) + +### Volatility Indicators + +Volatility indicators measure the rate of price movement, regardless of direction. They help traders identify potential breakouts or periods of consolidation. + +Available volatility indicators: +- Bollinger Bands +- Average True Range (ATR) +- Standard Deviation +- Variance + +[Learn more about Volatility Indicators](volatility.md) + +### Channel Indicators + +Channel indicators create bands that contain price movement, helping traders identify potential support and resistance levels. + +Available channel indicators: +- Donchian Channel +- Moving Average Envelope +- Fixed Channel Bands (FCB) + +## Using Indicators in the Chart + +The Deriv Chart library provides two ways to add indicators to your charts: + +1. **Overlay Indicators**: These indicators share the same Y-axis as the main chart and are drawn on top of it. +2. **Bottom Indicators**: These indicators have their own Y-axis and are drawn below the main chart. + +### Adding Indicators + +You can add indicators to your chart using the `overlayConfigs` and `bottomConfigs` parameters: + +```dart +Chart( + mainSeries: CandleSeries(candles), + // Overlay indicators (on the main chart) + overlayConfigs: [ + // Bollinger Bands + BollingerBandsIndicatorConfig( + period: 20, + standardDeviation: 2, + movingAverageType: MovingAverageType.exponential, + upperLineStyle: LineStyle(color: Colors.purple), + middleLineStyle: LineStyle(color: Colors.black), + lowerLineStyle: LineStyle(color: Colors.blue), + ), + // Simple Moving Average + MAIndicatorConfig( + period: 14, + type: MovingAverageType.simple, + lineStyle: LineStyle(color: Colors.red), + ), + ], + // Bottom indicators (separate charts) + bottomConfigs: [ + // Relative Strength Index + RSIIndicatorConfig( + period: 14, + lineStyle: LineStyle(color: Colors.green), + oscillatorLinesConfig: OscillatorLinesConfig( + overboughtValue: 70, + oversoldValue: 30, + ), + showZones: true, + ), + // Moving Average Convergence Divergence + MACDIndicatorConfig( + fastPeriod: 12, + slowPeriod: 26, + signalPeriod: 9, + lineStyle: LineStyle(color: Colors.blue), + signalLineStyle: LineStyle(color: Colors.red), + histogramStyle: HistogramStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + ), + ), + ], + pipSize: 2, +) +``` + +### Using DerivChart for Indicator Management + +The `DerivChart` widget provides a more user-friendly way to manage indicators: + +```dart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60, + activeSymbol: 'R_100', + pipSize: 2, +) +``` + +This widget includes UI controls for adding, removing, and configuring indicators. It also saves indicator settings to persistent storage. + +### Creating a Custom Indicator Repository + +For more control over indicator management, you can create your own `AddOnsRepository`: + +```dart +final indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + onEditCallback: (int index) { + // Handle editing of indicator at index + }, + sharedPrefKey: 'R_100', +); + +// Load saved indicators +await indicatorsRepo.loadFromPrefs( + await SharedPreferences.getInstance(), + 'R_100', +); + +// Add an indicator +indicatorsRepo.add(RSIIndicatorConfig(period: 14)); + +// Remove an indicator +indicatorsRepo.removeAt(0); + +// Update an indicator +indicatorsRepo.updateAt(0, RSIIndicatorConfig(period: 21)); + +// Use the repository with DerivChart +DerivChart( + mainSeries: CandleSeries(candles), + indicatorsRepo: indicatorsRepo, + granularity: 60, + pipSize: 2, +) +``` + +## Indicator Configuration + +Each indicator has its own configuration class that extends `IndicatorConfig`. These classes provide parameters to customize the indicator's behavior and appearance. + +### Common Configuration Parameters + +Most indicators share these common parameters: + +- **period**: The number of data points used in the calculation +- **lineStyle**: The style of the indicator line (color, thickness, etc.) +- **offset**: The number of periods to shift the indicator (positive for right, negative for left) + +### Example: RSI Configuration + +```dart +RSIIndicatorConfig( + period: 14, + lineStyle: LineStyle( + color: Colors.green, + thickness: 1, + ), + oscillatorLinesConfig: OscillatorLinesConfig( + overboughtValue: 70, + oversoldValue: 30, + overboughtStyle: LineStyle(color: Colors.red), + oversoldStyle: LineStyle(color: Colors.green), + ), + showZones: true, +) +``` + +### Example: Bollinger Bands Configuration + +```dart +BollingerBandsIndicatorConfig( + period: 20, + standardDeviation: 2, + movingAverageType: MovingAverageType.exponential, + upperLineStyle: LineStyle(color: Colors.purple), + middleLineStyle: LineStyle(color: Colors.black), + lowerLineStyle: LineStyle(color: Colors.blue), +) +``` + +## Creating Custom Indicators + +You can create custom indicators by extending the appropriate base classes: + +1. Create a configuration class that extends `IndicatorConfig` +2. Create a series class that extends `AbstractSingleIndicatorSeries` or `Series` +3. Create a painter class that extends `DataPainter` + +Example of a custom indicator: + +```dart +// Configuration +class CustomIndicatorConfig extends IndicatorConfig { + final int period; + final LineStyle lineStyle; + + CustomIndicatorConfig({ + required this.period, + this.lineStyle = const LineStyle(), + super.id, + }); + + @override + String get name => 'Custom ($period)'; + + @override + bool get isOverlay => true; + + @override + Series createSeries(List candles) => CustomIndicatorSeries( + candles, + period: period, + lineStyle: lineStyle, + ); + + @override + Map toJson() => { + 'id': id, + 'type': 'Custom', + 'period': period, + 'lineStyle': lineStyle.toJson(), + }; + + factory CustomIndicatorConfig.fromJson(Map json) => CustomIndicatorConfig( + id: json['id'], + period: json['period'], + lineStyle: LineStyle.fromJson(json['lineStyle']), + ); +} + +// Series +class CustomIndicatorSeries extends AbstractSingleIndicatorSeries { + final int period; + final LineStyle lineStyle; + + CustomIndicatorSeries( + List candles, { + required this.period, + required this.lineStyle, + }) : super(candles); + + @override + List _calculateIndicatorValues() { + final result = []; + + // Custom calculation logic + for (int i = period - 1; i < candles.length; i++) { + double sum = 0; + for (int j = 0; j < period; j++) { + sum += candles[i - j].close; + } + final value = sum / period; + + result.add(Tick( + epoch: candles[i].epoch, + quote: value, + )); + } + + return result; + } + + @override + SeriesPainter createPainter() => CustomIndicatorPainter(this); +} + +// Painter +class CustomIndicatorPainter extends DataPainter { + final CustomIndicatorSeries series; + + CustomIndicatorPainter(this.series) : super(series); + + @override + void onPaintData( + Canvas canvas, + Size size, + List data, + double Function(DateTime) xFromEpoch, + double Function(double) yFromQuote, + ) { + if (data.isEmpty) return; + + final path = Path(); + final paint = Paint() + ..color = series.lineStyle.color + ..strokeWidth = series.lineStyle.thickness + ..style = PaintingStyle.stroke; + + path.moveTo( + xFromEpoch(data.first.epoch), + yFromQuote(data.first.quote), + ); + + for (int i = 1; i < data.length; i++) { + path.lineTo( + xFromEpoch(data[i].epoch), + yFromQuote(data[i].quote), + ); + } + + canvas.drawPath(path, paint); + } +} +``` + +## Next Steps + +Now that you understand the technical indicators available in the Deriv Chart library, you can explore: + +- [Moving Averages](moving_averages.md) - Learn about moving average indicators +- [Oscillators](oscillators.md) - Learn about oscillator indicators +- [Volatility Indicators](volatility.md) - Learn about volatility indicators +- [Custom Indicators](custom_indicators.md) - Learn how to create custom indicators \ No newline at end of file diff --git a/doc/features/markers.md b/doc/features/markers.md new file mode 100644 index 000000000..ec88a3d07 --- /dev/null +++ b/doc/features/markers.md @@ -0,0 +1,476 @@ +# Chart Markers + +This document explains how to use markers in the Deriv Chart library to highlight specific points on the chart and enable user interactions with those points. + +## Introduction to Markers + +Markers are visual elements that highlight specific data points on a chart. Unlike annotations, which typically span across the chart (like horizontal or vertical lines), markers are attached to specific data points and can respond to user interactions. + +The Deriv Chart library provides several types of markers: +- Standard markers (up, down, neutral) +- Active markers (highlighted markers with special interaction behavior) +- Entry and exit tick markers + +## MarkerSeries + +All markers are managed through the `MarkerSeries` class, which is passed to the `Chart` widget: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries([ + // List of markers + ]), +) +``` + +## Standard Markers + +Standard markers highlight specific data points on the chart. They can be configured with different styles and can respond to user interactions. + +### Basic Usage + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries([ + Marker.up( + epoch: ticks[5].epoch, + quote: ticks[5].quote, + onTap: () { + print('Up marker tapped'); + }, + ), + Marker.down( + epoch: ticks[10].epoch, + quote: ticks[10].quote, + onTap: () { + print('Down marker tapped'); + }, + ), + Marker.neutral( + epoch: ticks[15].epoch, + quote: ticks[15].quote, + onTap: () { + print('Neutral marker tapped'); + }, + ), + ]), +) +``` + +### Marker Types + +The Deriv Chart library provides three types of standard markers: + +1. **Up Marker**: Typically used to indicate a positive event or an entry point + ```dart + Marker.up( + epoch: DateTime.now(), + quote: 125.50, + onTap: () {}, + ) + ``` + +2. **Down Marker**: Typically used to indicate a negative event or an exit point + ```dart + Marker.down( + epoch: DateTime.now(), + quote: 120.75, + onTap: () {}, + ) + ``` + +3. **Neutral Marker**: Used for general points of interest + ```dart + Marker.neutral( + epoch: DateTime.now(), + quote: 123.00, + onTap: () {}, + ) + ``` + +### Customizing Markers + +You can customize markers by providing a custom `MarkerStyle`: + +```dart +Marker.up( + epoch: ticks[5].epoch, + quote: ticks[5].quote, + style: MarkerStyle( + color: Colors.green, + size: 12, + borderWidth: 2, + borderColor: Colors.white, + ), + onTap: () {}, +) +``` + +## Active Marker + +An active marker is a special marker that is highlighted and can have additional interaction behaviors. Typically, only one marker can be active at a time. + +### Basic Usage + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [ + // Standard markers + Marker.up(epoch: ticks[5].epoch, quote: ticks[5].quote, onTap: () {}), + Marker.down(epoch: ticks[10].epoch, quote: ticks[10].quote, onTap: () {}), + ], + // Active marker + activeMarker: ActiveMarker( + epoch: ticks[5].epoch, + quote: ticks[5].quote, + onTap: () { + print('Active marker tapped'); + }, + onOutsideTap: () { + print('Tapped outside active marker'); + // Remove active marker + }, + ), + ), +) +``` + +### Active Marker Properties + +The `ActiveMarker` class has the following properties: + +- `epoch`: The timestamp of the marker +- `quote`: The price level of the marker +- `onTap`: Callback when the marker is tapped +- `onOutsideTap`: Callback when the user taps outside the marker + +### Dynamic Active Marker + +You can dynamically update the active marker based on user interactions: + +```dart +class DynamicMarkerExample extends StatefulWidget { + final List ticks; + + const DynamicMarkerExample({ + Key? key, + required this.ticks, + }) : super(key: key); + + @override + State createState() => _DynamicMarkerExampleState(); +} + +class _DynamicMarkerExampleState extends State { + ActiveMarker? _activeMarker; + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(widget.ticks), + pipSize: 2, + markerSeries: MarkerSeries( + widget.ticks.asMap().entries.map((entry) { + final index = entry.key; + final tick = entry.value; + + // Add a marker every 5 ticks + if (index % 5 == 0) { + return Marker.neutral( + epoch: tick.epoch, + quote: tick.quote, + onTap: () { + setState(() { + _activeMarker = ActiveMarker( + epoch: tick.epoch, + quote: tick.quote, + onTap: () { + print('Active marker tapped'); + }, + onOutsideTap: () { + setState(() { + _activeMarker = null; + }); + }, + ); + }); + }, + ); + } + return null; + }).whereType().toList(), + activeMarker: _activeMarker, + ), + ); + } +} +``` + +## Entry and Exit Tick Markers + +Entry and exit tick markers are special markers that highlight the entry and exit points of a trade or analysis period. + +### Basic Usage + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [], // No standard markers + entryTick: Tick( + epoch: ticks.first.epoch, + quote: ticks.first.quote, + ), + exitTick: Tick( + epoch: ticks.last.epoch, + quote: ticks.last.quote, + ), + ), +) +``` + +### Customizing Entry and Exit Tick Markers + +You can customize the appearance of entry and exit tick markers by providing custom styles: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [], + entryTick: Tick( + epoch: ticks.first.epoch, + quote: ticks.first.quote, + ), + exitTick: Tick( + epoch: ticks.last.epoch, + quote: ticks.last.quote, + ), + entryTickStyle: MarkerStyle( + color: Colors.green, + size: 10, + borderWidth: 2, + borderColor: Colors.white, + ), + exitTickStyle: MarkerStyle( + color: Colors.red, + size: 10, + borderWidth: 2, + borderColor: Colors.white, + ), + ), +) +``` + +## Combining Different Marker Types + +You can combine different types of markers in a single chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [ + // Standard markers + Marker.up(epoch: ticks[5].epoch, quote: ticks[5].quote, onTap: () {}), + Marker.down(epoch: ticks[10].epoch, quote: ticks[10].quote, onTap: () {}), + ], + // Active marker + activeMarker: ActiveMarker( + epoch: ticks[5].epoch, + quote: ticks[5].quote, + onTap: () {}, + onOutsideTap: () {}, + ), + // Entry and exit ticks + entryTick: Tick( + epoch: ticks.first.epoch, + quote: ticks.first.quote, + ), + exitTick: Tick( + epoch: ticks.last.epoch, + quote: ticks.last.quote, + ), + ), +) +``` + +## Markers with Real-Time Data + +When working with real-time data, you can update markers as new data arrives: + +```dart +class RealTimeMarkerExample extends StatefulWidget { + final Stream tickStream; + + const RealTimeMarkerExample({ + Key? key, + required this.tickStream, + }) : super(key: key); + + @override + State createState() => _RealTimeMarkerExampleState(); +} + +class _RealTimeMarkerExampleState extends State { + final List _ticks = []; + final List _markers = []; + + @override + void initState() { + super.initState(); + widget.tickStream.listen(_onNewTick); + } + + void _onNewTick(Tick tick) { + setState(() { + _ticks.add(tick); + + // Add a marker for significant price movements + if (_ticks.length > 1) { + final previousTick = _ticks[_ticks.length - 2]; + final priceDifference = tick.quote - previousTick.quote; + + if (priceDifference.abs() > 1.0) { + _markers.add( + priceDifference > 0 + ? Marker.up( + epoch: tick.epoch, + quote: tick.quote, + onTap: () {}, + ) + : Marker.down( + epoch: tick.epoch, + quote: tick.quote, + onTap: () {}, + ), + ); + } + } + }); + } + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + markerSeries: MarkerSeries(_markers), + ); + } +} +``` + +## Custom Markers + +You can create custom markers by extending the `Marker` class: + +```dart +class CustomMarker extends Marker { + final IconData icon; + final Color color; + + CustomMarker({ + required DateTime epoch, + required double quote, + required this.icon, + required this.color, + VoidCallback? onTap, + }) : super( + epoch: epoch, + quote: quote, + type: MarkerType.neutral, // Use neutral as base type + onTap: onTap, + ); + + // Override the paint method to customize the marker appearance + @override + void paint(Canvas canvas, Offset center, double scale) { + final paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + // Draw a circle background + canvas.drawCircle(center, 10 * scale, paint); + + // Draw the icon + final textPainter = TextPainter( + text: TextSpan( + text: String.fromCharCode(icon.codePoint), + style: TextStyle( + color: Colors.white, + fontSize: 12 * scale, + fontFamily: icon.fontFamily, + ), + ), + textDirection: TextDirection.ltr, + ); + + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + center.dx - textPainter.width / 2, + center.dy - textPainter.height / 2, + ), + ); + } +} +``` + +Usage of the custom marker: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries([ + CustomMarker( + epoch: ticks[5].epoch, + quote: ticks[5].quote, + icon: Icons.star, + color: Colors.orange, + onTap: () { + print('Star marker tapped'); + }, + ), + CustomMarker( + epoch: ticks[10].epoch, + quote: ticks[10].quote, + icon: Icons.warning, + color: Colors.red, + onTap: () { + print('Warning marker tapped'); + }, + ), + ]), +) +``` + +## Best Practices + +When using markers, consider the following best practices: + +1. **Use sparingly**: Too many markers can clutter the chart and make it difficult to read +2. **Provide interaction feedback**: Use the `onTap` callback to provide feedback when a marker is tapped +3. **Use appropriate marker types**: Use up markers for positive events, down markers for negative events, and neutral markers for general points of interest +4. **Consider visibility**: Ensure markers are visible against the chart background +5. **Handle edge cases**: Ensure markers are still visible when they are at the edges of the chart + +## Next Steps + +Now that you understand how to use markers in the Deriv Chart library, you can explore: + +- [Annotations](annotations.md) - Learn how to use annotations to highlight specific areas +- [Crosshair](crosshair.md) - Understand how to use the crosshair for precise data inspection +- [Drawing Tools](drawing_tools/overview.md) - Explore interactive drawing tools for technical analysis \ No newline at end of file diff --git a/doc/getting_started/basic_usage.md b/doc/getting_started/basic_usage.md new file mode 100644 index 000000000..154591f22 --- /dev/null +++ b/doc/getting_started/basic_usage.md @@ -0,0 +1,231 @@ +# Basic Usage + +This guide will walk you through the basic usage of the Deriv Chart library, showing you how to create different types of charts and customize them. + +## Creating a Simple Chart + +To create a basic chart, you need to: + +1. Import the library +2. Create a data series +3. Pass the data series to the Chart widget + +Here's a simple example of a line chart: + +```dart +import 'package:flutter/material.dart'; +import 'package:deriv_chart/deriv_chart.dart'; + +class SimpleChartExample extends StatelessWidget { + const SimpleChartExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // Create sample data + final ticks = [ + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 5)), quote: 100), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 4)), quote: 120), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 3)), quote: 110), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 2)), quote: 130), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 1)), quote: 125), + Tick(epoch: DateTime.now(), quote: 140), + ]; + + // Create the chart + return SizedBox( + height: 300, + child: Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, // Number of decimal places for price values + ), + ); + } +} +``` + +## Chart Data Types + +The Deriv Chart library supports two main types of data: + +1. **Tick Data**: Simple time-price pairs, used for line charts +2. **OHLC Data**: Open-High-Low-Close data, used for candlestick and OHLC charts + +### Tick Data Example + +```dart +final ticks = [ + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 5)), quote: 100), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 4)), quote: 120), + // More ticks... +]; +``` + +### OHLC (Candle) Data Example + +```dart +final candles = [ + Candle( + epoch: DateTime.now().subtract(const Duration(minutes: 5)), + open: 100, + high: 120, + low: 95, + close: 110, + ), + Candle( + epoch: DateTime.now().subtract(const Duration(minutes: 4)), + open: 110, + high: 130, + low: 105, + close: 125, + ), + // More candles... +]; +``` + +## Chart Types + +The Deriv Chart library supports several chart types: + +### Line Chart + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, +) +``` + +### Candlestick Chart + +```dart +Chart( + mainSeries: CandleSeries(candles), + pipSize: 2, + granularity: 60, // 60 seconds per candle +) +``` + +### OHLC Chart + +```dart +Chart( + mainSeries: OHLCSeries(candles), + pipSize: 2, + granularity: 60, // 60 seconds per candle +) +``` + +### Hollow Candlestick Chart + +```dart +Chart( + mainSeries: HollowCandleSeries(candles), + pipSize: 2, + granularity: 60, // 60 seconds per candle +) +``` + +## Basic Customization + +### Styling the Chart + +You can customize the appearance of your charts by providing style objects: + +```dart +Chart( + mainSeries: CandleSeries( + candles, + style: CandleStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + wickWidth: 1, + bodyWidth: 8, + ), + ), + pipSize: 2, +) +``` + +### Adding Annotations + +You can add horizontal or vertical barriers to mark specific price levels or time points: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + HorizontalBarrier( + 125.0, + title: 'Resistance', + style: HorizontalBarrierStyle( + color: Colors.red, + isDashed: true, + ), + ), + VerticalBarrier( + ticks[2].epoch, + title: 'Event', + style: VerticalBarrierStyle( + color: Colors.blue, + isDashed: false, + ), + ), + ], +) +``` + +### Adding Tick Indicators + +Tick indicators are special annotations that highlight specific data points: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + TickIndicator(ticks.last), + ], +) +``` + +## Handling User Interactions + +### Listening to Visible Area Changes + +You can listen to changes in the visible area of the chart (scrolling and zooming): + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + onVisibleAreaChanged: (int leftEpoch, int rightEpoch) { + print('Visible area changed: $leftEpoch to $rightEpoch'); + // Load more data if needed + }, +) +``` + +### Listening to Crosshair Events + +You can listen to when the crosshair appears on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + onCrosshairAppeared: () { + print('Crosshair appeared'); + // Provide haptic feedback or update UI + }, +) +``` + +## Next Steps + +Now that you understand the basics of using the Deriv Chart library, you can explore: + +- [Chart Types](chart_types.md) - Learn more about different chart types +- [Configuration](configuration.md) - Discover more configuration options +- [Indicators](../features/indicators/overview.md) - Add technical indicators to your charts +- [Drawing Tools](../features/drawing_tools/overview.md) - Enable interactive drawing tools \ No newline at end of file diff --git a/doc/getting_started/chart_types.md b/doc/getting_started/chart_types.md new file mode 100644 index 000000000..29abc9031 --- /dev/null +++ b/doc/getting_started/chart_types.md @@ -0,0 +1,281 @@ +# Chart Types + +The Deriv Chart library supports various chart types for visualizing financial data. This guide explains each chart type, its use cases, and how to implement it. + +## Overview + +The chart type is determined by the `mainSeries` parameter passed to the `Chart` widget. Each chart type is represented by a different series class: + +- `LineSeries` - Line chart +- `CandleSeries` - Candlestick chart +- `OHLCSeries` - OHLC (Open-High-Low-Close) chart +- `HollowCandleSeries` - Hollow candlestick chart + +## Line Chart + +Line charts connect data points with straight lines, showing the price movement over time. They are useful for visualizing trends and are often used for tick data or when a simpler visualization is preferred. + +### Implementation + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, +) +``` + +### Data Requirements + +Line charts require a list of `Tick` objects, each containing an `epoch` (timestamp) and a `quote` (price). + +```dart +final ticks = [ + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 5)), quote: 100), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 4)), quote: 120), + // More ticks... +]; +``` + +### Customization + +You can customize the appearance of the line chart using the `LineStyle` class: + +```dart +Chart( + mainSeries: LineSeries( + ticks, + style: LineStyle( + color: Colors.blue, + thickness: 2, + isDashed: false, + ), + ), + pipSize: 2, +) +``` + +### Example + +Line Chart Example + +## Candlestick Chart + +Candlestick charts display price movements using "candles" that show the open, high, low, and close prices. They are widely used in financial analysis as they provide a comprehensive view of price action. + +### Implementation + +```dart +Chart( + mainSeries: CandleSeries(candles), + pipSize: 2, + granularity: 60, // 60 seconds per candle +) +``` + +### Data Requirements + +Candlestick charts require a list of `Candle` objects, each containing an `epoch` (timestamp), `open`, `high`, `low`, and `close` prices. + +```dart +final candles = [ + Candle( + epoch: DateTime.now().subtract(const Duration(minutes: 5)), + open: 100, + high: 120, + low: 95, + close: 110, + ), + // More candles... +]; +``` + +### Customization + +You can customize the appearance of the candlestick chart using the `CandleStyle` class: + +```dart +Chart( + mainSeries: CandleSeries( + candles, + style: CandleStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + wickWidth: 1, + bodyWidth: 8, + ), + ), + pipSize: 2, + granularity: 60, +) +``` + +### Example + +Candlestick Chart Example + +## OHLC Chart + +OHLC (Open-High-Low-Close) charts display price movements using horizontal lines to represent the open and close prices, with vertical lines showing the high and low prices. They provide the same information as candlestick charts but in a different visual format. + +### Implementation + +```dart +Chart( + mainSeries: OHLCSeries(candles), + pipSize: 2, + granularity: 60, // 60 seconds per candle +) +``` + +### Data Requirements + +OHLC charts use the same `Candle` objects as candlestick charts. + +### Customization + +You can customize the appearance of the OHLC chart using the `OHLCStyle` class: + +```dart +Chart( + mainSeries: OHLCSeries( + candles, + style: OHLCStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + thickness: 1, + width: 8, + ), + ), + pipSize: 2, + granularity: 60, +) +``` + +## Hollow Candlestick Chart + +Hollow candlestick charts are a variation of standard candlestick charts where bullish candles (close > open) are hollow and bearish candles (close < open) are filled. This can make it easier to distinguish between bullish and bearish price movements. + +### Implementation + +```dart +Chart( + mainSeries: HollowCandleSeries(candles), + pipSize: 2, + granularity: 60, // 60 seconds per candle +) +``` + +### Data Requirements + +Hollow candlestick charts use the same `Candle` objects as standard candlestick charts. + +### Customization + +You can customize the appearance of the hollow candlestick chart using the `HollowCandleStyle` class: + +```dart +Chart( + mainSeries: HollowCandleSeries( + candles, + style: HollowCandleStyle( + positiveColor: Colors.green, + negativeColor: Colors.red, + wickWidth: 1, + bodyWidth: 8, + hollowPositiveColor: Colors.white, + ), + ), + pipSize: 2, + granularity: 60, +) +``` + +## Switching Between Chart Types + +You can dynamically switch between chart types by updating the `mainSeries` parameter: + +```dart +class ChartTypeSwitcherExample extends StatefulWidget { + const ChartTypeSwitcherExample({Key? key}) : super(key: key); + + @override + State createState() => _ChartTypeSwitcherExampleState(); +} + +class _ChartTypeSwitcherExampleState extends State { + ChartType _chartType = ChartType.candle; + final List _candles = [/* Your candle data */]; + final List _ticks = [/* Your tick data */]; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Radio( + value: ChartType.line, + groupValue: _chartType, + onChanged: (value) => setState(() => _chartType = value!), + ), + const Text('Line'), + Radio( + value: ChartType.candle, + groupValue: _chartType, + onChanged: (value) => setState(() => _chartType = value!), + ), + const Text('Candle'), + Radio( + value: ChartType.ohlc, + groupValue: _chartType, + onChanged: (value) => setState(() => _chartType = value!), + ), + const Text('OHLC'), + Radio( + value: ChartType.hollowCandle, + groupValue: _chartType, + onChanged: (value) => setState(() => _chartType = value!), + ), + const Text('Hollow Candle'), + ], + ), + Expanded( + child: Chart( + mainSeries: _getMainSeries(), + pipSize: 2, + granularity: 60, + ), + ), + ], + ); + } + + Series _getMainSeries() { + switch (_chartType) { + case ChartType.line: + return LineSeries(_ticks); + case ChartType.candle: + return CandleSeries(_candles); + case ChartType.ohlc: + return OHLCSeries(_candles); + case ChartType.hollowCandle: + return HollowCandleSeries(_candles); + } + } +} + +enum ChartType { line, candle, ohlc, hollowCandle } +``` + +### Example + +Chart Type Switching + +## Next Steps + +Now that you understand the different chart types available in the Deriv Chart library, you can explore: + +- [Configuration](configuration.md) - Learn about more configuration options +- [Indicators](../features/indicators/overview.md) - Add technical indicators to your charts +- [Drawing Tools](../features/drawing_tools/overview.md) - Enable interactive drawing tools \ No newline at end of file diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md new file mode 100644 index 000000000..5bc373001 --- /dev/null +++ b/doc/getting_started/configuration.md @@ -0,0 +1,316 @@ +# Configuration Options + +The Deriv Chart library provides numerous configuration options to customize the appearance and behavior of your charts. This guide explains the available options and how to use them. + +## Chart Widget Configuration + +The `Chart` widget accepts various parameters to customize its behavior and appearance: + +### Basic Configuration + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + granularity: 60, + theme: ChartDefaultDarkTheme(), + controller: ChartController(), +) +``` + +### Core Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `mainSeries` | `Series` | The primary data series to display (required) | +| `pipSize` | `int` | Number of decimal places for price values (default: 2) | +| `granularity` | `int` | Time interval in seconds for candles (default: 60) | +| `theme` | `ChartTheme` | Theme for the chart (default: based on app theme) | +| `controller` | `ChartController` | Controller for programmatic chart manipulation | + +### Data Series Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `overlaySeries` | `List` | Additional series to display on the main chart | +| `bottomSeries` | `List` | Series to display in separate charts below the main chart | +| `markerSeries` | `MarkerSeries` | Markers to display on the chart | +| `overlayConfigs` | `List` | Technical indicators to display on the main chart | +| `bottomConfigs` | `List` | Technical indicators to display in separate charts below the main chart | + +### Visual Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `annotations` | `List` | Annotations to display on the chart (barriers, etc.) | +| `showGrid` | `bool` | Whether to display grid lines (default: true) | +| `showLabels` | `bool` | Whether to display axis labels (default: true) | +| `showScrollButtons` | `bool` | Whether to display scroll buttons (default: false) | +| `showCrosshair` | `bool` | Whether to display crosshair on long press (default: true) | +| `showIndicatorNames` | `bool` | Whether to display indicator names (default: true) | +| `showWatermark` | `bool` | Whether to display watermark (default: false) | +| `watermarkConfig` | `WatermarkConfig` | Configuration for the watermark | + +### Callback Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `onVisibleAreaChanged` | `Function(int leftEpoch, int rightEpoch)` | Called when the visible area changes | +| `onCrosshairAppeared` | `Function()` | Called when the crosshair appears | +| `onCrosshairDisappeared` | `Function()` | Called when the crosshair disappears | +| `onTap` | `Function(TapDownDetails)` | Called when the chart is tapped | +| `onLongPressStart` | `Function(LongPressStartDetails)` | Called when a long press starts | +| `onLongPressMoveUpdate` | `Function(LongPressMoveUpdateDetails)` | Called when a long press moves | +| `onLongPressEnd` | `Function(LongPressEndDetails)` | Called when a long press ends | + +## PipSize + +The `pipSize` parameter determines the number of decimal places displayed for price values on the Y-axis. For example: + +- `pipSize: 2` displays prices as 123.45 +- `pipSize: 3` displays prices as 123.456 +- `pipSize: 0` displays prices as 123 + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 3, +) +``` + +## Granularity + +The `granularity` parameter specifies the time interval in seconds for candle charts. It affects how candles are grouped and displayed: + +- `granularity: 60` represents 1-minute candles +- `granularity: 300` represents 5-minute candles +- `granularity: 3600` represents 1-hour candles + +```dart +Chart( + mainSeries: CandleSeries(candles), + pipSize: 2, + granularity: 300, // 5-minute candles +) +``` + +## Theme Customization + +You can customize the appearance of the chart by providing a custom theme: + +```dart +class CustomTheme extends ChartDefaultDarkTheme { + @override + GridStyle get gridStyle => GridStyle( + gridLineColor: Colors.yellow, + xLabelStyle: textStyle( + textStyle: caption2, + color: Colors.yellow, + fontSize: 13, + ), + ); + + @override + CrosshairStyle get crosshairStyle => CrosshairStyle( + lineColor: Colors.orange, + labelBackgroundColor: Colors.orange.withOpacity(0.8), + labelTextStyle: textStyle( + textStyle: caption1, + color: Colors.white, + ), + ); +} + +// Using the custom theme +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + theme: CustomTheme(), +) +``` + +## Chart Controller + +The `ChartController` allows programmatic control of the chart: + +```dart +final controller = ChartController(); + +// In your widget +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + controller: controller, +) + +// Programmatically control the chart +controller.scrollToLastTick(); // Scroll to the most recent data +controller.scale(100); // Zoom the chart +controller.scrollTo(DateTime.now().subtract(const Duration(days: 1))); // Scroll to a specific time +``` + +### Available Controller Methods + +| Method | Description | +|--------|-------------| +| `scrollToLastTick()` | Scroll to the most recent data | +| `scale(double msPerPx)` | Set the zoom level | +| `scrollTo(DateTime time)` | Scroll to a specific time | +| `scrollBy(double pixels)` | Scroll by a specific number of pixels | +| `resetView()` | Reset the view to the default state | + +## Annotations + +Annotations allow you to add visual elements to the chart, such as horizontal and vertical barriers: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + HorizontalBarrier( + 125.0, + title: 'Resistance', + style: HorizontalBarrierStyle( + color: Colors.red, + isDashed: true, + lineThickness: 1, + labelBackgroundColor: Colors.red.withOpacity(0.8), + labelTextStyle: TextStyle(color: Colors.white), + ), + visibility: HorizontalBarrierVisibility.forceToStayOnRange, + ), + VerticalBarrier( + ticks[2].epoch, + title: 'Event', + style: VerticalBarrierStyle( + color: Colors.blue, + isDashed: false, + lineThickness: 1, + labelBackgroundColor: Colors.blue.withOpacity(0.8), + labelTextStyle: TextStyle(color: Colors.white), + ), + ), + TickIndicator(ticks.last), + ], +) +``` + +## Markers + +Markers allow you to highlight specific points on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries([ + Marker.up(epoch: ticks[1].epoch, quote: ticks[1].quote, onTap: () { + print('Up marker tapped'); + }), + Marker.down(epoch: ticks[3].epoch, quote: ticks[3].quote, onTap: () { + print('Down marker tapped'); + }), + ]), +) +``` + +### Active Marker + +You can also display an active marker that responds to user interactions: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [ + Marker.up(epoch: ticks[1].epoch, quote: ticks[1].quote, onTap: () {}), + Marker.down(epoch: ticks[3].epoch, quote: ticks[3].quote, onTap: () {}), + ], + activeMarker: ActiveMarker( + epoch: ticks[1].epoch, + quote: ticks[1].quote, + onTap: () { + print('Active marker tapped'); + }, + onOutsideTap: () { + print('Tapped outside active marker'); + // Remove active marker + }, + ), + ), +) +``` + +## Entry and Exit Ticks + +You can highlight entry and exit points on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [], + entryTick: Tick(epoch: ticks[0].epoch, quote: ticks[0].quote), + exitTick: Tick(epoch: ticks.last.epoch, quote: ticks.last.quote), + ), +) +``` + +## Localization + +To use chart localization, add the `ChartLocalization.delegate` to your `localizationsDelegates` inside the `MaterialApp`: + +```dart +MaterialApp( + localizationsDelegates: [ + ChartLocalization.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: [ + const Locale('en', ''), + const Locale('es', ''), + // Add other supported locales + ], + // ... +) +``` + +To change the locale of the chart: + +```dart +ChartLocalization.load(Locale('es')); +``` + +## DerivChart Configuration + +The `DerivChart` widget is a wrapper around the `Chart` widget that provides additional functionality for managing indicators and drawing tools: + +```dart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60, + activeSymbol: 'R_100', + pipSize: 4, + // All Chart parameters are also available here +) +``` + +### DerivChart Specific Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `activeSymbol` | `String` | Symbol code for saving indicator settings | +| `indicatorsRepo` | `AddOnsRepository` | Repository for managing indicators | +| `drawingToolsRepo` | `AddOnsRepository` | Repository for managing drawing tools | + +## Next Steps + +Now that you understand the configuration options available in the Deriv Chart library, you can explore: + +- [Indicators](../features/indicators/overview.md) - Add technical indicators to your charts +- [Drawing Tools](../features/drawing_tools/overview.md) - Enable interactive drawing tools +- [Advanced Usage](../advanced_usage/custom_themes.md) - Learn about advanced customization options \ No newline at end of file diff --git a/doc/getting_started/installation.md b/doc/getting_started/installation.md new file mode 100644 index 000000000..d37bf98c5 --- /dev/null +++ b/doc/getting_started/installation.md @@ -0,0 +1,112 @@ +# Installation Guide + +This guide will help you install and set up the Deriv Chart library in your Flutter project. + +## Requirements + +Before installing the Deriv Chart library, ensure your project meets the following requirements: + +- Dart SDK: >=3.0.0 <4.0.0 +- Flutter: >=3.10.1 + +## Adding the Package + +Add the Deriv Chart library to your project by including it in your `pubspec.yaml` file: + +```yaml +dependencies: + deriv_chart: ^0.3.7 # Replace with the latest version +``` + +Alternatively, you can use the Flutter CLI to add the package: + +```bash +flutter pub add deriv_chart +``` + +## Importing the Library + +After adding the package to your project, import it in your Dart files: + +```dart +import 'package:deriv_chart/deriv_chart.dart'; +``` + +## Verifying Installation + +To verify that the library is correctly installed, you can create a simple chart: + +```dart +import 'package:flutter/material.dart'; +import 'package:deriv_chart/deriv_chart.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Deriv Chart Example')), + body: Center( + child: SizedBox( + height: 300, + child: Chart( + mainSeries: LineSeries([ + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 5)), quote: 100), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 4)), quote: 120), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 3)), quote: 110), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 2)), quote: 130), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 1)), quote: 125), + Tick(epoch: DateTime.now(), quote: 140), + ]), + pipSize: 2, + ), + ), + ), + ), + ); + } +} +``` + +## Dependencies + +The Deriv Chart library depends on the following packages: + +- deriv_technical_analysis: ^1.1.1 +- provider: ^6.0.5 +- shared_preferences: ^2.1.0 +- intl: ^0.19.0 + +These dependencies are automatically installed when you add the Deriv Chart library to your project. + +## Platform-Specific Setup + +### Android + +No additional setup is required for Android. + +### iOS + +No additional setup is required for iOS. + +### Web + +For web applications, ensure that your `web/index.html` file includes the necessary viewport meta tag for proper rendering: + +```html + +``` + +## Next Steps + +Now that you have installed the Deriv Chart library, you can proceed to: + +- [Basic Usage](basic_usage.md) - Learn how to create and customize charts +- [Chart Types](chart_types.md) - Explore different chart types available +- [Configuration](configuration.md) - Understand configuration options \ No newline at end of file From a503e38e62766f66d30b7a07d649b0469267a9f6 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Wed, 30 Apr 2025 15:32:27 +0800 Subject: [PATCH 02/32] update coordinate_system.md file --- doc/README.md | 127 +++- .../performance_optimization.md | 477 ++++++++++++ doc/advanced_usage/real_time_data.md | 690 ++++++++++++++++++ doc/core_concepts/coordinate_system.md | 308 +++++++- 4 files changed, 1563 insertions(+), 39 deletions(-) create mode 100644 doc/advanced_usage/performance_optimization.md create mode 100644 doc/advanced_usage/real_time_data.md diff --git a/doc/README.md b/doc/README.md index f1ef46450..a45bec60d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,7 +1,62 @@ # Deriv Chart Documentation +
+ Deriv Chart +

A powerful, customizable Flutter charting library for financial applications

+
+ Welcome to the Deriv Chart documentation! This comprehensive guide will help you understand and use the Deriv Chart library effectively, whether you're a user of the library or a contributor to its development. +## Key Features + +- **Multiple Chart Types**: Line, Candlestick, OHLC, and Hollow Candlestick charts +- **Technical Indicators**: Built-in support for popular indicators (Moving Averages, RSI, MACD, etc.) +- **Interactive Drawing Tools**: Trend lines, Fibonacci tools, and more for technical analysis +- **Real-time Updates**: Efficient handling of streaming data +- **Customizable Themes**: Fully customizable appearance to match your app's design +- **Responsive Design**: Works across different screen sizes and orientations +- **High Performance**: Optimized rendering for smooth scrolling and zooming + +## Quick Start + +```dart +import 'package:flutter/material.dart'; +import 'package:deriv_chart/deriv_chart.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Deriv Chart Example')), + body: Center( + child: SizedBox( + height: 300, + child: Chart( + mainSeries: LineSeries([ + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 5)), quote: 100), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 4)), quote: 120), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 3)), quote: 110), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 2)), quote: 130), + Tick(epoch: DateTime.now().subtract(const Duration(minutes: 1)), quote: 125), + Tick(epoch: DateTime.now(), quote: 140), + ]), + pipSize: 2, + ), + ), + ), + ), + ); + } +} +``` + ## Table of Contents - [Getting Started](#getting-started) @@ -9,7 +64,9 @@ Welcome to the Deriv Chart documentation! This comprehensive guide will help you - [Features](#features) - [Advanced Usage](#advanced-usage) - [API Reference](#api-reference) +- [Examples](#examples) - [Contributing](#contributing) +- [Support](#support) ## Getting Started @@ -42,15 +99,15 @@ Explore the features available in the Deriv Chart library: ### Technical Analysis - [Indicators](features/indicators/overview.md) - Add technical indicators - - [Moving Averages](features/indicators/moving_averages.md) - - [Oscillators](features/indicators/oscillators.md) - - [Volatility Indicators](features/indicators/volatility.md) - - [Trend Indicators](features/indicators/trend_indicators.md) + - Moving Averages + - Oscillators + - Volatility Indicators + - Trend Indicators - [Drawing Tools](features/drawing_tools/overview.md) - Use interactive drawing tools - - [Lines and Channels](features/drawing_tools/lines_and_channels.md) - - [Fibonacci Tools](features/drawing_tools/fibonacci_tools.md) - - [Geometric Shapes](features/drawing_tools/geometric_shapes.md) + - Lines and Channels + - Fibonacci Tools + - Geometric Shapes ### Interactive Layer @@ -76,39 +133,30 @@ Detailed API documentation for the Deriv Chart library: - [Drawing Tool Classes](api_reference/drawing_tool_classes.md) - Drawing tool classes - [Theme Classes](api_reference/theme_classes.md) - Theme customization classes -## Contributing - -Learn how to contribute to the Deriv Chart library: - -- [Contribution Guidelines](contribution.md) - How to contribute -- [Development Setup](development_setup.md) - Set up your development environment -- [Code Style](code_style.md) - Follow the code style guidelines -- [Testing](testing.md) - Write and run tests - ## Examples The library includes several examples to help you get started: ### Basic Examples -- Line Chart -- Candlestick Chart -- OHLC Chart -- Hollow Candlestick Chart +- [Line Chart](../example/lib/examples/basic/line_chart_example.dart) +- [Candlestick Chart](../example/lib/examples/basic/candle_chart_example.dart) +- [OHLC Chart](../example/lib/examples/basic/ohlc_chart_example.dart) +- [Hollow Candlestick Chart](../example/lib/examples/basic/hollow_candle_example.dart) ### Feature Examples -- Chart with Indicators -- Chart with Drawing Tools -- Chart with Annotations -- Chart with Markers +- [Chart with Indicators](../example/lib/examples/features/indicators_example.dart) +- [Chart with Drawing Tools](../example/lib/examples/features/drawing_tools_example.dart) +- [Chart with Annotations](../example/lib/examples/features/annotations_example.dart) +- [Chart with Markers](../example/lib/examples/features/markers_example.dart) ### Advanced Examples -- Real-time Chart -- Custom Theme -- Custom Indicator -- Custom Drawing Tool +- [Real-time Chart](../example/lib/examples/advanced/real_time_chart_example.dart) +- [Custom Theme](../example/lib/examples/advanced/custom_theme_example.dart) +- [Custom Indicator](../example/lib/examples/advanced/custom_indicator_example.dart) +- [Custom Drawing Tool](../example/lib/examples/advanced/custom_drawing_tool_example.dart) You can find these examples in the `example` directory of the repository. @@ -116,13 +164,32 @@ You can find these examples in the `example` directory of the repository. The `showcase_app` directory contains a complete Flutter application that demonstrates all the features of the Deriv Chart library. You can use this app as a reference for your own implementation. +
+ Showcase App +
+ +## Contributing + +Learn how to contribute to the Deriv Chart library: + +- [Contribution Guidelines](contribution.md) - How to contribute +- [Development Setup](development_setup.md) - Set up your development environment +- [Code Style](code_style.md) - Follow the code style guidelines +- [Testing](testing.md) - Write and run tests + +## Compatibility + +- **Flutter**: 3.10.1 or higher +- **Dart**: 3.0.0 or higher +- **Platforms**: iOS, Android, Web, macOS, Windows, Linux + ## Support If you need help with the Deriv Chart library, you can: - Check the [FAQ](faq.md) for common questions -- Open an issue on GitHub -- Contact the maintainers +- Open an issue on [GitHub](https://github.com/your-organization/deriv-chart/issues) +- Contact the maintainers at support@example.com ## License diff --git a/doc/advanced_usage/performance_optimization.md b/doc/advanced_usage/performance_optimization.md new file mode 100644 index 000000000..6c237c5d8 --- /dev/null +++ b/doc/advanced_usage/performance_optimization.md @@ -0,0 +1,477 @@ +# Performance Optimization + +This document provides guidance on optimizing the performance of charts created with the Deriv Chart library, ensuring smooth rendering and interaction even with large datasets or on resource-constrained devices. + +## Introduction + +Financial charts often need to display large amounts of data while maintaining smooth scrolling, zooming, and interaction. The Deriv Chart library is designed with performance in mind, but there are several techniques you can use to further optimize your charts. + +## Data Management Techniques + +### Limit Visible Data + +The most effective way to improve performance is to limit the amount of data being processed and rendered: + +```dart +// Instead of passing all data to the chart +Chart( + mainSeries: LineSeries(allTicks), // Could be thousands of points + pipSize: 2, +) + +// Only pass the data you need to display +Chart( + mainSeries: LineSeries(visibleTicks), // Only the visible range + pipSize: 2, + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + // Load more data if needed + loadDataForRange(leftEpoch, rightEpoch); + }, +) +``` + +### Data Downsampling + +For very large datasets, consider downsampling the data to reduce the number of points: + +```dart +List downsampleTicks(List ticks, int targetCount) { + if (ticks.length <= targetCount) return ticks; + + final step = ticks.length / targetCount; + final result = []; + + for (int i = 0; i < ticks.length; i += step.round()) { + result.add(ticks[i]); + } + + return result; +} + +// Use downsampled data for the chart +final downsampledTicks = downsampleTicks(allTicks, 500); +Chart( + mainSeries: LineSeries(downsampledTicks), + pipSize: 2, +) +``` + +### Lazy Loading + +Implement lazy loading to fetch data only when needed: + +```dart +class LazyLoadingChartExample extends StatefulWidget { + @override + State createState() => _LazyLoadingChartExampleState(); +} + +class _LazyLoadingChartExampleState extends State { + List _ticks = []; + bool _isLoading = false; + DateTime _oldestLoadedDate = DateTime.now(); + + @override + void initState() { + super.initState(); + _loadInitialData(); + } + + Future _loadInitialData() async { + setState(() { + _isLoading = true; + }); + + // Load the most recent data + final now = DateTime.now(); + final oneWeekAgo = now.subtract(Duration(days: 7)); + + final ticks = await fetchTickData(oneWeekAgo, now); + + setState(() { + _ticks = ticks; + _oldestLoadedDate = oneWeekAgo; + _isLoading = false; + }); + } + + Future _loadMoreData(int leftEpoch) async { + if (_isLoading) return; + + final leftDate = DateTime.fromMillisecondsSinceEpoch(leftEpoch); + + // Only load more data if we're near the edge of our loaded data + if (leftDate.isAfter(_oldestLoadedDate.add(Duration(days: 1)))) return; + + setState(() { + _isLoading = true; + }); + + final newStartDate = _oldestLoadedDate.subtract(Duration(days: 7)); + final newTicks = await fetchTickData(newStartDate, _oldestLoadedDate); + + setState(() { + _ticks = [...newTicks, ..._ticks]; + _oldestLoadedDate = newStartDate; + _isLoading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + _loadMoreData(leftEpoch); + }, + ), + if (_isLoading) + Positioned( + left: 16, + top: 16, + child: CircularProgressIndicator(), + ), + ], + ); + } + + Future> fetchTickData(DateTime start, DateTime end) async { + // Implement your data fetching logic here + // This could be an API call, database query, etc. + await Future.delayed(Duration(milliseconds: 500)); // Simulate network delay + return generateMockTickData(start, end); + } + + List generateMockTickData(DateTime start, DateTime end) { + // Generate mock data for demonstration + final result = []; + var current = start; + double price = 100 + Random().nextDouble() * 10; + + while (current.isBefore(end)) { + price += (Random().nextDouble() - 0.5) * 2; + result.add(Tick(epoch: current, quote: price)); + current = current.add(Duration(minutes: 5)); + } + + return result; + } +} +``` + +## Rendering Optimizations + +### Use RepaintBoundary + +Wrap your chart in a `RepaintBoundary` to isolate its rendering from the rest of your UI: + +```dart +RepaintBoundary( + child: Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + ), +) +``` + +### Optimize Chart Size + +Larger charts require more rendering resources. Consider using a smaller chart size or implementing responsive sizing: + +```dart +LayoutBuilder( + builder: (context, constraints) { + // Limit the chart height based on available space + final chartHeight = min(constraints.maxHeight, 400.0); + + return SizedBox( + height: chartHeight, + child: Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + ), + ); + }, +) +``` + +### Reduce Indicator Count + +Each indicator adds computational and rendering overhead. Limit the number of indicators displayed simultaneously: + +```dart +// Instead of many indicators +Chart( + mainSeries: CandleSeries(candles), + overlayConfigs: [ + BollingerBandsIndicatorConfig(), + MAIndicatorConfig(period: 14), + MAIndicatorConfig(period: 50), + MAIndicatorConfig(period: 200), + ], + bottomConfigs: [ + RSIIndicatorConfig(), + MACDIndicatorConfig(), + StochasticIndicatorConfig(), + ], + pipSize: 2, +) + +// Limit to the most important indicators +Chart( + mainSeries: CandleSeries(candles), + overlayConfigs: [ + MAIndicatorConfig(period: 50), + ], + bottomConfigs: [ + RSIIndicatorConfig(), + ], + pipSize: 2, +) +``` + +### Simplify Drawing Tools + +Complex drawing tools can impact performance. Use simpler tools or limit the number of active drawing tools: + +```dart +// Implement a limit on the number of drawing tools +class LimitedDrawingToolsExample extends StatefulWidget { + @override + State createState() => _LimitedDrawingToolsExampleState(); +} + +class _LimitedDrawingToolsExampleState extends State { + final _drawingToolsRepo = AddOnsRepository( + createAddOn: (Map map) => DrawingToolConfig.fromJson(map), + onEditCallback: (int index) {}, + sharedPrefKey: 'drawing_tools', + ); + + static const int _maxDrawingTools = 10; + + @override + Widget build(BuildContext context) { + return DerivChart( + mainSeries: CandleSeries(candles), + drawingToolsRepo: _drawingToolsRepo, + onDrawingToolAdded: (DrawingToolConfig config) { + // Check if we've reached the limit + if (_drawingToolsRepo.items.length > _maxDrawingTools) { + // Remove the oldest drawing tool + _drawingToolsRepo.removeAt(0); + + // Show a notification + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Maximum number of drawing tools reached. Oldest tool removed.'), + ), + ); + } + }, + ); + } +} +``` + +## Device-Specific Optimizations + +### Detect Device Capabilities + +Adjust chart complexity based on device capabilities: + +```dart +bool isLowEndDevice() { + // This is a simplified example. In a real app, you would use more + // sophisticated detection based on device model, available memory, etc. + return Platform.isAndroid && + (defaultTargetPlatform == TargetPlatform.android) && + !kIsWeb; +} + +Widget buildChartBasedOnDevice() { + final isLowEnd = isLowEndDevice(); + + return Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + showGrid: !isLowEnd, // Disable grid on low-end devices + showCrosshair: !isLowEnd, // Disable crosshair on low-end devices + // Reduce other visual elements for low-end devices + ); +} +``` + +### Optimize for Web + +When deploying to web, consider additional optimizations: + +```dart +bool isWebPlatform() { + return kIsWeb; +} + +Widget buildChartForWeb() { + final isWeb = isWebPlatform(); + + // For web, use a more aggressive data downsampling + final optimizedTicks = isWeb ? downsampleTicks(ticks, 300) : ticks; + + return Chart( + mainSeries: LineSeries(optimizedTicks), + pipSize: 2, + ); +} +``` + +## Memory Management + +### Dispose Controllers + +Always dispose of chart controllers when they're no longer needed: + +```dart +class ChartScreenState extends State { + final _chartController = ChartController(); + + @override + void dispose() { + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + controller: _chartController, + ); + } +} +``` + +### Clear Unused Data + +Clear data that's no longer needed to free up memory: + +```dart +void clearOldData() { + // Keep only the last 1000 ticks + if (allTicks.length > 1000) { + allTicks = allTicks.sublist(allTicks.length - 1000); + } +} +``` + +## Measuring Performance + +### Use Performance Overlay + +Flutter's performance overlay can help identify rendering issues: + +```dart +MaterialApp( + showPerformanceOverlay: true, + home: Scaffold( + body: Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + ), + ), +) +``` + +### Profile with DevTools + +Use Flutter DevTools to profile your app and identify performance bottlenecks: + +1. Run your app in debug mode +2. Open DevTools +3. Use the Performance tab to record and analyze performance + +### Custom Performance Metrics + +Implement custom performance metrics to track chart performance: + +```dart +class PerformanceMetricsExample extends StatefulWidget { + @override + State createState() => _PerformanceMetricsExampleState(); +} + +class _PerformanceMetricsExampleState extends State { + int _frameCount = 0; + double _averageFrameTime = 0; + Stopwatch _stopwatch = Stopwatch(); + + @override + void initState() { + super.initState(); + _stopwatch.start(); + + // Set up a timer to calculate FPS + Timer.periodic(Duration(seconds: 1), (timer) { + if (mounted) { + setState(() { + _averageFrameTime = _frameCount > 0 + ? _stopwatch.elapsedMilliseconds / _frameCount + : 0; + _frameCount = 0; + _stopwatch.reset(); + _stopwatch.start(); + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + onPaint: () { + // Increment frame count each time the chart is painted + _frameCount++; + }, + ), + Positioned( + top: 16, + right: 16, + child: Container( + padding: EdgeInsets.all(8), + color: Colors.black.withOpacity(0.7), + child: Text( + 'Avg frame time: ${_averageFrameTime.toStringAsFixed(2)} ms', + style: TextStyle(color: Colors.white), + ), + ), + ), + ], + ); + } +} +``` + +## Best Practices + +1. **Start simple and add complexity as needed**: Begin with a basic chart and add features incrementally +2. **Test on target devices**: Always test performance on the actual devices your users will use +3. **Monitor memory usage**: Keep an eye on memory usage, especially with large datasets +4. **Use efficient data structures**: Choose appropriate data structures for your use case +5. **Implement pagination**: For historical data, implement pagination to load data in chunks +6. **Optimize UI updates**: Minimize setState calls and use more granular state management +7. **Consider using compute for heavy calculations**: Move heavy calculations to a separate isolate + +## Next Steps + +Now that you understand how to optimize the performance of your charts, you can explore: + +- [Real-time Data](real_time_data.md) - Learn how to handle real-time data updates +- [Custom Indicators](custom_indicators.md) - Create efficient custom indicators +- [Custom Drawing Tools](custom_drawing_tools.md) - Implement optimized drawing tools \ No newline at end of file diff --git a/doc/advanced_usage/real_time_data.md b/doc/advanced_usage/real_time_data.md new file mode 100644 index 000000000..5fbecb49e --- /dev/null +++ b/doc/advanced_usage/real_time_data.md @@ -0,0 +1,690 @@ +# Real-time Data + +This document explains how to handle real-time data updates in the Deriv Chart library, enabling you to create dynamic charts that update as new market data arrives. + +## Introduction + +Financial charts often need to display real-time data, such as live market prices or streaming data feeds. The Deriv Chart library provides several mechanisms to efficiently handle real-time data updates without sacrificing performance. + +## Basic Real-time Updates + +### Updating the Data Series + +The simplest way to handle real-time data is to update the data series when new data arrives: + +```dart +class RealTimeChartExample extends StatefulWidget { + @override + State createState() => _RealTimeChartExampleState(); +} + +class _RealTimeChartExampleState extends State { + List _ticks = []; + late StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + + // Subscribe to a data stream + _subscription = tickDataStream.listen((tick) { + setState(() { + _ticks.add(tick); + + // Optional: Limit the number of ticks to prevent memory issues + if (_ticks.length > 1000) { + _ticks = _ticks.sublist(_ticks.length - 1000); + } + }); + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + ); + } +} +``` + +### Auto-scrolling to Latest Data + +To ensure the chart automatically scrolls to show the latest data: + +```dart +class AutoScrollingChartExample extends StatefulWidget { + @override + State createState() => _AutoScrollingChartExampleState(); +} + +class _AutoScrollingChartExampleState extends State { + List _ticks = []; + late StreamSubscription _subscription; + final _chartController = ChartController(); + bool _autoScroll = true; + + @override + void initState() { + super.initState(); + + // Subscribe to a data stream + _subscription = tickDataStream.listen((tick) { + setState(() { + _ticks.add(tick); + + // Optional: Limit the number of ticks to prevent memory issues + if (_ticks.length > 1000) { + _ticks = _ticks.sublist(_ticks.length - 1000); + } + + // Auto-scroll to the latest tick if enabled + if (_autoScroll) { + _chartController.scrollToLastTick(); + } + }); + }); + } + + @override + void dispose() { + _subscription.cancel(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text('Auto-scroll'), + Switch( + value: _autoScroll, + onChanged: (value) { + setState(() { + _autoScroll = value; + + // If auto-scroll is re-enabled, scroll to the latest tick + if (_autoScroll) { + _chartController.scrollToLastTick(); + } + }); + }, + ), + ], + ), + Expanded( + child: Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + controller: _chartController, + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + // Disable auto-scroll if the user manually scrolls away from the latest data + if (_autoScroll && _ticks.isNotEmpty) { + final latestEpoch = _ticks.last.epoch.millisecondsSinceEpoch; + final rightEdgeTime = DateTime.fromMillisecondsSinceEpoch(rightEpoch); + + // If the latest tick is not visible, disable auto-scroll + if (latestEpoch > rightEpoch) { + setState(() { + _autoScroll = false; + }); + } + } + }, + ), + ), + ], + ); + } +} +``` + +## Handling Different Data Types + +### Real-time Tick Data + +For streaming tick data (time-price pairs): + +```dart +class RealTimeTickChartExample extends StatefulWidget { + @override + State createState() => _RealTimeTickChartExampleState(); +} + +class _RealTimeTickChartExampleState extends State { + List _ticks = []; + late StreamSubscription _subscription; + final _chartController = ChartController(); + + @override + void initState() { + super.initState(); + + // Subscribe to a tick data stream + _subscription = tickDataStream.listen((tickData) { + setState(() { + _ticks.add(Tick( + epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), + quote: tickData.price, + )); + + _chartController.scrollToLastTick(); + }); + }); + } + + @override + void dispose() { + _subscription.cancel(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + controller: _chartController, + ); + } +} +``` + +### Real-time OHLC Data + +For streaming OHLC (candle) data: + +```dart +class RealTimeCandleChartExample extends StatefulWidget { + @override + State createState() => _RealTimeCandleChartExampleState(); +} + +class _RealTimeCandleChartExampleState extends State { + List _candles = []; + Candle? _currentCandle; + late StreamSubscription _subscription; + final _chartController = ChartController(); + final int _granularity = 60; // 1-minute candles + + @override + void initState() { + super.initState(); + + // Subscribe to a tick data stream + _subscription = tickDataStream.listen((tickData) { + final tickTime = DateTime.fromMillisecondsSinceEpoch(tickData.timestamp); + final tickPrice = tickData.price; + + setState(() { + // Update or create the current candle + if (_currentCandle == null || _isNewCandlePeriod(tickTime)) { + // If we have a current candle, add it to the list + if (_currentCandle != null) { + _candles.add(_currentCandle!); + } + + // Create a new candle + _currentCandle = Candle( + epoch: _normalizeTime(tickTime), + open: tickPrice, + high: tickPrice, + low: tickPrice, + close: tickPrice, + ); + } else { + // Update the current candle + _currentCandle = Candle( + epoch: _currentCandle!.epoch, + open: _currentCandle!.open, + high: max(_currentCandle!.high, tickPrice), + low: min(_currentCandle!.low, tickPrice), + close: tickPrice, + ); + } + + _chartController.scrollToLastTick(); + }); + }); + } + + bool _isNewCandlePeriod(DateTime time) { + if (_currentCandle == null) return true; + + final currentPeriodStart = _currentCandle!.epoch; + final currentPeriodEnd = currentPeriodStart.add(Duration(seconds: _granularity)); + + return time.isAfter(currentPeriodEnd) || time.isAtSameMomentAs(currentPeriodEnd); + } + + DateTime _normalizeTime(DateTime time) { + // Normalize time to the start of the candle period + final seconds = (time.second ~/ _granularity) * _granularity; + return DateTime( + time.year, + time.month, + time.day, + time.hour, + time.minute, + seconds, + ); + } + + @override + void dispose() { + _subscription.cancel(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // Create a copy of the candles list with the current candle + final displayCandles = [..._candles]; + if (_currentCandle != null) { + displayCandles.add(_currentCandle!); + } + + return Chart( + mainSeries: CandleSeries(displayCandles), + pipSize: 2, + granularity: _granularity, + controller: _chartController, + ); + } +} +``` + +## Optimizing Real-time Updates + +### Throttling Updates + +To prevent excessive UI updates, consider throttling the data updates: + +```dart +class ThrottledUpdateChartExample extends StatefulWidget { + @override + State createState() => _ThrottledUpdateChartExampleState(); +} + +class _ThrottledUpdateChartExampleState extends State { + List _ticks = []; + List _pendingTicks = []; + late StreamSubscription _subscription; + Timer? _updateTimer; + final _chartController = ChartController(); + + @override + void initState() { + super.initState(); + + // Subscribe to a tick data stream + _subscription = tickDataStream.listen((tickData) { + final tick = Tick( + epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), + quote: tickData.price, + ); + + // Add to pending ticks + _pendingTicks.add(tick); + + // Set up a timer to update the UI if not already set + _updateTimer ??= Timer(Duration(milliseconds: 100), _updateChart); + }); + } + + void _updateChart() { + if (_pendingTicks.isEmpty) return; + + setState(() { + _ticks.addAll(_pendingTicks); + _pendingTicks.clear(); + + // Optional: Limit the number of ticks + if (_ticks.length > 1000) { + _ticks = _ticks.sublist(_ticks.length - 1000); + } + + _chartController.scrollToLastTick(); + }); + + _updateTimer = null; + } + + @override + void dispose() { + _subscription.cancel(); + _updateTimer?.cancel(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + controller: _chartController, + ); + } +} +``` + +### Using ValueNotifier + +For more efficient updates, consider using `ValueNotifier` instead of `setState`: + +```dart +class ValueNotifierChartExample extends StatefulWidget { + @override + State createState() => _ValueNotifierChartExampleState(); +} + +class _ValueNotifierChartExampleState extends State { + final ValueNotifier> _ticksNotifier = ValueNotifier>([]); + late StreamSubscription _subscription; + final _chartController = ChartController(); + + @override + void initState() { + super.initState(); + + // Subscribe to a tick data stream + _subscription = tickDataStream.listen((tickData) { + final tick = Tick( + epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), + quote: tickData.price, + ); + + // Update the notifier value + final ticks = [..._ticksNotifier.value, tick]; + + // Optional: Limit the number of ticks + if (ticks.length > 1000) { + _ticksNotifier.value = ticks.sublist(ticks.length - 1000); + } else { + _ticksNotifier.value = ticks; + } + + _chartController.scrollToLastTick(); + }); + } + + @override + void dispose() { + _subscription.cancel(); + _ticksNotifier.dispose(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder>( + valueListenable: _ticksNotifier, + builder: (context, ticks, _) { + return Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + controller: _chartController, + ); + }, + ); + } +} +``` + +## Handling WebSocket Connections + +For real-time data from WebSocket connections: + +```dart +class WebSocketChartExample extends StatefulWidget { + @override + State createState() => _WebSocketChartExampleState(); +} + +class _WebSocketChartExampleState extends State { + List _ticks = []; + WebSocketChannel? _channel; + final _chartController = ChartController(); + bool _isConnected = false; + + @override + void initState() { + super.initState(); + _connectWebSocket(); + } + + void _connectWebSocket() { + _channel = WebSocketChannel.connect( + Uri.parse('wss://your-websocket-endpoint.com'), + ); + + setState(() { + _isConnected = true; + }); + + _channel!.stream.listen( + (data) { + // Parse the data + final jsonData = jsonDecode(data); + final tickData = TickData.fromJson(jsonData); + + setState(() { + _ticks.add(Tick( + epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), + quote: tickData.price, + )); + + // Optional: Limit the number of ticks + if (_ticks.length > 1000) { + _ticks = _ticks.sublist(_ticks.length - 1000); + } + + _chartController.scrollToLastTick(); + }); + }, + onDone: () { + setState(() { + _isConnected = false; + }); + + // Attempt to reconnect after a delay + Future.delayed(Duration(seconds: 5), _connectWebSocket); + }, + onError: (error) { + setState(() { + _isConnected = false; + }); + + // Attempt to reconnect after a delay + Future.delayed(Duration(seconds: 5), _connectWebSocket); + }, + ); + + // Send subscription message + _channel!.sink.add(jsonEncode({ + 'type': 'subscribe', + 'symbol': 'BTCUSD', + })); + } + + @override + void dispose() { + _channel?.sink.close(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + padding: EdgeInsets.all(8), + color: _isConnected ? Colors.green : Colors.red, + child: Text( + _isConnected ? 'Connected' : 'Disconnected', + style: TextStyle(color: Colors.white), + ), + ), + Expanded( + child: Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + controller: _chartController, + ), + ), + ], + ); + } +} +``` + +## Handling Historical and Real-time Data Together + +To display historical data and append real-time updates: + +```dart +class HistoricalAndRealTimeChartExample extends StatefulWidget { + @override + State createState() => _HistoricalAndRealTimeChartExampleState(); +} + +class _HistoricalAndRealTimeChartExampleState extends State { + List _ticks = []; + late StreamSubscription _subscription; + final _chartController = ChartController(); + bool _isLoading = true; + + @override + void initState() { + super.initState(); + + // Load historical data first + _loadHistoricalData().then((_) { + // Then subscribe to real-time updates + _subscribeToRealTimeData(); + }); + } + + Future _loadHistoricalData() async { + try { + // Fetch historical data + final historicalData = await fetchHistoricalData(); + + setState(() { + _ticks = historicalData.map((data) => Tick( + epoch: DateTime.fromMillisecondsSinceEpoch(data.timestamp), + quote: data.price, + )).toList(); + + _isLoading = false; + }); + } catch (e) { + setState(() { + _isLoading = false; + }); + + // Show error + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to load historical data: $e')), + ); + } + } + + void _subscribeToRealTimeData() { + _subscription = tickDataStream.listen((tickData) { + // Check if this tick is newer than our latest historical tick + final tickTime = DateTime.fromMillisecondsSinceEpoch(tickData.timestamp); + + if (_ticks.isEmpty || tickTime.isAfter(_ticks.last.epoch)) { + setState(() { + _ticks.add(Tick( + epoch: tickTime, + quote: tickData.price, + )); + + _chartController.scrollToLastTick(); + }); + } + }); + } + + @override + void dispose() { + _subscription.cancel(); + _chartController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_isLoading) { + return Center(child: CircularProgressIndicator()); + } + + return Chart( + mainSeries: LineSeries(_ticks), + pipSize: 2, + controller: _chartController, + ); + } + + Future> fetchHistoricalData() async { + // Implement your historical data fetching logic here + await Future.delayed(Duration(seconds: 1)); // Simulate network delay + return generateMockHistoricalData(); + } + + List generateMockHistoricalData() { + // Generate mock historical data + final now = DateTime.now(); + final result = []; + double price = 100; + + for (int i = 0; i < 100; i++) { + final time = now.subtract(Duration(minutes: 100 - i)); + price += (Random().nextDouble() - 0.5) * 2; + + result.add(TickData( + timestamp: time.millisecondsSinceEpoch, + price: price, + )); + } + + return result; + } +} +``` + +## Best Practices + +1. **Limit data points**: Keep the number of data points reasonable to maintain performance +2. **Throttle updates**: Don't update the UI on every tick; batch updates for better performance +3. **Handle connection issues**: Implement reconnection logic for WebSocket connections +4. **Provide visual feedback**: Show connection status and loading indicators +5. **Optimize memory usage**: Remove old data points that are no longer needed +6. **Use appropriate data structures**: Choose efficient data structures for your use case +7. **Test with realistic data rates**: Test with realistic data rates to ensure your app can handle the load + +## Next Steps + +Now that you understand how to handle real-time data in the Deriv Chart library, you can explore: + +- [Performance Optimization](performance_optimization.md) - Optimize chart performance +- [Custom Indicators](custom_indicators.md) - Create custom indicators +- [Custom Drawing Tools](custom_drawing_tools.md) - Implement custom drawing tools \ No newline at end of file diff --git a/doc/core_concepts/coordinate_system.md b/doc/core_concepts/coordinate_system.md index c77fbd211..92fca6ed3 100644 --- a/doc/core_concepts/coordinate_system.md +++ b/doc/core_concepts/coordinate_system.md @@ -8,21 +8,50 @@ The Deriv Chart library uses a coordinate system that maps: - Time (epochs) to X-coordinates on the screen - Price (quotes) to Y-coordinates on the screen +``` +Y-axis (Price) + ^ + | + | • (epoch2, quote2) + | + | • (epoch1, quote1) + | + | + +---------------------------------> X-axis (Time) +``` + This mapping is essential for: - Rendering data points at the correct positions - Handling user interactions (taps, drags) - Implementing scrolling and zooming - Supporting crosshair functionality +- Enabling drawing tools and annotations ## X-Axis Coordinate System -The X-axis represents time and is managed by the `XAxisWrapper` component. +The X-axis represents time and is managed by the `XAxisWrapper` component, which serves as the foundation for horizontal positioning across the entire chart. ### Key Concepts 1. **rightBoundEpoch**: The timestamp at the right edge of the chart 2. **msPerPx**: Milliseconds per pixel (zoom level) 3. **leftBoundEpoch**: The timestamp at the left edge of the chart +4. **visibleTimeRange**: The time range currently visible on the chart + +``` + leftBoundEpoch rightBoundEpoch + | | + v v + +------------------+-----------------------------------+ + | | | + | Past (not | Visible Area | Future (not + | visible) | | visible) + | | | + +------------------+----------------------------------+ + |<------- screenWidth (px) ------->| + |<------ visibleTimeRange -------->| + | (screenWidth * msPerPx) | +``` ### Calculations @@ -30,6 +59,7 @@ The relationship between these values is: ``` leftBoundEpoch = rightBoundEpoch - screenWidth * msPerPx +visibleTimeRange = screenWidth * msPerPx ``` Where: @@ -67,6 +97,12 @@ void scrollBy(double pixels) { } ``` +#### Edge Cases and Constraints + +- **Live Data**: When displaying live data, the `rightBoundEpoch` may be constrained to the current time or the latest data point +- **Historical Limits**: The chart may impose limits on how far back in time users can scroll +- **Smooth Scrolling**: The chart implements momentum-based scrolling for a natural feel + ### Zooming Zooming is implemented by changing the `msPerPx`: @@ -74,23 +110,35 @@ Zooming is implemented by changing the `msPerPx`: - Zooming out: Increase `msPerPx` (more milliseconds per pixel) ```dart -void scale(double newMsPerPx) { - // Calculate the center point of the visible area - final centerEpoch = (rightBoundEpoch + leftBoundEpoch) ~/ 2; +void scale(double newMsPerPx, {double? focalPointX}) { + // If no focal point is provided, use the center of the chart + final double focusX = focalPointX ?? width / 2; + + // Calculate the epoch at the focal point + final focalEpoch = epochFromX(focusX); // Update msPerPx msPerPx = newMsPerPx; - // Recalculate rightBoundEpoch to keep the center point fixed - rightBoundEpoch = centerEpoch + (width * msPerPx / 2).toInt(); + // Recalculate rightBoundEpoch to keep the focal point at the same screen position + rightBoundEpoch = focalEpoch.millisecondsSinceEpoch + ((width - focusX) * msPerPx).toInt(); notifyListeners(); } ``` +#### Zoom Constraints + +The chart implements min and max zoom levels to ensure usability: + +```dart +// Constrain msPerPx to reasonable limits +msPerPx = math.max(minMsPerPx, math.min(maxMsPerPx, msPerPx)); +``` + ## Y-Axis Coordinate System -The Y-axis represents price and is managed by each chart component (MainChart and BottomCharts) independently. +The Y-axis represents price and is managed by each chart component (MainChart and BottomCharts) independently, allowing different scales for different types of data. ### Key Concepts @@ -98,6 +146,26 @@ The Y-axis represents price and is managed by each chart component (MainChart an 2. **bottomBoundQuote**: The minimum price in the visible area 3. **topPadding** and **bottomPadding**: Padding to add above and below the data 4. **quotePerPx**: Price units per pixel +5. **visibleQuoteRange**: The price range currently visible on the chart + +``` + ^ + | topPadding + +------------------+ + | | + | topBoundQuote | + | | + | | + | Visible | + | Quote Range | + | | + | | + | bottomBoundQuote| + | | + +------------------+ + | bottomPadding | + v +``` ### Calculations @@ -105,6 +173,7 @@ The relationship between these values is: ``` quotePerPx = (topBoundQuote - bottomBoundQuote) / (height - topPadding - bottomPadding) +visibleQuoteRange = topBoundQuote - bottomBoundQuote ``` Where: @@ -161,9 +230,30 @@ void updateYAxisBounds() { } ``` +#### Auto-Scaling and Fixed Scaling + +The chart supports both auto-scaling and fixed scaling modes: + +- **Auto-scaling**: The Y-axis automatically adjusts to fit the visible data +- **Fixed scaling**: The Y-axis maintains a fixed range regardless of the visible data + +```dart +void setFixedYAxisBounds(double min, double max) { + isFixedYAxis = true; + bottomBoundQuote = min; + topBoundQuote = max; + updateQuotePerPx(); +} + +void resetToAutoYAxisBounds() { + isFixedYAxis = false; + updateYAxisBounds(); +} +``` + ## Grid System -The grid system uses the coordinate system to place grid lines and labels at appropriate intervals. +The grid system uses the coordinate system to place grid lines and labels at appropriate intervals, enhancing readability and providing visual reference points. ### X-Axis Grid @@ -193,6 +283,26 @@ List gridTimestamps() { } ``` +#### Dynamic Time Intervals + +The chart dynamically selects appropriate time intervals based on the zoom level: + +```dart +TimeInterval timeGridInterval() { + // Calculate the minimum distance between grid lines in pixels + const minDistanceBetweenLines = 60.0; + + // Calculate the time range that corresponds to the minimum distance + final minTimeRange = minDistanceBetweenLines * msPerPx; + + // Select the appropriate interval + if (minTimeRange < 60000) return TimeInterval.minute; + if (minTimeRange < 3600000) return TimeInterval.hour; + if (minTimeRange < 86400000) return TimeInterval.day; + return TimeInterval.month; +} +``` + ### Y-Axis Grid The Y-axis grid is determined by: @@ -218,6 +328,73 @@ List gridQuotes() { } ``` +#### Dynamic Price Intervals + +The chart dynamically selects appropriate price intervals based on the visible range and chart height: + +```dart +double quoteGridInterval() { + // Calculate the minimum distance between grid lines in pixels + const minDistanceBetweenLines = 40.0; + + // Calculate the quote range that corresponds to the minimum distance + final minQuoteRange = minDistanceBetweenLines * quotePerPx; + + // Select the appropriate interval from predefined options + final intervals = [0.00001, 0.0001, 0.001, 0.01, 0.1, 0.25, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500, 1000]; + + for (final interval in intervals) { + if (interval >= minQuoteRange) { + return interval; + } + } + + return intervals.last; +} +``` + +## Performance Considerations + +### Clipping and Culling + +For optimal performance, the chart only renders data points that are within the visible area: + +```dart +bool isVisible(DateTime epoch, double quote) { + final epochMs = epoch.millisecondsSinceEpoch; + return epochMs >= leftBoundEpoch && + epochMs <= rightBoundEpoch && + quote >= bottomBoundQuote && + quote <= topBoundQuote; +} +``` + +### Efficient Rendering + +The chart uses efficient rendering techniques to handle large datasets: + +1. **Data Decimation**: When zoomed out, the chart may reduce the number of points rendered +2. **Incremental Rendering**: For very large datasets, the chart may render incrementally +3. **Canvas Optimization**: The chart uses optimized Canvas drawing operations + +```dart +void optimizedRenderDataPoints(List points) { + // Skip points that are too close together on screen + double lastX = -double.infinity; + const minXDistance = 1.0; // Minimum distance in pixels + + for (final point in points) { + final x = xFromEpoch(point.epoch); + + if (x - lastX >= minXDistance) { + final y = yFromQuote(point.quote); + canvas.drawCircle(Offset(x, y), 2, paint); + lastX = x; + } + } +} +``` + ## Coordinate System in Action ### Plotting Data Points @@ -305,16 +482,128 @@ void onTap(TapDownDetails details) { } ``` +### Drawing Tools and Annotations + +Drawing tools and annotations use the coordinate system to position themselves on the chart: + +```dart +void drawHorizontalLine(Canvas canvas, double quote) { + final y = yFromQuote(quote); + + canvas.drawLine( + Offset(0, y), + Offset(width, y), + Paint() + ..color = Colors.red + ..strokeWidth = 1 + ..style = PaintingStyle.stroke, + ); +} + +void drawVerticalLine(Canvas canvas, DateTime epoch) { + final x = xFromEpoch(epoch); + + canvas.drawLine( + Offset(x, 0), + Offset(x, height), + Paint() + ..color = Colors.blue + ..strokeWidth = 1 + ..style = PaintingStyle.stroke, + ); +} +``` + ## Shared X-Axis, Independent Y-Axes The Deriv Chart library uses a shared X-axis for all charts (MainChart and BottomCharts) but independent Y-axes: +``` ++------------------------------------------+ +| | +| MainChart (Y-axis for price data) | +| | ++------------------------------------------+ +| | +| BottomChart 1 (Y-axis for RSI) | +| | ++------------------------------------------+ +| | +| BottomChart 2 (Y-axis for MACD) | +| | ++------------------------------------------+ + ^ + | + Shared X-axis +``` + 1. The `XAxisWrapper` provides a single `XAxisModel` that is shared by all charts 2. Each chart (MainChart and BottomCharts) has its own Y-axis calculations This allows: - Synchronized scrolling and zooming across all charts - Independent Y-axis scaling for each chart based on its data range +- Consistent time alignment across all indicators + +### Implementation Details + +```dart +class Chart extends StatelessWidget { + final XAxisModel xAxisModel; + final MainChart mainChart; + final List bottomCharts; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Main chart with its own Y-axis + Expanded( + flex: 3, + child: mainChart.withXAxisModel(xAxisModel), + ), + + // Bottom charts, each with its own Y-axis + ...bottomCharts.map((chart) => + Expanded( + flex: 1, + child: chart.withXAxisModel(xAxisModel), + ), + ), + ], + ); + } +} +``` + +## Real-World Applications + +### Time-Series Analysis + +The coordinate system enables sophisticated time-series analysis: + +- **Trend Lines**: Drawing lines connecting significant points to identify trends +- **Support/Resistance Levels**: Horizontal lines at key price levels +- **Moving Averages**: Smoothed lines showing average prices over time +- **Fibonacci Retracements**: Horizontal lines at key Fibonacci levels + +### Interactive Features + +The coordinate system powers interactive features: + +- **Crosshair**: Shows precise price and time at the cursor position +- **Tooltips**: Display data details at specific points +- **Selection**: Allows users to select specific time ranges for analysis +- **Zoom to Selection**: Enables users to zoom into a specific area of interest + +## Integration with Other Components + +The coordinate system integrates with other components of the Deriv Chart library: + +- **Interactive Layer**: Uses the coordinate system to position drawing tools +- **Data Series**: Converts data points to screen coordinates for rendering +- **Indicators**: Calculate values based on data and render using the coordinate system +- **Annotations**: Position themselves on the chart using the coordinate system ## Next Steps @@ -322,4 +611,5 @@ Now that you understand the coordinate system used in the Deriv Chart library, y - [Rendering Pipeline](rendering_pipeline.md) - Learn how data is rendered on the canvas - [Architecture](architecture.md) - Understand the overall architecture of the library -- [API Reference](../api_reference/chart_widget.md) - Explore the complete API \ No newline at end of file +- [Data Models](data_models.md) - Explore the data models used in the library +- [Interactive Layer](../features/drawing_tools/overview.md) - Learn about the interactive drawing tools \ No newline at end of file From 26a33ac73f637a924674e64dfc5654ee09b78e5c Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Wed, 30 Apr 2025 16:03:21 +0800 Subject: [PATCH 03/32] update documentation --- doc/core_concepts/coordinate_system.md | 381 +------------------------ 1 file changed, 14 insertions(+), 367 deletions(-) diff --git a/doc/core_concepts/coordinate_system.md b/doc/core_concepts/coordinate_system.md index 92fca6ed3..495325561 100644 --- a/doc/core_concepts/coordinate_system.md +++ b/doc/core_concepts/coordinate_system.md @@ -70,19 +70,10 @@ Where: The `XAxisModel` provides functions to convert between epochs and X-coordinates: -```dart -// Convert epoch to X-coordinate -double xFromEpoch(DateTime epoch) { - return width - (rightBoundEpoch - epoch.millisecondsSinceEpoch) / msPerPx; -} - -// Convert X-coordinate to epoch -DateTime epochFromX(double x) { - return DateTime.fromMillisecondsSinceEpoch( - rightBoundEpoch - ((width - x) * msPerPx).toInt(), - ); -} -``` +- **xFromEpoch**: Converts a timestamp (epoch) to an X-coordinate on the screen +- **epochFromX**: Converts an X-coordinate on the screen to a timestamp (epoch) + +These conversion functions are essential for mapping data points to screen positions and interpreting user interactions. ### Scrolling @@ -90,12 +81,7 @@ Scrolling is implemented by changing the `rightBoundEpoch`: - Scrolling right (into the past): Decrease `rightBoundEpoch` - Scrolling left (into the future): Increase `rightBoundEpoch` -```dart -void scrollBy(double pixels) { - rightBoundEpoch -= (pixels * msPerPx).toInt(); - notifyListeners(); -} -``` +The `scrollBy` method adjusts the `rightBoundEpoch` based on the number of pixels scrolled, converting pixels to milliseconds using the current `msPerPx` value. #### Edge Cases and Constraints @@ -109,32 +95,11 @@ Zooming is implemented by changing the `msPerPx`: - Zooming in: Decrease `msPerPx` (fewer milliseconds per pixel) - Zooming out: Increase `msPerPx` (more milliseconds per pixel) -```dart -void scale(double newMsPerPx, {double? focalPointX}) { - // If no focal point is provided, use the center of the chart - final double focusX = focalPointX ?? width / 2; - - // Calculate the epoch at the focal point - final focalEpoch = epochFromX(focusX); - - // Update msPerPx - msPerPx = newMsPerPx; - - // Recalculate rightBoundEpoch to keep the focal point at the same screen position - rightBoundEpoch = focalEpoch.millisecondsSinceEpoch + ((width - focusX) * msPerPx).toInt(); - - notifyListeners(); -} -``` +The `scale` method updates the `msPerPx` value while maintaining the position of a focal point (typically the center of the chart or the point under the user's finger). This ensures that the chart zooms in or out around the expected point. #### Zoom Constraints -The chart implements min and max zoom levels to ensure usability: - -```dart -// Constrain msPerPx to reasonable limits -msPerPx = math.max(minMsPerPx, math.min(maxMsPerPx, msPerPx)); -``` +The chart implements min and max zoom levels to ensure usability. The `msPerPx` value is constrained between minimum and maximum values to prevent users from zooming too far in or out. ## Y-Axis Coordinate System @@ -184,17 +149,10 @@ Where: The `BasicChart` provides functions to convert between quotes and Y-coordinates: -```dart -// Convert quote to Y-coordinate -double yFromQuote(double quote) { - return height - bottomPadding - (quote - bottomBoundQuote) / quotePerPx; -} +- **yFromQuote**: Converts a price value (quote) to a Y-coordinate on the screen +- **quoteFromY**: Converts a Y-coordinate on the screen to a price value (quote) -// Convert Y-coordinate to quote -double quoteFromY(double y) { - return bottomBoundQuote + (height - bottomPadding - y) * quotePerPx; -} -``` +These conversion functions are essential for mapping data points to screen positions and interpreting user interactions. ### Y-Axis Scaling @@ -203,316 +161,32 @@ The Y-axis scale is determined by: 2. Adding padding to ensure data doesn't touch the edges 3. Adjusting for a consistent scale when animating -```dart -void updateYAxisBounds() { - // Find min and max values in visible data - double minValue = double.infinity; - double maxValue = -double.infinity; - - for (final series in allSeries) { - if (!series.minValue.isNaN && series.minValue < minValue) { - minValue = series.minValue; - } - if (!series.maxValue.isNaN && series.maxValue > maxValue) { - maxValue = series.maxValue; - } - } - - // Add padding - final range = maxValue - minValue; - final paddingAmount = range * 0.1; // 10% padding - - topBoundQuote = maxValue + paddingAmount; - bottomBoundQuote = minValue - paddingAmount; - - // Update quotePerPx - quotePerPx = (topBoundQuote - bottomBoundQuote) / (height - topPadding - bottomPadding); -} -``` - -#### Auto-Scaling and Fixed Scaling - -The chart supports both auto-scaling and fixed scaling modes: - -- **Auto-scaling**: The Y-axis automatically adjusts to fit the visible data -- **Fixed scaling**: The Y-axis maintains a fixed range regardless of the visible data - -```dart -void setFixedYAxisBounds(double min, double max) { - isFixedYAxis = true; - bottomBoundQuote = min; - topBoundQuote = max; - updateQuotePerPx(); -} - -void resetToAutoYAxisBounds() { - isFixedYAxis = false; - updateYAxisBounds(); -} -``` - ## Grid System The grid system uses the coordinate system to place grid lines and labels at appropriate intervals, enhancing readability and providing visual reference points. -### X-Axis Grid - -The X-axis grid is determined by: -1. Calculating the appropriate time interval based on the zoom level -2. Generating timestamps at regular intervals between `leftBoundEpoch` and `rightBoundEpoch` -3. Converting these timestamps to X-coordinates - -```dart -List gridTimestamps() { - final interval = timeGridInterval(); - final result = []; - - // Start from the leftmost visible timestamp aligned to the interval - DateTime current = alignToInterval( - DateTime.fromMillisecondsSinceEpoch(leftBoundEpoch), - interval, - ); - - // Generate timestamps until we reach the right edge - while (current.millisecondsSinceEpoch <= rightBoundEpoch) { - result.add(current); - current = addInterval(current, interval); - } - - return result; -} -``` - -#### Dynamic Time Intervals - -The chart dynamically selects appropriate time intervals based on the zoom level: - -```dart -TimeInterval timeGridInterval() { - // Calculate the minimum distance between grid lines in pixels - const minDistanceBetweenLines = 60.0; - - // Calculate the time range that corresponds to the minimum distance - final minTimeRange = minDistanceBetweenLines * msPerPx; - - // Select the appropriate interval - if (minTimeRange < 60000) return TimeInterval.minute; - if (minTimeRange < 3600000) return TimeInterval.hour; - if (minTimeRange < 86400000) return TimeInterval.day; - return TimeInterval.month; -} -``` - -### Y-Axis Grid - -The Y-axis grid is determined by: -1. Calculating the appropriate price interval based on the visible range -2. Generating price values at regular intervals between `bottomBoundQuote` and `topBoundQuote` -3. Converting these price values to Y-coordinates - -```dart -List gridQuotes() { - final interval = quoteGridInterval(); - final result = []; - - // Start from the bottom aligned to the interval - double current = (bottomBoundQuote / interval).floor() * interval; - - // Generate quotes until we reach the top - while (current <= topBoundQuote) { - result.add(current); - current += interval; - } - - return result; -} -``` - -#### Dynamic Price Intervals - -The chart dynamically selects appropriate price intervals based on the visible range and chart height: - -```dart -double quoteGridInterval() { - // Calculate the minimum distance between grid lines in pixels - const minDistanceBetweenLines = 40.0; - - // Calculate the quote range that corresponds to the minimum distance - final minQuoteRange = minDistanceBetweenLines * quotePerPx; - - // Select the appropriate interval from predefined options - final intervals = [0.00001, 0.0001, 0.001, 0.01, 0.1, 0.25, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500, 1000]; - - for (final interval in intervals) { - if (interval >= minQuoteRange) { - return interval; - } - } - - return intervals.last; -} -``` - ## Performance Considerations ### Clipping and Culling For optimal performance, the chart only renders data points that are within the visible area: -```dart -bool isVisible(DateTime epoch, double quote) { - final epochMs = epoch.millisecondsSinceEpoch; - return epochMs >= leftBoundEpoch && - epochMs <= rightBoundEpoch && - quote >= bottomBoundQuote && - quote <= topBoundQuote; -} -``` +The `isVisible` method checks if a data point is within the visible area of the chart. It compares the epoch and quote values against the current bounds to determine visibility. -### Efficient Rendering - -The chart uses efficient rendering techniques to handle large datasets: - -1. **Data Decimation**: When zoomed out, the chart may reduce the number of points rendered -2. **Incremental Rendering**: For very large datasets, the chart may render incrementally -3. **Canvas Optimization**: The chart uses optimized Canvas drawing operations - -```dart -void optimizedRenderDataPoints(List points) { - // Skip points that are too close together on screen - double lastX = -double.infinity; - const minXDistance = 1.0; // Minimum distance in pixels - - for (final point in points) { - final x = xFromEpoch(point.epoch); - - if (x - lastX >= minXDistance) { - final y = yFromQuote(point.quote); - canvas.drawCircle(Offset(x, y), 2, paint); - lastX = x; - } - } -} -``` ## Coordinate System in Action -### Plotting Data Points - -To plot a data point (epoch, quote) on the canvas: - -```dart -void plotPoint(Canvas canvas, DateTime epoch, double quote) { - final x = xFromEpoch(epoch); - final y = yFromQuote(quote); - - canvas.drawCircle(Offset(x, y), 3, Paint()..color = Colors.blue); -} -``` - -### Drawing Lines - -To draw a line between two data points: - -```dart -void drawLine(Canvas canvas, DateTime epoch1, double quote1, DateTime epoch2, double quote2) { - final x1 = xFromEpoch(epoch1); - final y1 = yFromQuote(quote1); - final x2 = xFromEpoch(epoch2); - final y2 = yFromQuote(quote2); - - canvas.drawLine( - Offset(x1, y1), - Offset(x2, y2), - Paint() - ..color = Colors.blue - ..strokeWidth = 2, - ); -} -``` - -### Drawing Candles - -To draw a candle: - -```dart -void drawCandle(Canvas canvas, Candle candle) { - final x = xFromEpoch(candle.epoch); - final yOpen = yFromQuote(candle.open); - final yHigh = yFromQuote(candle.high); - final yLow = yFromQuote(candle.low); - final yClose = yFromQuote(candle.close); - - // Draw wick - canvas.drawLine( - Offset(x, yHigh), - Offset(x, yLow), - Paint() - ..color = Colors.black - ..strokeWidth = 1, - ); - - // Draw body - final bodyPaint = Paint() - ..color = candle.isBullish ? Colors.green : Colors.red; - - canvas.drawRect( - Rect.fromPoints( - Offset(x - 4, yOpen), - Offset(x + 4, yClose), - ), - bodyPaint, - ); -} -``` - ### Handling User Interactions To convert a user tap to a data point: -```dart -void onTap(TapDownDetails details) { - final x = details.localPosition.dx; - final y = details.localPosition.dy; - - final epoch = epochFromX(x); - final quote = quoteFromY(y); - - print('Tapped at epoch: $epoch, quote: $quote'); -} -``` +The `onTap` method converts screen coordinates from a tap event to epoch and quote values using the coordinate conversion functions. This allows the chart to determine which data point the user tapped on. ### Drawing Tools and Annotations Drawing tools and annotations use the coordinate system to position themselves on the chart: -```dart -void drawHorizontalLine(Canvas canvas, double quote) { - final y = yFromQuote(quote); - - canvas.drawLine( - Offset(0, y), - Offset(width, y), - Paint() - ..color = Colors.red - ..strokeWidth = 1 - ..style = PaintingStyle.stroke, - ); -} - -void drawVerticalLine(Canvas canvas, DateTime epoch) { - final x = xFromEpoch(epoch); - - canvas.drawLine( - Offset(x, 0), - Offset(x, height), - Paint() - ..color = Colors.blue - ..strokeWidth = 1 - ..style = PaintingStyle.stroke, - ); -} -``` +The `drawHorizontalLine` and `drawVerticalLine` methods demonstrate how to draw horizontal and vertical lines at specific price levels or timestamps. These are used for barriers, support/resistance levels, and other annotations. ## Shared X-Axis, Independent Y-Axes @@ -547,34 +221,7 @@ This allows: ### Implementation Details -```dart -class Chart extends StatelessWidget { - final XAxisModel xAxisModel; - final MainChart mainChart; - final List bottomCharts; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - // Main chart with its own Y-axis - Expanded( - flex: 3, - child: mainChart.withXAxisModel(xAxisModel), - ), - - // Bottom charts, each with its own Y-axis - ...bottomCharts.map((chart) => - Expanded( - flex: 1, - child: chart.withXAxisModel(xAxisModel), - ), - ), - ], - ); - } -} -``` +The `Chart` widget organizes the MainChart and BottomCharts in a vertical column. Each chart receives the same XAxisModel instance, ensuring that they share the same X-axis viewport. Each chart maintains its own Y-axis calculations based on its specific data. ## Real-World Applications From 0c31929ce91142e2a96615eed8854184b0936c38 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 11:50:22 +0800 Subject: [PATCH 04/32] remove platform specific installation guid --- doc/getting_started/installation.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/doc/getting_started/installation.md b/doc/getting_started/installation.md index d37bf98c5..629818b09 100644 --- a/doc/getting_started/installation.md +++ b/doc/getting_started/installation.md @@ -85,24 +85,6 @@ The Deriv Chart library depends on the following packages: These dependencies are automatically installed when you add the Deriv Chart library to your project. -## Platform-Specific Setup - -### Android - -No additional setup is required for Android. - -### iOS - -No additional setup is required for iOS. - -### Web - -For web applications, ensure that your `web/index.html` file includes the necessary viewport meta tag for proper rendering: - -```html - -``` - ## Next Steps Now that you have installed the Deriv Chart library, you can proceed to: From 9ee3d5226d70c18ae1451b3b712610d95eac9992 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 11:58:27 +0800 Subject: [PATCH 05/32] minor change in docs --- doc/getting_started/basic_usage.md | 7 ++++--- doc/getting_started/chart_types.md | 15 ++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/getting_started/basic_usage.md b/doc/getting_started/basic_usage.md index 154591f22..566dcb14e 100644 --- a/doc/getting_started/basic_usage.md +++ b/doc/getting_started/basic_usage.md @@ -92,6 +92,7 @@ The Deriv Chart library supports several chart types: Chart( mainSeries: LineSeries(ticks), pipSize: 2, + granularity: 60000, // 60000 milliseconds (1 minute) interval ) ``` @@ -101,7 +102,7 @@ Chart( Chart( mainSeries: CandleSeries(candles), pipSize: 2, - granularity: 60, // 60 seconds per candle + granularity: 60000, // 60000 milliseconds (1 minute) interval per candle ) ``` @@ -111,7 +112,7 @@ Chart( Chart( mainSeries: OHLCSeries(candles), pipSize: 2, - granularity: 60, // 60 seconds per candle + granularity: 60000, // 60000 milliseconds (1 minute) interval per candle ) ``` @@ -121,7 +122,7 @@ Chart( Chart( mainSeries: HollowCandleSeries(candles), pipSize: 2, - granularity: 60, // 60 seconds per candle + granularity: 60000, // 60000 milliseconds (1 minute) interval per candle ) ``` diff --git a/doc/getting_started/chart_types.md b/doc/getting_started/chart_types.md index 29abc9031..020504a26 100644 --- a/doc/getting_started/chart_types.md +++ b/doc/getting_started/chart_types.md @@ -21,6 +21,7 @@ Line charts connect data points with straight lines, showing the price movement Chart( mainSeries: LineSeries(ticks), pipSize: 2, + granularity: 60000, // 60000 milliseconds (1 minute) interval ) ``` @@ -68,7 +69,7 @@ Candlestick charts display price movements using "candles" that show the open, h Chart( mainSeries: CandleSeries(candles), pipSize: 2, - granularity: 60, // 60 seconds per candle + granularity: 60000, // 60000 milliseconds (1 minute) interval per candle ) ``` @@ -105,7 +106,7 @@ Chart( ), ), pipSize: 2, - granularity: 60, + granularity: 60000, // 60000 milliseconds (1 minute) interval ) ``` @@ -123,7 +124,7 @@ OHLC (Open-High-Low-Close) charts display price movements using horizontal lines Chart( mainSeries: OHLCSeries(candles), pipSize: 2, - granularity: 60, // 60 seconds per candle + granularity: 60000, // 60000 milliseconds (1 minute) interval per candle ) ``` @@ -147,7 +148,7 @@ Chart( ), ), pipSize: 2, - granularity: 60, + granularity: 60000, // 60000 milliseconds (1 minute) interval ) ``` @@ -161,7 +162,7 @@ Hollow candlestick charts are a variation of standard candlestick charts where b Chart( mainSeries: HollowCandleSeries(candles), pipSize: 2, - granularity: 60, // 60 seconds per candle + granularity: 60000, // 60000 milliseconds (1 minute) interval per candle ) ``` @@ -186,7 +187,7 @@ Chart( ), ), pipSize: 2, - granularity: 60, + granularity: 60000, // 60000 milliseconds (1 minute) interval ) ``` @@ -244,7 +245,7 @@ class _ChartTypeSwitcherExampleState extends State { child: Chart( mainSeries: _getMainSeries(), pipSize: 2, - granularity: 60, + granularity: 60000, // 60000 milliseconds (1 minute) interval ), ), ], From 666a146853a101ae6b462ff8aca338a58411f1b7 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 12:02:38 +0800 Subject: [PATCH 06/32] minor change in docs --- doc/getting_started/basic_usage.md | 3 +-- doc/getting_started/chart_types.md | 14 +++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/doc/getting_started/basic_usage.md b/doc/getting_started/basic_usage.md index 566dcb14e..1a6f4a027 100644 --- a/doc/getting_started/basic_usage.md +++ b/doc/getting_started/basic_usage.md @@ -139,8 +139,7 @@ Chart( style: CandleStyle( positiveColor: Colors.green, negativeColor: Colors.red, - wickWidth: 1, - bodyWidth: 8, + neutralColor: Colors.grey, ), ), pipSize: 2, diff --git a/doc/getting_started/chart_types.md b/doc/getting_started/chart_types.md index 020504a26..efabe3949 100644 --- a/doc/getting_started/chart_types.md +++ b/doc/getting_started/chart_types.md @@ -101,8 +101,7 @@ Chart( style: CandleStyle( positiveColor: Colors.green, negativeColor: Colors.red, - wickWidth: 1, - bodyWidth: 8, + neutralColor: Colors.grey, ), ), pipSize: 2, @@ -140,11 +139,10 @@ You can customize the appearance of the OHLC chart using the `OHLCStyle` class: Chart( mainSeries: OHLCSeries( candles, - style: OHLCStyle( + style: CandleStyle( positiveColor: Colors.green, negativeColor: Colors.red, - thickness: 1, - width: 8, + neutralColor: Colors.grey, ), ), pipSize: 2, @@ -178,12 +176,10 @@ You can customize the appearance of the hollow candlestick chart using the `Holl Chart( mainSeries: HollowCandleSeries( candles, - style: HollowCandleStyle( + style: CandleStyle( positiveColor: Colors.green, negativeColor: Colors.red, - wickWidth: 1, - bodyWidth: 8, - hollowPositiveColor: Colors.white, + neutralColor: Colors.grey, ), ), pipSize: 2, From bbe40f2ae86063e0987f7df527d18eb589bb4f29 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 13:17:59 +0800 Subject: [PATCH 07/32] minor change in docs --- doc/getting_started/configuration.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md index 5bc373001..91ce960cb 100644 --- a/doc/getting_started/configuration.md +++ b/doc/getting_started/configuration.md @@ -12,7 +12,7 @@ The `Chart` widget accepts various parameters to customize its behavior and appe Chart( mainSeries: LineSeries(ticks), pipSize: 2, - granularity: 60, + granularity: 60000, // 60000 milliseconds (1 minute) theme: ChartDefaultDarkTheme(), controller: ChartController(), ) @@ -24,7 +24,7 @@ Chart( |-----------|------|-------------| | `mainSeries` | `Series` | The primary data series to display (required) | | `pipSize` | `int` | Number of decimal places for price values (default: 2) | -| `granularity` | `int` | Time interval in seconds for candles (default: 60) | +| `granularity` | `int` | Time interval in milliseconds for candles or average ms difference between consecutive ticks (default: 60000) | | `theme` | `ChartTheme` | Theme for the chart (default: based on app theme) | | `controller` | `ChartController` | Controller for programmatic chart manipulation | @@ -43,13 +43,11 @@ Chart( | Parameter | Type | Description | |-----------|------|-------------| | `annotations` | `List` | Annotations to display on the chart (barriers, etc.) | -| `showGrid` | `bool` | Whether to display grid lines (default: true) | -| `showLabels` | `bool` | Whether to display axis labels (default: true) | -| `showScrollButtons` | `bool` | Whether to display scroll buttons (default: false) | | `showCrosshair` | `bool` | Whether to display crosshair on long press (default: true) | -| `showIndicatorNames` | `bool` | Whether to display indicator names (default: true) | -| `showWatermark` | `bool` | Whether to display watermark (default: false) | -| `watermarkConfig` | `WatermarkConfig` | Configuration for the watermark | +| `showDataFitButton` | `bool` | Whether to display the data fit button (default: true) | +| `showScrollToLastTickButton` | `bool` | Whether to display scroll to last tick button (default: true) | +| `loadingAnimationColor` | `Color` | The color of the loading animation | +| `chartAxisConfig` | `ChartAxisConfig` | Configuration for chart axes including grid and labels | ### Callback Parameters @@ -80,17 +78,17 @@ Chart( ## Granularity -The `granularity` parameter specifies the time interval in seconds for candle charts. It affects how candles are grouped and displayed: +The `granularity` parameter specifies the time interval in milliseconds for candle charts. It affects how candles are grouped and displayed: -- `granularity: 60` represents 1-minute candles -- `granularity: 300` represents 5-minute candles -- `granularity: 3600` represents 1-hour candles +- `granularity: 60000` represents 1-minute candles +- `granularity: 300000` represents 5-minute candles +- `granularity: 3600000` represents 1-hour candles ```dart Chart( mainSeries: CandleSeries(candles), pipSize: 2, - granularity: 300, // 5-minute candles + granularity: 300000, // 5-minute candles (300000 milliseconds) ) ``` From c2ff4779e61855b376189050c5dd2b004bdd3332 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 13:19:58 +0800 Subject: [PATCH 08/32] minor change in docs --- doc/getting_started/configuration.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md index 91ce960cb..fe436b20b 100644 --- a/doc/getting_started/configuration.md +++ b/doc/getting_started/configuration.md @@ -53,13 +53,11 @@ Chart( | Parameter | Type | Description | |-----------|------|-------------| -| `onVisibleAreaChanged` | `Function(int leftEpoch, int rightEpoch)` | Called when the visible area changes | -| `onCrosshairAppeared` | `Function()` | Called when the crosshair appears | -| `onCrosshairDisappeared` | `Function()` | Called when the crosshair disappears | -| `onTap` | `Function(TapDownDetails)` | Called when the chart is tapped | -| `onLongPressStart` | `Function(LongPressStartDetails)` | Called when a long press starts | -| `onLongPressMoveUpdate` | `Function(LongPressMoveUpdateDetails)` | Called when a long press moves | -| `onLongPressEnd` | `Function(LongPressEndDetails)` | Called when a long press ends | +| `onVisibleAreaChanged` | `Function(int leftEpoch, int rightEpoch)` | Called when the visible area changes due to scrolling or zooming | +| `onQuoteAreaChanged` | `Function(double minQuote, double maxQuote)` | Called when the visible quote area changes | +| `onCrosshairAppeared` | `VoidCallback` | Called when the crosshair appears | +| `onCrosshairDisappeared` | `VoidCallback` | Called when the crosshair disappears | +| `onCrosshairHover` | `OnCrosshairHoverCallback` | Called when the crosshair cursor is hovered/moved | ## PipSize From 148a30e7fe11ddc38950a1dbe8b2120e0b3bc24a Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 13:24:22 +0800 Subject: [PATCH 09/32] minor change in docs --- doc/getting_started/configuration.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md index fe436b20b..93bb863af 100644 --- a/doc/getting_started/configuration.md +++ b/doc/getting_started/configuration.md @@ -142,18 +142,16 @@ Chart( // Programmatically control the chart controller.scrollToLastTick(); // Scroll to the most recent data controller.scale(100); // Zoom the chart -controller.scrollTo(DateTime.now().subtract(const Duration(days: 1))); // Scroll to a specific time +controller.scroll(100); // Scroll by 100 pixels ``` ### Available Controller Methods | Method | Description | |--------|-------------| -| `scrollToLastTick()` | Scroll to the most recent data | -| `scale(double msPerPx)` | Set the zoom level | -| `scrollTo(DateTime time)` | Scroll to a specific time | -| `scrollBy(double pixels)` | Scroll by a specific number of pixels | -| `resetView()` | Reset the view to the default state | +| `scrollToLastTick({bool animate = false})` | Scroll to the most recent data with optional animation | +| `scale(double scale)` | Set the zoom level | +| `scroll(double pxShift)` | Scroll by a specific number of pixels | ## Annotations From a9b9c6c04ffaef87fb2863246538e8d582a49df0 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 13:47:43 +0800 Subject: [PATCH 10/32] minor change in docs --- doc/getting_started/configuration.md | 50 +++++++++------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md index 93bb863af..c59a59b89 100644 --- a/doc/getting_started/configuration.md +++ b/doc/getting_started/configuration.md @@ -168,9 +168,11 @@ Chart( style: HorizontalBarrierStyle( color: Colors.red, isDashed: true, - lineThickness: 1, - labelBackgroundColor: Colors.red.withOpacity(0.8), - labelTextStyle: TextStyle(color: Colors.white), + titleBackgroundColor: Colors.red.withOpacity(0.8), + textStyle: TextStyle(color: Colors.white), + labelShape: LabelShape.rectangle, + labelHeight: 24, + labelPadding: 4, ), visibility: HorizontalBarrierVisibility.forceToStayOnRange, ), @@ -180,12 +182,18 @@ Chart( style: VerticalBarrierStyle( color: Colors.blue, isDashed: false, - lineThickness: 1, - labelBackgroundColor: Colors.blue.withOpacity(0.8), - labelTextStyle: TextStyle(color: Colors.white), + titleBackgroundColor: Colors.blue.withOpacity(0.8), + textStyle: TextStyle(color: Colors.white), + labelPosition: VerticalBarrierLabelPosition.auto, ), ), - TickIndicator(ticks.last), + TickIndicator( + ticks.last, + style: HorizontalBarrierStyle( + labelShape: LabelShape.pentagon, + ), + visibility: HorizontalBarrierVisibility.normal, + ), ], ) ``` @@ -253,32 +261,6 @@ Chart( ) ``` -## Localization - -To use chart localization, add the `ChartLocalization.delegate` to your `localizationsDelegates` inside the `MaterialApp`: - -```dart -MaterialApp( - localizationsDelegates: [ - ChartLocalization.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: [ - const Locale('en', ''), - const Locale('es', ''), - // Add other supported locales - ], - // ... -) -``` - -To change the locale of the chart: - -```dart -ChartLocalization.load(Locale('es')); -``` - ## DerivChart Configuration The `DerivChart` widget is a wrapper around the `Chart` widget that provides additional functionality for managing indicators and drawing tools: @@ -286,7 +268,7 @@ The `DerivChart` widget is a wrapper around the `Chart` widget that provides add ```dart DerivChart( mainSeries: CandleSeries(candles), - granularity: 60, + granularity: 60000, // 60000 milliseconds (1 minute) activeSymbol: 'R_100', pipSize: 4, // All Chart parameters are also available here From 7d1442d8474f36da77a84bb771917283706fb9c2 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 10 Jun 2025 15:34:31 +0800 Subject: [PATCH 11/32] minor change in docs --- doc/core_concepts/coordinate_system.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/doc/core_concepts/coordinate_system.md b/doc/core_concepts/coordinate_system.md index 495325561..45f727240 100644 --- a/doc/core_concepts/coordinate_system.md +++ b/doc/core_concepts/coordinate_system.md @@ -223,26 +223,6 @@ This allows: The `Chart` widget organizes the MainChart and BottomCharts in a vertical column. Each chart receives the same XAxisModel instance, ensuring that they share the same X-axis viewport. Each chart maintains its own Y-axis calculations based on its specific data. -## Real-World Applications - -### Time-Series Analysis - -The coordinate system enables sophisticated time-series analysis: - -- **Trend Lines**: Drawing lines connecting significant points to identify trends -- **Support/Resistance Levels**: Horizontal lines at key price levels -- **Moving Averages**: Smoothed lines showing average prices over time -- **Fibonacci Retracements**: Horizontal lines at key Fibonacci levels - -### Interactive Features - -The coordinate system powers interactive features: - -- **Crosshair**: Shows precise price and time at the cursor position -- **Tooltips**: Display data details at specific points -- **Selection**: Allows users to select specific time ranges for analysis -- **Zoom to Selection**: Enables users to zoom into a specific area of interest - ## Integration with Other Components The coordinate system integrates with other components of the Deriv Chart library: From b8ecf7f552978baf87938159b794067d17034fb8 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Thu, 19 Jun 2025 17:01:03 +0800 Subject: [PATCH 12/32] add using indicators and tools to configuration section --- doc/getting_started/configuration.md | 224 ++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 1 deletion(-) diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md index c59a59b89..39baed62c 100644 --- a/doc/getting_started/configuration.md +++ b/doc/getting_started/configuration.md @@ -151,7 +151,229 @@ controller.scroll(100); // Scroll by 100 pixels |--------|-------------| | `scrollToLastTick({bool animate = false})` | Scroll to the most recent data with optional animation | | `scale(double scale)` | Set the zoom level | -| `scroll(double pxShift)` | Scroll by a specific number of pixels | + +## Using Indicators + +The Deriv Chart library provides support for technical indicators that can be displayed either on the main chart (overlay) or in separate charts below the main chart. There are two ways to add indicators to your charts: + +### 1. Direct Configuration with Chart Widget + +You can directly pass indicator configurations to the `Chart` widget: + +```dart +Chart( + mainSeries: CandleSeries(candles), + pipSize: 2, + granularity: 60000, + // Indicators displayed on the main chart + overlayConfigs: [ + BollingerBandsIndicatorConfig( + period: 20, + deviation: 2, + maType: MAType.sma, + ), + ], + // Indicators displayed in separate charts below the main chart + bottomConfigs: [ + RSIIndicatorConfig( + period: 14, + overbought: 70, + oversold: 30, + ), + SMIIndicatorConfig( + period: 14, + signalPeriod: 3, + ), + ], +) +``` + +### 2. Using AddOnsRepository with DerivChart Widget + +For more advanced usage, including saving indicator settings, you can use the `DerivChart` widget with an `AddOnsRepository`: + +```dart +// Create a repository for indicators +final indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + sharedPrefKey: 'R_100', // Use the symbol code for saving settings +); + +// Add indicators to the repository +indicatorsRepo.add(BollingerBandsIndicatorConfig( + period: 20, + deviation: 2, + maType: MAType.sma, +)); + +indicatorsRepo.add(RSIIndicatorConfig( + period: 14, + overbought: 70, + oversold: 30, +)); + +// Use the repository with DerivChart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + indicatorsRepo: indicatorsRepo, +) +``` + +### Repository Management in DerivChart + +The `DerivChart` widget provides flexible options for managing indicators and drawing tools: + +#### Automatic Repository Management + +If you don't provide repositories, `DerivChart` will: +- Automatically instantiate its own `indicatorsRepo` and `drawingToolsRepo` +- Display UI icons over the chart to open built-in dialogs for adding, editing, and removing indicators and drawing tools +- Handle saving and loading configurations using the `activeSymbol` as the storage key +- Manage all UI interactions internally + +```dart +// DerivChart with automatic repository management +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + // No indicatorsRepo or drawingToolsRepo provided +) +``` + +#### External Repository Management + +If you provide your own repositories, `DerivChart` will: +- Use the provided repositories instead of creating its own +- Not display the built-in UI icons, assuming you'll handle UI interactions externally +- Still apply the indicators and drawing tools from the repositories to the chart + +```dart +// DerivChart with external repository management +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + indicatorsRepo: myIndicatorsRepo, // Externally managed repository + drawingToolsRepo: myDrawingToolsRepo, // Externally managed repository +) +``` + +When using external repositories, you're responsible for: +- Initializing the repositories +- Adding, editing, and removing items from the repositories +- Providing your own UI for managing indicators and drawing tools if needed + +### Real-World Example + +Here's how indicators are used in a complete application, similar to the example app: + +```dart +class ChartScreen extends StatefulWidget { + @override + _ChartScreenState createState() => _ChartScreenState(); +} + +class _ChartScreenState extends State { + final List candles = []; // Your data source + final ChartController controller = ChartController(); + late AddOnsRepository indicatorsRepo; + + @override + void initState() { + super.initState(); + + // Initialize indicators repository + indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + sharedPrefKey: 'R_100', + ); + + // Load saved indicators or add default ones + _loadIndicators(); + + // Load chart data + _fetchChartData(); + } + + void _loadIndicators() { + // Add default indicators if none are saved + if (indicatorsRepo.items.isEmpty) { + indicatorsRepo.add(BollingerBandsIndicatorConfig( + period: 20, + deviation: 2, + maType: MAType.sma, + )); + + indicatorsRepo.add(RSIIndicatorConfig( + period: 14, + overbought: 70, + oversold: 30, + )); + } + } + + void _fetchChartData() { + // Fetch your chart data here + // When data is received, update the state + setState(() { + // Update candles with new data + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Chart with Indicators')), + body: candles.isEmpty + ? Center(child: CircularProgressIndicator()) + : DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + controller: controller, + indicatorsRepo: indicatorsRepo, + isLive: true, + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + // Load more historical data if needed + if (leftEpoch < candles.first.epoch) { + _loadMoreHistory(); + } + }, + ), + ); + } + + void _loadMoreHistory() { + // Load more historical data + } +} +``` + +The `DerivChart` widget automatically: +- Filters indicators into overlay and bottom indicators based on their `isOverlay` property +- Provides UI buttons for adding, editing, and removing indicators +- Saves indicator configurations to persistent storage +- Loads saved indicators when the chart is initialized + +### Available Indicators + +The library includes many built-in indicators such as: + +| Indicator | Type | Description | +|-----------|------|-------------| +| Bollinger Bands | Overlay | Shows volatility and potential price levels | +| Moving Average | Overlay | Smooths price data to show trends | +| RSI | Bottom | Relative Strength Index measures momentum | +| MACD | Bottom | Moving Average Convergence Divergence | +| Stochastic Oscillator | Bottom | Compares closing price to price range | +| Awesome Oscillator | Bottom | Shows market momentum | ## Annotations From 397118b82eb504b86ae370b12991e0e0e2fcddce Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Thu, 19 Jun 2025 18:06:43 +0800 Subject: [PATCH 13/32] update doc --- doc/README.md | 1 + doc/getting_started/advanced_features.md | 502 +++++++++++++++++++++++ doc/getting_started/basic_usage.md | 3 +- doc/getting_started/configuration.md | 357 +--------------- 4 files changed, 524 insertions(+), 339 deletions(-) create mode 100644 doc/getting_started/advanced_features.md diff --git a/doc/README.md b/doc/README.md index a45bec60d..a617d72ff 100644 --- a/doc/README.md +++ b/doc/README.md @@ -76,6 +76,7 @@ If you're new to the Deriv Chart library, start here to learn the basics: - [Basic Usage](getting_started/basic_usage.md) - Create your first chart - [Chart Types](getting_started/chart_types.md) - Learn about different chart types - [Configuration](getting_started/configuration.md) - Understand configuration options +- [Advanced Features](getting_started/advanced_features.md) - Learn about indicators, annotations, markers, and drawing tools ## Core Concepts diff --git a/doc/getting_started/advanced_features.md b/doc/getting_started/advanced_features.md new file mode 100644 index 000000000..11b74aa00 --- /dev/null +++ b/doc/getting_started/advanced_features.md @@ -0,0 +1,502 @@ +# Advanced Features + +This guide covers advanced features of the Deriv Chart library, including technical indicators, markers, annotations, and drawing tools. + +## Technical Indicators + +The Deriv Chart library provides support for technical indicators that can be displayed either on the main chart (overlay) or in separate charts below the main chart. + +### Adding Indicators to Chart Widget + +You can directly add indicators to the `Chart` widget using the `overlayConfigs` and `bottomConfigs` parameters: + +```dart +Chart( + mainSeries: CandleSeries(candles), + pipSize: 2, + granularity: 60000, + // Indicators displayed on the main chart + overlayConfigs: [ + BollingerBandsIndicatorConfig( + period: 20, + deviation: 2, + maType: MAType.sma, + ), + ], + // Indicators displayed in separate charts below the main chart + bottomConfigs: [ + RSIIndicatorConfig( + period: 14, + overbought: 70, + oversold: 30, + ), + SMIIndicatorConfig( + period: 14, + signalPeriod: 3, + ), + ], +) +``` + +### Using DerivChart for Indicator Management + +For more advanced usage, including saving indicator settings, you can use the `DerivChart` widget with an `AddOnsRepository`: + +```dart +// Create a repository for indicators +final indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + sharedPrefKey: 'R_100', // Use the symbol code for saving settings +); + +// Add indicators to the repository +indicatorsRepo.add(BollingerBandsIndicatorConfig( + period: 20, + deviation: 2, + maType: MAType.sma, +)); + +indicatorsRepo.add(RSIIndicatorConfig( + period: 14, + overbought: 70, + oversold: 30, +)); + +// Use the repository with DerivChart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + indicatorsRepo: indicatorsRepo, +) +``` + +### Automatic Repository Management + +If you don't provide repositories to `DerivChart`, it will: +- Automatically instantiate its own `indicatorsRepo` and `drawingToolsRepo` +- Display UI icons over the chart to open built-in dialogs for adding, editing, and removing indicators and drawing tools +- Handle saving and loading configurations using the `activeSymbol` as the storage key +- Manage all UI interactions internally + +```dart +// DerivChart with automatic repository management +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + // No indicatorsRepo or drawingToolsRepo provided +) +``` + +### External Repository Management + +If you provide your own repositories, `DerivChart` will: +- Use the provided repositories instead of creating its own +- Not display the built-in UI icons, assuming you'll handle UI interactions externally +- Still apply the indicators and drawing tools from the repositories to the chart + +```dart +// DerivChart with external repository management +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + indicatorsRepo: myIndicatorsRepo, // Externally managed repository + drawingToolsRepo: myDrawingToolsRepo, // Externally managed repository +) +``` + +When using external repositories, you're responsible for: +- Initializing the repositories +- Adding, editing, and removing items from the repositories +- Providing your own UI for managing indicators and drawing tools if needed + +### Available Indicators + +The library includes many built-in indicators such as: + +| Indicator | Type | Description | +|-----------|------|-------------| +| Bollinger Bands | Overlay | Shows volatility and potential price levels | +| Moving Average | Overlay | Smooths price data to show trends | +| RSI | Bottom | Relative Strength Index measures momentum | +| MACD | Bottom | Moving Average Convergence Divergence | +| Stochastic Oscillator | Bottom | Compares closing price to price range | +| Awesome Oscillator | Bottom | Shows market momentum | + +## Annotations + +Annotations allow you to add visual elements to the chart, such as horizontal and vertical barriers. + +### Horizontal Barriers + +Horizontal barriers mark specific price levels on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + HorizontalBarrier( + 125.0, // Price level + title: 'Resistance', + style: HorizontalBarrierStyle( + color: Colors.red, + isDashed: true, + titleBackgroundColor: Colors.red.withOpacity(0.8), + textStyle: TextStyle(color: Colors.white), + labelShape: LabelShape.rectangle, + labelHeight: 24, + labelPadding: 4, + ), + visibility: HorizontalBarrierVisibility.forceToStayOnRange, + ), + ], +) +``` + +### Vertical Barriers + +Vertical barriers mark specific time points on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + VerticalBarrier( + ticks[2].epoch, // Time point + title: 'Event', + style: VerticalBarrierStyle( + color: Colors.blue, + isDashed: false, + titleBackgroundColor: Colors.blue.withOpacity(0.8), + textStyle: TextStyle(color: Colors.white), + labelPosition: VerticalBarrierLabelPosition.auto, + ), + ), + ], +) +``` + +### Tick Indicators + +Tick indicators highlight specific data points on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + TickIndicator( + ticks.last, + style: HorizontalBarrierStyle( + labelShape: LabelShape.pentagon, + ), + visibility: HorizontalBarrierVisibility.normal, + ), + ], +) +``` + +### Combined Barriers + +You can also create barriers that have both horizontal and vertical components: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + annotations: [ + CombinedBarrier( + ticks.last, + title: 'Combined Barrier', + horizontalBarrierStyle: HorizontalBarrierStyle( + color: Colors.purple, + isDashed: true, + ), + ), + ], +) +``` + +## Markers + +Markers allow you to highlight specific points on the chart with interactive elements. + +### Basic Markers + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries([ + Marker.up( + epoch: ticks[1].epoch, + quote: ticks[1].quote, + onTap: () { + print('Up marker tapped'); + } + ), + Marker.down( + epoch: ticks[3].epoch, + quote: ticks[3].quote, + onTap: () { + print('Down marker tapped'); + } + ), + ]), +) +``` + +### Active Marker + +You can also display an active marker that responds to user interactions: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [ + Marker.up(epoch: ticks[1].epoch, quote: ticks[1].quote, onTap: () {}), + Marker.down(epoch: ticks[3].epoch, quote: ticks[3].quote, onTap: () {}), + ], + activeMarker: ActiveMarker( + epoch: ticks[1].epoch, + quote: ticks[1].quote, + onTap: () { + print('Active marker tapped'); + }, + onOutsideTap: () { + print('Tapped outside active marker'); + // Remove active marker + }, + ), + ), +) +``` + +### Entry and Exit Ticks + +You can highlight entry and exit points on the chart: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + [], + entryTick: Tick(epoch: ticks[0].epoch, quote: ticks[0].quote), + exitTick: Tick(epoch: ticks.last.epoch, quote: ticks.last.quote), + ), +) +``` + +### Custom Marker Icons + +You can customize the appearance of markers by providing a custom marker icon painter: + +```dart +Chart( + mainSeries: LineSeries(ticks), + pipSize: 2, + markerSeries: MarkerSeries( + markers, + markerIconPainter: CustomMarkerIconPainter(), + ), +) +``` + +## Drawing Tools + +The Deriv Chart library supports interactive drawing tools that allow users to draw on the chart. + +### Using Drawing Tools with DerivChart + +The `DerivChart` widget provides built-in support for drawing tools: + +```dart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + drawingToolsRepo: myDrawingToolsRepo, // Optional +) +``` + +### Managing Drawing Tools + +Similar to indicators, drawing tools can be managed automatically by DerivChart or through an external repository: + +```dart +// Create a repository for drawing tools +final drawingToolsRepo = AddOnsRepository( + createAddOn: (Map map) => DrawingToolConfig.fromJson(map), + sharedPrefKey: 'R_100', +); + +// Add drawing tools to the repository +drawingToolsRepo.add(LineDrawingToolConfig( + // Configuration options +)); + +// Use the repository with DerivChart +DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + drawingToolsRepo: drawingToolsRepo, +) +``` + +### Available Drawing Tools + +The library includes several built-in drawing tools: + +- Line +- Horizontal Line +- Vertical Line +- Trend Line +- Rectangle +- Channel +- Fibonacci Fan +- Ray + +## Real-World Example + +Here's a complete example showing how to use advanced features in a real application: + +```dart +class AdvancedChartScreen extends StatefulWidget { + @override + _AdvancedChartScreenState createState() => _AdvancedChartScreenState(); +} + +class _AdvancedChartScreenState extends State { + final List candles = []; // Your data source + final ChartController controller = ChartController(); + late AddOnsRepository indicatorsRepo; + late AddOnsRepository drawingToolsRepo; + + @override + void initState() { + super.initState(); + + // Initialize repositories + indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + sharedPrefKey: 'R_100', + ); + + drawingToolsRepo = AddOnsRepository( + createAddOn: (Map map) => DrawingToolConfig.fromJson(map), + sharedPrefKey: 'R_100', + ); + + // Add default indicators + indicatorsRepo.add(BollingerBandsIndicatorConfig( + period: 20, + deviation: 2, + maType: MAType.sma, + )); + + indicatorsRepo.add(RSIIndicatorConfig( + period: 14, + overbought: 70, + oversold: 30, + )); + + // Load chart data + _fetchChartData(); + } + + void _fetchChartData() { + // Fetch your chart data here + // When data is received, update the state + setState(() { + // Update candles with new data + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Advanced Chart')), + body: candles.isEmpty + ? Center(child: CircularProgressIndicator()) + : DerivChart( + mainSeries: CandleSeries(candles), + granularity: 60000, + activeSymbol: 'R_100', + pipSize: 4, + controller: controller, + indicatorsRepo: indicatorsRepo, + drawingToolsRepo: drawingToolsRepo, + isLive: true, + annotations: [ + HorizontalBarrier( + candles.map((c) => c.close).reduce(max) * 1.05, + title: 'Resistance', + style: HorizontalBarrierStyle( + color: Colors.red, + isDashed: true, + ), + ), + TickIndicator( + Tick( + epoch: candles.last.epoch, + quote: candles.last.close, + ), + style: HorizontalBarrierStyle( + labelShape: LabelShape.pentagon, + hasBlinkingDot: true, + ), + ), + ], + markerSeries: MarkerSeries( + [ + Marker.up( + epoch: candles[5].epoch, + quote: candles[5].high, + onTap: () => print('Marker tapped'), + ), + ], + entryTick: Tick( + epoch: candles.first.epoch, + quote: candles.first.open, + ), + exitTick: Tick( + epoch: candles.last.epoch, + quote: candles.last.close, + ), + ), + onVisibleAreaChanged: (leftEpoch, rightEpoch) { + // Load more historical data if needed + if (leftEpoch < candles.first.epoch) { + _loadMoreHistory(); + } + }, + ), + ); + } + + void _loadMoreHistory() { + // Load more historical data + } +} +``` + +## Next Steps + +Now that you understand the advanced features of the Deriv Chart library, you can explore: + +- [Indicators Reference](../features/indicators/reference.md) - Detailed information about all available indicators +- [Drawing Tools Reference](../features/drawing_tools/reference.md) - Detailed information about all available drawing tools +- [Custom Indicators](../advanced_usage/custom_indicators.md) - Learn how to create custom indicators \ No newline at end of file diff --git a/doc/getting_started/basic_usage.md b/doc/getting_started/basic_usage.md index 1a6f4a027..5051fcd2e 100644 --- a/doc/getting_started/basic_usage.md +++ b/doc/getting_started/basic_usage.md @@ -227,5 +227,4 @@ Now that you understand the basics of using the Deriv Chart library, you can exp - [Chart Types](chart_types.md) - Learn more about different chart types - [Configuration](configuration.md) - Discover more configuration options -- [Indicators](../features/indicators/overview.md) - Add technical indicators to your charts -- [Drawing Tools](../features/drawing_tools/overview.md) - Enable interactive drawing tools \ No newline at end of file +- [Advanced Features](advanced_features.md) - Learn about technical indicators, annotations, markers, and drawing tools \ No newline at end of file diff --git a/doc/getting_started/configuration.md b/doc/getting_started/configuration.md index 39baed62c..bd1f07dd5 100644 --- a/doc/getting_started/configuration.md +++ b/doc/getting_started/configuration.md @@ -152,363 +152,46 @@ controller.scroll(100); // Scroll by 100 pixels | `scrollToLastTick({bool animate = false})` | Scroll to the most recent data with optional animation | | `scale(double scale)` | Set the zoom level | -## Using Indicators +## Advanced Features -The Deriv Chart library provides support for technical indicators that can be displayed either on the main chart (overlay) or in separate charts below the main chart. There are two ways to add indicators to your charts: +The Deriv Chart library provides several advanced features that can enhance your charts: -### 1. Direct Configuration with Chart Widget +### Technical Indicators -You can directly pass indicator configurations to the `Chart` widget: - -```dart -Chart( - mainSeries: CandleSeries(candles), - pipSize: 2, - granularity: 60000, - // Indicators displayed on the main chart - overlayConfigs: [ - BollingerBandsIndicatorConfig( - period: 20, - deviation: 2, - maType: MAType.sma, - ), - ], - // Indicators displayed in separate charts below the main chart - bottomConfigs: [ - RSIIndicatorConfig( - period: 14, - overbought: 70, - oversold: 30, - ), - SMIIndicatorConfig( - period: 14, - signalPeriod: 3, - ), - ], -) -``` - -### 2. Using AddOnsRepository with DerivChart Widget - -For more advanced usage, including saving indicator settings, you can use the `DerivChart` widget with an `AddOnsRepository`: - -```dart -// Create a repository for indicators -final indicatorsRepo = AddOnsRepository( - createAddOn: (Map map) => IndicatorConfig.fromJson(map), - sharedPrefKey: 'R_100', // Use the symbol code for saving settings -); - -// Add indicators to the repository -indicatorsRepo.add(BollingerBandsIndicatorConfig( - period: 20, - deviation: 2, - maType: MAType.sma, -)); - -indicatorsRepo.add(RSIIndicatorConfig( - period: 14, - overbought: 70, - oversold: 30, -)); - -// Use the repository with DerivChart -DerivChart( - mainSeries: CandleSeries(candles), - granularity: 60000, - activeSymbol: 'R_100', - pipSize: 4, - indicatorsRepo: indicatorsRepo, -) -``` - -### Repository Management in DerivChart - -The `DerivChart` widget provides flexible options for managing indicators and drawing tools: - -#### Automatic Repository Management - -If you don't provide repositories, `DerivChart` will: -- Automatically instantiate its own `indicatorsRepo` and `drawingToolsRepo` -- Display UI icons over the chart to open built-in dialogs for adding, editing, and removing indicators and drawing tools -- Handle saving and loading configurations using the `activeSymbol` as the storage key -- Manage all UI interactions internally - -```dart -// DerivChart with automatic repository management -DerivChart( - mainSeries: CandleSeries(candles), - granularity: 60000, - activeSymbol: 'R_100', - pipSize: 4, - // No indicatorsRepo or drawingToolsRepo provided -) -``` - -#### External Repository Management - -If you provide your own repositories, `DerivChart` will: -- Use the provided repositories instead of creating its own -- Not display the built-in UI icons, assuming you'll handle UI interactions externally -- Still apply the indicators and drawing tools from the repositories to the chart - -```dart -// DerivChart with external repository management -DerivChart( - mainSeries: CandleSeries(candles), - granularity: 60000, - activeSymbol: 'R_100', - pipSize: 4, - indicatorsRepo: myIndicatorsRepo, // Externally managed repository - drawingToolsRepo: myDrawingToolsRepo, // Externally managed repository -) -``` - -When using external repositories, you're responsible for: -- Initializing the repositories -- Adding, editing, and removing items from the repositories -- Providing your own UI for managing indicators and drawing tools if needed - -### Real-World Example - -Here's how indicators are used in a complete application, similar to the example app: - -```dart -class ChartScreen extends StatefulWidget { - @override - _ChartScreenState createState() => _ChartScreenState(); -} - -class _ChartScreenState extends State { - final List candles = []; // Your data source - final ChartController controller = ChartController(); - late AddOnsRepository indicatorsRepo; - - @override - void initState() { - super.initState(); - - // Initialize indicators repository - indicatorsRepo = AddOnsRepository( - createAddOn: (Map map) => IndicatorConfig.fromJson(map), - sharedPrefKey: 'R_100', - ); - - // Load saved indicators or add default ones - _loadIndicators(); - - // Load chart data - _fetchChartData(); - } - - void _loadIndicators() { - // Add default indicators if none are saved - if (indicatorsRepo.items.isEmpty) { - indicatorsRepo.add(BollingerBandsIndicatorConfig( - period: 20, - deviation: 2, - maType: MAType.sma, - )); - - indicatorsRepo.add(RSIIndicatorConfig( - period: 14, - overbought: 70, - oversold: 30, - )); - } - } - - void _fetchChartData() { - // Fetch your chart data here - // When data is received, update the state - setState(() { - // Update candles with new data - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Chart with Indicators')), - body: candles.isEmpty - ? Center(child: CircularProgressIndicator()) - : DerivChart( - mainSeries: CandleSeries(candles), - granularity: 60000, - activeSymbol: 'R_100', - pipSize: 4, - controller: controller, - indicatorsRepo: indicatorsRepo, - isLive: true, - onVisibleAreaChanged: (leftEpoch, rightEpoch) { - // Load more historical data if needed - if (leftEpoch < candles.first.epoch) { - _loadMoreHistory(); - } - }, - ), - ); - } - - void _loadMoreHistory() { - // Load more historical data - } -} -``` - -The `DerivChart` widget automatically: -- Filters indicators into overlay and bottom indicators based on their `isOverlay` property -- Provides UI buttons for adding, editing, and removing indicators -- Saves indicator configurations to persistent storage -- Loads saved indicators when the chart is initialized - -### Available Indicators - -The library includes many built-in indicators such as: - -| Indicator | Type | Description | +| Parameter | Type | Description | |-----------|------|-------------| -| Bollinger Bands | Overlay | Shows volatility and potential price levels | -| Moving Average | Overlay | Smooths price data to show trends | -| RSI | Bottom | Relative Strength Index measures momentum | -| MACD | Bottom | Moving Average Convergence Divergence | -| Stochastic Oscillator | Bottom | Compares closing price to price range | -| Awesome Oscillator | Bottom | Shows market momentum | - -## Annotations - -Annotations allow you to add visual elements to the chart, such as horizontal and vertical barriers: - -```dart -Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - annotations: [ - HorizontalBarrier( - 125.0, - title: 'Resistance', - style: HorizontalBarrierStyle( - color: Colors.red, - isDashed: true, - titleBackgroundColor: Colors.red.withOpacity(0.8), - textStyle: TextStyle(color: Colors.white), - labelShape: LabelShape.rectangle, - labelHeight: 24, - labelPadding: 4, - ), - visibility: HorizontalBarrierVisibility.forceToStayOnRange, - ), - VerticalBarrier( - ticks[2].epoch, - title: 'Event', - style: VerticalBarrierStyle( - color: Colors.blue, - isDashed: false, - titleBackgroundColor: Colors.blue.withOpacity(0.8), - textStyle: TextStyle(color: Colors.white), - labelPosition: VerticalBarrierLabelPosition.auto, - ), - ), - TickIndicator( - ticks.last, - style: HorizontalBarrierStyle( - labelShape: LabelShape.pentagon, - ), - visibility: HorizontalBarrierVisibility.normal, - ), - ], -) -``` - -## Markers - -Markers allow you to highlight specific points on the chart: - -```dart -Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - markerSeries: MarkerSeries([ - Marker.up(epoch: ticks[1].epoch, quote: ticks[1].quote, onTap: () { - print('Up marker tapped'); - }), - Marker.down(epoch: ticks[3].epoch, quote: ticks[3].quote, onTap: () { - print('Down marker tapped'); - }), - ]), -) -``` - -### Active Marker +| `overlayConfigs` | `List` | Technical indicators to display on the main chart | +| `bottomConfigs` | `List` | Technical indicators to display in separate charts below the main chart | -You can also display an active marker that responds to user interactions: +### Annotations -```dart -Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - markerSeries: MarkerSeries( - [ - Marker.up(epoch: ticks[1].epoch, quote: ticks[1].quote, onTap: () {}), - Marker.down(epoch: ticks[3].epoch, quote: ticks[3].quote, onTap: () {}), - ], - activeMarker: ActiveMarker( - epoch: ticks[1].epoch, - quote: ticks[1].quote, - onTap: () { - print('Active marker tapped'); - }, - onOutsideTap: () { - print('Tapped outside active marker'); - // Remove active marker - }, - ), - ), -) -``` +| Parameter | Type | Description | +|-----------|------|-------------| +| `annotations` | `List` | Visual elements like horizontal/vertical barriers and tick indicators | -## Entry and Exit Ticks +### Markers -You can highlight entry and exit points on the chart: +| Parameter | Type | Description | +|-----------|------|-------------| +| `markerSeries` | `MarkerSeries` | Highlight specific points on the chart with interactive markers | -```dart -Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - markerSeries: MarkerSeries( - [], - entryTick: Tick(epoch: ticks[0].epoch, quote: ticks[0].quote), - exitTick: Tick(epoch: ticks.last.epoch, quote: ticks.last.quote), - ), -) -``` +For detailed information and examples of these advanced features, see the [Advanced Features](advanced_features.md) documentation. -## DerivChart Configuration +## DerivChart Widget The `DerivChart` widget is a wrapper around the `Chart` widget that provides additional functionality for managing indicators and drawing tools: -```dart -DerivChart( - mainSeries: CandleSeries(candles), - granularity: 60000, // 60000 milliseconds (1 minute) - activeSymbol: 'R_100', - pipSize: 4, - // All Chart parameters are also available here -) -``` - -### DerivChart Specific Parameters - | Parameter | Type | Description | |-----------|------|-------------| | `activeSymbol` | `String` | Symbol code for saving indicator settings | | `indicatorsRepo` | `AddOnsRepository` | Repository for managing indicators | | `drawingToolsRepo` | `AddOnsRepository` | Repository for managing drawing tools | +For detailed information about using the DerivChart widget, see the [Advanced Features](advanced_features.md) documentation. + ## Next Steps Now that you understand the configuration options available in the Deriv Chart library, you can explore: -- [Indicators](../features/indicators/overview.md) - Add technical indicators to your charts -- [Drawing Tools](../features/drawing_tools/overview.md) - Enable interactive drawing tools -- [Advanced Usage](../advanced_usage/custom_themes.md) - Learn about advanced customization options \ No newline at end of file +- [Advanced Features](advanced_features.md) - Learn about technical indicators, annotations, markers, and drawing tools +- [Custom Themes](../advanced_usage/custom_themes.md) - Learn about advanced customization options \ No newline at end of file From 353b13eac3acdf677eaf18f3de19641915b69bb2 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Fri, 20 Jun 2025 09:33:44 +0800 Subject: [PATCH 14/32] update list of indicators in docs --- doc/getting_started/advanced_features.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/getting_started/advanced_features.md b/doc/getting_started/advanced_features.md index 11b74aa00..eb45e7453 100644 --- a/doc/getting_started/advanced_features.md +++ b/doc/getting_started/advanced_features.md @@ -117,7 +117,7 @@ When using external repositories, you're responsible for: ### Available Indicators -The library includes many built-in indicators such as: +The library includes a comprehensive set of built-in indicators. Here are some of the most commonly used ones: | Indicator | Type | Description | |-----------|------|-------------| @@ -127,6 +127,12 @@ The library includes many built-in indicators such as: | MACD | Bottom | Moving Average Convergence Divergence | | Stochastic Oscillator | Bottom | Compares closing price to price range | | Awesome Oscillator | Bottom | Shows market momentum | +| Ichimoku Cloud | Overlay | Comprehensive trend indicator showing support/resistance | +| Parabolic SAR | Overlay | Identifies potential reversals in market trend | +| ATR | Bottom | Average True Range measures volatility | +| Williams %R | Bottom | Momentum indicator showing overbought/oversold levels | + +This is not an exhaustive list. For a complete list of all available indicators and their implementation details, refer to the [GitHub repository](https://github.com/deriv-com/flutter-chart/tree/master/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series). ## Annotations From 11afc9a7c64fdc4dd58c9008a4c9b5b24e493569 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Fri, 20 Jun 2025 09:44:57 +0800 Subject: [PATCH 15/32] minor change in doc --- doc/core_concepts/coordinate_system.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/doc/core_concepts/coordinate_system.md b/doc/core_concepts/coordinate_system.md index 45f727240..4bb7521c1 100644 --- a/doc/core_concepts/coordinate_system.md +++ b/doc/core_concepts/coordinate_system.md @@ -165,15 +165,6 @@ The Y-axis scale is determined by: The grid system uses the coordinate system to place grid lines and labels at appropriate intervals, enhancing readability and providing visual reference points. -## Performance Considerations - -### Clipping and Culling - -For optimal performance, the chart only renders data points that are within the visible area: - -The `isVisible` method checks if a data point is within the visible area of the chart. It compares the epoch and quote values against the current bounds to determine visibility. - - ## Coordinate System in Action ### Handling User Interactions From ff452051fe24905deab6d6c3aba3aac56828a4f5 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Fri, 20 Jun 2025 10:56:05 +0800 Subject: [PATCH 16/32] update rednering pipeline doc --- doc/core_concepts/rendering_pipeline.md | 118 +++++++++++++++++++----- 1 file changed, 97 insertions(+), 21 deletions(-) diff --git a/doc/core_concepts/rendering_pipeline.md b/doc/core_concepts/rendering_pipeline.md index 779ba9024..b17b71058 100644 --- a/doc/core_concepts/rendering_pipeline.md +++ b/doc/core_concepts/rendering_pipeline.md @@ -13,20 +13,48 @@ The rendering pipeline in the Deriv Chart library consists of several stages: This pipeline ensures efficient rendering and a responsive user experience, even with large datasets. -## Data Processing Stage - -The data processing stage prepares the raw market data for visualization: +## The ChartData Interface -### Data Sorting - -All data is sorted chronologically by epoch (timestamp): +At the core of the rendering pipeline is the `ChartData` interface, which defines the contract for all visual elements except for drawing tool that can be rendered on a chart: ```dart -void sortData() { - data.sort((a, b) => a.epoch.compareTo(b.epoch)); +abstract class ChartData { + late String id; + bool didUpdate(ChartData? oldData); + bool shouldRepaint(ChartData? oldData); + void update(int leftEpoch, int rightEpoch); + double get minValue; + double get maxValue; + int? getMinEpoch(); + int? getMaxEpoch(); + void paint( + Canvas canvas, + Size size, + EpochToX epochToX, + QuoteToY quoteToY, + AnimationInfo animationInfo, + ChartConfig chartConfig, + ChartTheme theme, + ChartScaleModel chartScaleModel, + ); } ``` +All chart elements implement this interface, including: +- Chart types (LineSeries, CandleSeries, OHLCSeries, HollowCandleSeries) +- Technical indicators (both overlay and bottom indicators) +- Annotations (horizontal barriers, vertical barriers, tick indicators) +- Markers and other visual elements + +The `ChartData` interface provides a unified way for the chart to: +1. Calculate visible data based on the current viewport +2. Determine min/max values for proper scaling +3. Paint elements on the canvas with appropriate coordinate transformations + +## Data Processing Stage + +The data processing stage prepares the raw market data for visualization: + ### Visible Data Calculation Only data within the visible range is processed for rendering: @@ -583,29 +611,77 @@ void onPaintData( The complete rendering flow is as follows: 1. **Data Update**: - - New data is received - - Data is sorted chronologically - - `updateVisibleRange` is called with the current viewport - -2. **Viewport Update**: - - User scrolls or zooms - - `XAxisModel` updates `rightBoundEpoch` and `msPerPx` - - `updateVisibleRange` is called with the new viewport - -3. **Y-Axis Update**: - - Visible data min/max values are calculated + - Each `ChartData` implementation's `update(leftEpoch, rightEpoch)` method is called + - Each implementation calculates its visible data subset based on the current viewport + +2. **Y-Axis Update**: + - Each `ChartData` implementation calculates its `minValue` and `maxValue` for the visible data + - The chart collects these values using the `ChartDataListExtension` methods: + ```dart + double getMinValue() { + final Iterable minValues = + where((ChartData? c) => c != null && !c.minValue.isNaN) + .map((ChartData? c) => c!.minValue); + return minValues.isEmpty ? double.nan : minValues.reduce(min); + } + + double getMaxValue() { + final Iterable maxValues = + where((ChartData? c) => c != null && !c.maxValue.isNaN) + .map((ChartData? c) => c!.maxValue); + return maxValues.isEmpty ? double.nan : maxValues.reduce(max); + } + ``` - Y-axis bounds are updated with padding - `quotePerPx` is recalculated 4. **Painting**: - Each layer's `CustomPaint` is triggered to repaint - - Each layer's painter calls the appropriate `SeriesPainter` - - Each `SeriesPainter` renders its content using the coordinate conversion functions + - The chart calls the `paint` method on each of its `ChartData` objects: + ```dart + void paint( + Canvas canvas, + Size size, + EpochToX epochToX, + QuoteToY quoteToY, + AnimationInfo animationInfo, + ChartConfig chartConfig, + ChartTheme theme, + ChartScaleModel chartScaleModel, + ); + ``` + - Each `ChartData` implementation renders its content using the coordinate conversion functions 5. **Composition**: - Flutter composites all layers into the final chart - The chart is displayed on the screen +## Painter Classes + +The Deriv Chart library uses a decoupled painter architecture to promote code reuse: + +1. **Specialized Painters**: Each visual element type has specialized painters: + - `LinePainter`: Renders line graphs (used by LineSeries and many indicators) + - `CandlePainter`: Renders candlestick charts + - `OHLCPainter`: Renders OHLC charts + - `HorizontalBarrierPainter`: Renders horizontal barriers + - `VerticalBarrierPainter`: Renders vertical barriers + - `MarkerPainter`: Renders markers + +2. **Painter Reuse**: The same painter can be used by different `ChartData` implementations: + - `LinePainter` is used by LineSeries, Moving Average indicators, and other line-based indicators + - This approach ensures consistent rendering across different chart elements + +3. **Painter Composition**: Complex chart elements can use multiple painters: + - Bollinger Bands uses both `LinePainter` (for the middle line) and specialized painters for the bands + - MACD uses `LinePainter` for signal line and histogram painters for the bars + +This decoupled approach allows for: +- Better code organization +- Easier maintenance +- Consistent visual appearance +- Code reuse across different chart elements + ## Next Steps Now that you understand the rendering pipeline used in the Deriv Chart library, you can explore: From ede96770b35778fb890e9ac82989384c624eafb6 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Mon, 23 Jun 2025 11:41:04 +0800 Subject: [PATCH 17/32] update rendering pipeline doc --- doc/core_concepts/rendering_pipeline.md | 658 ++---------------------- 1 file changed, 43 insertions(+), 615 deletions(-) diff --git a/doc/core_concepts/rendering_pipeline.md b/doc/core_concepts/rendering_pipeline.md index b17b71058..4ec98c5d0 100644 --- a/doc/core_concepts/rendering_pipeline.md +++ b/doc/core_concepts/rendering_pipeline.md @@ -4,15 +4,38 @@ This document explains the rendering pipeline used in the Deriv Chart library, d ## Overview -The rendering pipeline in the Deriv Chart library consists of several stages: +The rendering pipeline in the Deriv Chart library follows a clear 4-step process that is triggered whenever the viewport changes (through zooming or scrolling): -1. **Data Processing**: Preparing and filtering data for visualization -2. **Coordinate Mapping**: Converting data points to screen coordinates -3. **Painting**: Drawing the visual elements on the canvas -4. **Composition**: Combining multiple layers into the final chart +1. **Viewport Changes**: User interaction triggers zoom or scroll events +2. **Data Update**: Chart data objects update their visible data based on new viewport bounds +3. **Y-Axis Calculation**: Overall min/max values are calculated for proper scaling +4. **Painting**: Custom painters render all visual elements on the canvas This pipeline ensures efficient rendering and a responsive user experience, even with large datasets. +## Rendering Process Steps + +### Step 1: Viewport Changes +When users zoom or scroll the chart, the viewport boundaries change. This triggers the rendering pipeline by updating the left and right epoch boundaries that define what data should be visible. + +### Step 2: Chart Data Update +Each chart data object's [`update(leftEpoch, rightEpoch)`](../api_reference/chart_data.md:25) method is called with the new viewport boundaries. During this update: +- Each chart data object calculates which data points fall within the visible range +- Min/max values are recalculated based on the visible data subset +- Data transformations (like indicator calculations) are applied if needed + +### Step 3: Y-Axis Min/Max Calculation +The chart collects the min/max values from all chart data instances to determine the overall Y-axis scaling: +- Each chart data object provides its [`minValue`](../api_reference/chart_data.md:26) and [`maxValue`](../api_reference/chart_data.md:27) +- The chart calculates the overall minimum and maximum across all data objects +- Y-axis bounds are set with appropriate padding for optimal visualization + +### Step 4: Custom Paint Rendering +The [`paint`](../api_reference/chart_data.md:30) method of the custom painter is called, which: +- Triggers each chart data object's [`paint`](../api_reference/chart_data.md:30) method +- Provides coordinate conversion functions ([`EpochToX`](../api_reference/coordinate_system.md:10), [`QuoteToY`](../api_reference/coordinate_system.md:15)) +- Renders all visual elements in the correct layer order + ## The ChartData Interface At the core of the rendering pipeline is the `ChartData` interface, which defines the contract for all visual elements except for drawing tool that can be rendered on a chart: @@ -51,334 +74,17 @@ The `ChartData` interface provides a unified way for the chart to: 2. Determine min/max values for proper scaling 3. Paint elements on the canvas with appropriate coordinate transformations -## Data Processing Stage - -The data processing stage prepares the raw market data for visualization: - -### Visible Data Calculation - -Only data within the visible range is processed for rendering: - -```dart -void updateVisibleRange(int leftEpoch, int rightEpoch) { - visibleData = _getVisibleData(leftEpoch, rightEpoch); - _calculateMinMaxValues(); -} - -List _getVisibleData(int leftEpoch, int rightEpoch) { - // Use binary search to find the start and end indices - final startIndex = _binarySearchStartIndex(leftEpoch); - final endIndex = _binarySearchEndIndex(rightEpoch); - - // Return the slice of data within the visible range - return data.sublist(startIndex, endIndex + 1); -} -``` - -### Min/Max Value Calculation - -The minimum and maximum values in the visible range are calculated: - -```dart -void _calculateMinMaxValues() { - if (visibleData.isEmpty) { - _minValue = double.nan; - _maxValue = double.nan; - return; - } - - _minValue = double.infinity; - _maxValue = -double.infinity; - - for (final item in visibleData) { - final value = _getValue(item); - if (value < _minValue) _minValue = value; - if (value > _maxValue) _maxValue = value; - } -} -``` - -### Data Transformation - -Some series types (like indicators) transform the raw data: - -```dart -List _calculateIndicatorValues() { - final result = []; - - // Apply the indicator calculation to the data - for (int i = period - 1; i < data.length; i++) { - final value = _calculateIndicatorValue(i); - result.add(Tick( - epoch: data[i].epoch, - quote: value, - )); - } - - return result; -} -``` - -## Coordinate Mapping Stage +## Data Processing -The coordinate mapping stage converts data points to screen coordinates: +The data processing stage prepares raw market data for visualization by filtering visible data and calculating min/max values within the current viewport. -### X-Coordinate Mapping +## Coordinate Mapping -Time values (epochs) are mapped to X-coordinates: +The coordinate mapping stage converts data points to screen coordinates using transformation functions that map time values to X-coordinates and price values to Y-coordinates. -```dart -double xFromEpoch(DateTime epoch) { - return width - (rightBoundEpoch - epoch.millisecondsSinceEpoch) / msPerPx; -} -``` - -### Y-Coordinate Mapping - -Price values (quotes) are mapped to Y-coordinates: - -```dart -double yFromQuote(double quote) { - return height - bottomPadding - (quote - bottomBoundQuote) / quotePerPx; -} -``` +## Layer Composition -### Viewport Clipping - -Data points outside the viewport are clipped: - -```dart -bool isInViewport(double x, double y) { - return x >= 0 && x <= width && y >= 0 && y <= height; -} -``` - -## Painting Stage - -The painting stage draws the visual elements on the canvas: - -### SeriesPainter - -The `SeriesPainter` is the base class for all painters: - -```dart -abstract class SeriesPainter { - final Series series; - - SeriesPainter(this.series); - - void onPaint( - Canvas canvas, - Size size, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, - ); -} -``` - -### DataPainter - -The `DataPainter` extends `SeriesPainter` to handle sequential data: - -```dart -abstract class DataPainter extends SeriesPainter { - final DataSeries dataSeries; - - DataPainter(this.dataSeries) : super(dataSeries); - - @override - void onPaint( - Canvas canvas, - Size size, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, - ) { - // Common setup for all data painters - - // Call the specific painting implementation - onPaintData( - canvas, - size, - dataSeries.visibleData, - xFromEpoch, - yFromQuote, - ); - } - - void onPaintData( - Canvas canvas, - Size size, - List data, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, - ); -} -``` - -### Specific Painters - -Each chart type has its own painter implementation: - -#### LinePainter - -```dart -class LinePainter extends DataPainter { - final LineSeries lineSeries; - - LinePainter(this.lineSeries) : super(lineSeries); - - @override - void onPaintData( - Canvas canvas, - Size size, - List data, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, - ) { - if (data.isEmpty) return; - - final path = Path(); - final paint = Paint() - ..color = lineSeries.style.color - ..strokeWidth = lineSeries.style.thickness - ..style = PaintingStyle.stroke; - - // Move to the first point - final firstX = xFromEpoch(data.first.epoch); - final firstY = yFromQuote(data.first.quote); - path.moveTo(firstX, firstY); - - // Add line segments to each subsequent point - for (int i = 1; i < data.length; i++) { - final x = xFromEpoch(data[i].epoch); - final y = yFromQuote(data[i].quote); - path.lineTo(x, y); - } - - // Draw the path - canvas.drawPath(path, paint); - } -} -``` - -#### CandlePainter - -```dart -class CandlePainter extends DataPainter { - final CandleSeries candleSeries; - - CandlePainter(this.candleSeries) : super(candleSeries); - - @override - void onPaintData( - Canvas canvas, - Size size, - List data, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, - ) { - if (data.isEmpty) return; - - final style = candleSeries.style; - final wickPaint = Paint() - ..strokeWidth = style.wickWidth; - final bodyPaint = Paint(); - - for (final candle in data) { - final x = xFromEpoch(candle.epoch); - final yOpen = yFromQuote(candle.open); - final yHigh = yFromQuote(candle.high); - final yLow = yFromQuote(candle.low); - final yClose = yFromQuote(candle.close); - - // Determine colors based on candle direction - final isPositive = candle.close >= candle.open; - wickPaint.color = isPositive ? style.positiveColor : style.negativeColor; - bodyPaint.color = isPositive ? style.positiveColor : style.negativeColor; - - // Draw wick - canvas.drawLine( - Offset(x, yHigh), - Offset(x, yLow), - wickPaint, - ); - - // Draw body - final halfBodyWidth = style.bodyWidth / 2; - canvas.drawRect( - Rect.fromPoints( - Offset(x - halfBodyWidth, yOpen), - Offset(x + halfBodyWidth, yClose), - ), - bodyPaint, - ); - } - } -} -``` - -### Barrier Painters - -Barriers have their own painters: - -```dart -class HorizontalBarrierPainter extends SeriesPainter { - final HorizontalBarrier barrier; - - HorizontalBarrierPainter(this.barrier) : super(barrier); - - @override - void onPaint( - Canvas canvas, - Size size, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, - ) { - final y = yFromQuote(barrier.value); - - // Draw the horizontal line - final paint = Paint() - ..color = barrier.style.color - ..strokeWidth = barrier.style.lineThickness; - - if (barrier.style.isDashed) { - // Draw dashed line - drawDashedLine( - canvas, - Offset(0, y), - Offset(size.width, y), - paint, - ); - } else { - // Draw solid line - canvas.drawLine( - Offset(0, y), - Offset(size.width, y), - paint, - ); - } - - // Draw the label if provided - if (barrier.title != null) { - drawLabel( - canvas, - barrier.title!, - Offset(10, y), - barrier.style.labelBackgroundColor, - barrier.style.labelTextStyle, - ); - } - } -} -``` - -## Composition Stage - -The composition stage combines multiple layers into the final chart: - -### Layer Management - -The chart manages multiple layers: +The chart uses multiple layers that are composited together: 1. **Grid Layer**: Background grid lines and labels 2. **Data Layer**: Main data series and overlay indicators @@ -387,300 +93,22 @@ The chart manages multiple layers: 5. **Crosshair Layer**: Crosshair lines and labels 6. **Interactive Layer**: Drawing tools and user interactions -### Layer Ordering - -Layers are painted in a specific order to ensure proper visual hierarchy: - -```dart -@override -Widget build(BuildContext context) { - return Stack( - children: [ - // Grid layer (bottom) - GridLayer( - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - theme: theme, - ), - - // Data layer - DataLayer( - mainSeries: mainSeries, - overlaySeries: overlaySeries, - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - ), - - // Annotation layer - AnnotationLayer( - annotations: annotations, - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - ), - - // Marker layer - MarkerLayer( - markerSeries: markerSeries, - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - ), - - // Interactive layer - InteractiveLayer( - drawingTools: drawingTools, - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - ), - - // Crosshair layer (top) - CrosshairLayer( - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - theme: theme, - ), - ], - ); -} -``` - -### CustomPaint - -Each layer uses `CustomPaint` to render its content: - -```dart -class DataLayer extends StatelessWidget { - final Series mainSeries; - final List overlaySeries; - final XAxisModel xAxisModel; - final YAxisModel yAxisModel; - - const DataLayer({ - required this.mainSeries, - required this.overlaySeries, - required this.xAxisModel, - required this.yAxisModel, - }); - - @override - Widget build(BuildContext context) { - return CustomPaint( - painter: DataLayerPainter( - mainSeries: mainSeries, - overlaySeries: overlaySeries, - xAxisModel: xAxisModel, - yAxisModel: yAxisModel, - ), - size: Size.infinite, - ); - } -} - -class DataLayerPainter extends CustomPainter { - final Series mainSeries; - final List overlaySeries; - final XAxisModel xAxisModel; - final YAxisModel yAxisModel; - - DataLayerPainter({ - required this.mainSeries, - required this.overlaySeries, - required this.xAxisModel, - required this.yAxisModel, - }); - - @override - void paint(Canvas canvas, Size size) { - // Paint the main series - final mainPainter = mainSeries.createPainter(); - mainPainter.onPaint( - canvas, - size, - xAxisModel.xFromEpoch, - yAxisModel.yFromQuote, - ); - - // Paint the overlay series - for (final series in overlaySeries) { - final painter = series.createPainter(); - painter.onPaint( - canvas, - size, - xAxisModel.xFromEpoch, - yAxisModel.yFromQuote, - ); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => true; -} -``` - ## Optimization Techniques The rendering pipeline includes several optimization techniques: -### Binary Search for Visible Data - -Binary search is used to efficiently find the visible data range: - -```dart -int _binarySearchStartIndex(int leftEpoch) { - int low = 0; - int high = data.length - 1; - - while (low <= high) { - final mid = (low + high) ~/ 2; - final midEpoch = data[mid].epoch.millisecondsSinceEpoch; - - if (midEpoch < leftEpoch) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - return max(0, min(low, data.length - 1)); -} -``` - -### Path Optimization - -For line charts, paths are used instead of individual line segments: - -```dart -// Inefficient approach (drawing individual lines) -for (int i = 1; i < data.length; i++) { - canvas.drawLine( - Offset(xFromEpoch(data[i-1].epoch), yFromQuote(data[i-1].quote)), - Offset(xFromEpoch(data[i].epoch), yFromQuote(data[i].quote)), - paint, - ); -} - -// Efficient approach (using a path) -final path = Path(); -path.moveTo(xFromEpoch(data.first.epoch), yFromQuote(data.first.quote)); -for (int i = 1; i < data.length; i++) { - path.lineTo(xFromEpoch(data[i].epoch), yFromQuote(data[i].quote)); -} -canvas.drawPath(path, paint); -``` - -### Caching Indicator Values - -Indicator values are cached to avoid recalculation: - -```dart -List _getIndicatorValues() { - if (_cachedValues != null) return _cachedValues!; - - _cachedValues = _calculateIndicatorValues(); - return _cachedValues!; -} -``` - -### Viewport Clipping - -Only elements within the viewport are rendered: - -```dart -void onPaintData( - Canvas canvas, - Size size, - List data, - double Function(DateTime) xFromEpoch, - double Function(double) yFromQuote, -) { - // Save the canvas state - canvas.save(); - - // Clip to the viewport - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - - // Paint the data - // ... - - // Restore the canvas state - canvas.restore(); -} -``` - -## Rendering Flow - -The complete rendering flow is as follows: - -1. **Data Update**: - - Each `ChartData` implementation's `update(leftEpoch, rightEpoch)` method is called - - Each implementation calculates its visible data subset based on the current viewport - -2. **Y-Axis Update**: - - Each `ChartData` implementation calculates its `minValue` and `maxValue` for the visible data - - The chart collects these values using the `ChartDataListExtension` methods: - ```dart - double getMinValue() { - final Iterable minValues = - where((ChartData? c) => c != null && !c.minValue.isNaN) - .map((ChartData? c) => c!.minValue); - return minValues.isEmpty ? double.nan : minValues.reduce(min); - } - - double getMaxValue() { - final Iterable maxValues = - where((ChartData? c) => c != null && !c.maxValue.isNaN) - .map((ChartData? c) => c!.maxValue); - return maxValues.isEmpty ? double.nan : maxValues.reduce(max); - } - ``` - - Y-axis bounds are updated with padding - - `quotePerPx` is recalculated - -4. **Painting**: - - Each layer's `CustomPaint` is triggered to repaint - - The chart calls the `paint` method on each of its `ChartData` objects: - ```dart - void paint( - Canvas canvas, - Size size, - EpochToX epochToX, - QuoteToY quoteToY, - AnimationInfo animationInfo, - ChartConfig chartConfig, - ChartTheme theme, - ChartScaleModel chartScaleModel, - ); - ``` - - Each `ChartData` implementation renders its content using the coordinate conversion functions - -5. **Composition**: - - Flutter composites all layers into the final chart - - The chart is displayed on the screen - -## Painter Classes - -The Deriv Chart library uses a decoupled painter architecture to promote code reuse: - -1. **Specialized Painters**: Each visual element type has specialized painters: - - `LinePainter`: Renders line graphs (used by LineSeries and many indicators) - - `CandlePainter`: Renders candlestick charts - - `OHLCPainter`: Renders OHLC charts - - `HorizontalBarrierPainter`: Renders horizontal barriers - - `VerticalBarrierPainter`: Renders vertical barriers - - `MarkerPainter`: Renders markers +- **Binary Search**: Efficiently finds visible data range +- **Path Optimization**: Uses paths instead of individual line segments for better performance +- **Caching**: Indicator values are cached to avoid recalculation +- **Viewport Clipping**: Only elements within the viewport are rendered -2. **Painter Reuse**: The same painter can be used by different `ChartData` implementations: - - `LinePainter` is used by LineSeries, Moving Average indicators, and other line-based indicators - - This approach ensures consistent rendering across different chart elements +## Painter Architecture -3. **Painter Composition**: Complex chart elements can use multiple painters: - - Bollinger Bands uses both `LinePainter` (for the middle line) and specialized painters for the bands - - MACD uses `LinePainter` for signal line and histogram painters for the bars +The library uses a decoupled painter architecture where: -This decoupled approach allows for: -- Better code organization -- Easier maintenance -- Consistent visual appearance -- Code reuse across different chart elements +- Each visual element type has specialized painters ([`LinePainter`](../api_reference/painters.md:10), [`CandlePainter`](../api_reference/painters.md:20), etc.) +- Painters can be reused across different chart data implementations +- Complex elements can compose multiple painters together ## Next Steps From db469ec180b0ce8a661409af697a12dfeb1f66a2 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Mon, 23 Jun 2025 17:12:39 +0800 Subject: [PATCH 18/32] cleanup --- doc/README.md | 2 +- doc/architecture.md | 0 doc/core_concepts/architecture.md | 15 ++++++++++++++- doc/{ => core_concepts}/how_chart_lib_works.md | 12 ++++++------ doc/{ => core_concepts}/interactive_layer.md | 4 ++-- doc/drawing_tools.md | 2 +- 6 files changed, 24 insertions(+), 11 deletions(-) delete mode 100644 doc/architecture.md rename doc/{ => core_concepts}/how_chart_lib_works.md (99%) rename doc/{ => core_concepts}/interactive_layer.md (99%) diff --git a/doc/README.md b/doc/README.md index a617d72ff..195e13996 100644 --- a/doc/README.md +++ b/doc/README.md @@ -112,7 +112,7 @@ Explore the features available in the Deriv Chart library: ### Interactive Layer -- [Interactive Layer](interactive_layer.md) - Understand the interactive layer architecture +- [Interactive Layer](core_concepts/interactive_layer.md) - Understand the interactive layer architecture ## Advanced Usage diff --git a/doc/architecture.md b/doc/architecture.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index a705f5e96..eb2fce42c 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -248,7 +248,7 @@ DataPainter extends SeriesPainter to provide common painting functionality for D ## Interactive Layer -The Interactive Layer manages user interactions with drawing tools: +The Interactive Layer manages user interactions with drawing tools and provides a sophisticated state-based architecture for handling drawing tool creation, selection, and manipulation. ``` ┌─────────────────────────┐ @@ -268,6 +268,16 @@ The Interactive Layer manages user interactions with drawing tools: └─────────────────┘└─────────────────┘└─────────────────┘└─────────────────┘ ``` +### Key Components + +The Interactive Layer consists of several key components: + +- **InteractiveState**: Defines different modes of interaction (normal, selected, adding) +- **InteractiveLayerBehaviour**: Provides platform-specific interaction handling and customizes state transitions +- **DrawingV2**: The base interface for all drawable elements on the chart +- **InteractableDrawing**: Concrete implementations of drawing tools that can be interacted with +- **DrawingAddingPreview**: Specialized components for handling the drawing creation process + ### InteractiveState InteractiveState defines the current mode of interaction with the chart: @@ -286,6 +296,9 @@ Each drawing tool has its own state: - **hovered**: Pointer is hovering over the tool - **adding**: Tool is being created - **dragging**: Tool is being moved +- **animating**: Tool is being animated + +For detailed information about the Interactive Layer architecture, components, and implementation details, see [Interactive Layer](interactive_layer.md). ## Theme System diff --git a/doc/how_chart_lib_works.md b/doc/core_concepts/how_chart_lib_works.md similarity index 99% rename from doc/how_chart_lib_works.md rename to doc/core_concepts/how_chart_lib_works.md index 2a56a52e4..40209c232 100644 --- a/doc/how_chart_lib_works.md +++ b/doc/core_concepts/how_chart_lib_works.md @@ -142,7 +142,7 @@ The market data(input data of chart) is a list of _Ticks_ or _OHLC_. Chart widget is a Canvas that we paint all data of chart inside this Canvas. -![plot](chart_scheme.png) +![plot](../chart_scheme.png) this widget has X-Axis and Y-Axis enabled by default. @@ -202,7 +202,7 @@ Zooming in the chart happens by updating **msPerPx**. ## Data Visualisation -![plot](data_series.png) +![plot](../data_series.png) We have an abstract class named **ChartData** that represents any type of data that the chart takes and makes it paint its self on the chart's canvas including _Line_, _Candle_ data, _Markers_, _barriers_, etc. A **ChartData** can be anything that shows some data on the chart. The chart can take a bunch of ChartData objects and paint them on its canvas. @@ -246,7 +246,7 @@ They have the `createPainter` object to paint the **BarrierObject** that gets in # Painter classes -![plot](data_painters.png) +![plot](../data_painters.png) **SeriesPainter** is an abstract class responsible for painting its [series] data. @@ -329,7 +329,7 @@ Chart has its own default dark and light themes that switch depending on Theme.o - Maintains its own Y-axis range and scaling while sharing the X-axis viewport - Can be dynamically added, removed, or reordered - Supports user interactions like zooming and scrolling in sync with MainChart -![plot](basic-chart.png) +![plot](../basic-chart.png) # Chart @@ -342,7 +342,7 @@ Chart has its own default dark and light themes that switch depending on Theme.o \*if you want to have indicators and drawing tools in the chart, you should use **\*DerivChart** instead of **Chart\*\*** -![plot](deriv-chart.png) +![plot](../deriv-chart.png) # Widgets @@ -362,7 +362,7 @@ CustomDraggableSheet is a wrapper widget to be used combined with a bottom sheet ### Drawing Tools -For a brief explanation of how drawing tools work, please refer to [Drawing Tools](drawing_tools.md) section. +For a brief explanation of how drawing tools work, please refer to [Drawing Tools](../drawing_tools.md) section. ### Interactive Layer (New Implementation) diff --git a/doc/interactive_layer.md b/doc/core_concepts/interactive_layer.md similarity index 99% rename from doc/interactive_layer.md rename to doc/core_concepts/interactive_layer.md index b37ee0d34..5c2475918 100644 --- a/doc/interactive_layer.md +++ b/doc/core_concepts/interactive_layer.md @@ -227,7 +227,7 @@ This architecture provides a clean separation of concerns and makes it easy to a The following diagram illustrates the architecture and flow of the Interactive Layer, including the relationships between InteractiveStates, InteractiveLayerBehaviour, and DrawingAddingPreview: -![Interactive Layer Architecture Diagram](diagrams/interactive_layer_architecture.svg) +![Interactive Layer Architecture Diagram](../diagrams/interactive_layer_architecture.svg) ### Flow Explanation: @@ -273,7 +273,7 @@ To illustrate how the Interactive Layer components work together in practice, le ### Sequence Diagram -![Horizontal Line Adding Sequence Diagram](diagrams/horizontal_line_sequence.svg) +![Horizontal Line Adding Sequence Diagram](../diagrams/horizontal_line_sequence.svg) ### Flow Explanation diff --git a/doc/drawing_tools.md b/doc/drawing_tools.md index 79c18f49b..283b77a15 100644 --- a/doc/drawing_tools.md +++ b/doc/drawing_tools.md @@ -107,7 +107,7 @@ Chart( ### Legacy Drawing Tools Implementation -The following sections describe the implementation details of the legacy drawing tools system. If you're using the new V2 implementation with `useDrawingToolsV2: true`, please refer to the [Interactive Layer documentation](interactive_layer.md) for detailed information about its architecture and components. +The following sections describe the implementation details of the legacy drawing tools system. If you're using the new V2 implementation with `useDrawingToolsV2: true`, please refer to the [Interactive Layer documentation](core_concepts/interactive_layer.md) for detailed information about its architecture and components. The process initiates by opening the drawing tools dialog and selecting a preferred drawing tool. Subsequently, the user can add specific taps on the Deriv chart to start drawing with default configurations. From 29d1b20bea5f1c1784cfb780399dd339d8101481 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Mon, 23 Jun 2025 17:15:09 +0800 Subject: [PATCH 19/32] cleanup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3dbd4cbe..6cd059cad 100644 --- a/README.md +++ b/README.md @@ -426,7 +426,7 @@ _controller.scale(100); For more detailed information, check out these documentation files: -- [How Chart Library Works](doc/how_chart_lib_works.md) +- [How Chart Library Works](doc/core_concepts/how_chart_lib_works.md) - [Data Series](doc/data_series.png) - [Data Painters](doc/data_painters.png) - [Drawing Tools](doc/drawing_tools.md) From d7bd646ff4c2b69fc4204d726905b0fcc75868d6 Mon Sep 17 00:00:00 2001 From: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:21:35 +0800 Subject: [PATCH 20/32] Update custom_themes.md --- doc/advanced_usage/custom_themes.md | 75 +---------------------------- 1 file changed, 1 insertion(+), 74 deletions(-) diff --git a/doc/advanced_usage/custom_themes.md b/doc/advanced_usage/custom_themes.md index f7ffed87b..d057a4a1a 100644 --- a/doc/advanced_usage/custom_themes.md +++ b/doc/advanced_usage/custom_themes.md @@ -377,83 +377,10 @@ class _ThemeSwitcherExampleState extends State { } ``` -## Theme Presets - -You can create multiple theme presets for users to choose from: - -```dart -enum ChartThemePreset { - defaultLight, - defaultDark, - blueTheme, - greenTheme, - highContrast, -} - -ChartTheme getThemeForPreset(ChartThemePreset preset) { - switch (preset) { - case ChartThemePreset.defaultLight: - return ChartDefaultLightTheme(); - case ChartThemePreset.defaultDark: - return ChartDefaultDarkTheme(); - case ChartThemePreset.blueTheme: - return BlueTheme(); - case ChartThemePreset.greenTheme: - return GreenTheme(); - case ChartThemePreset.highContrast: - return HighContrastTheme(); - } -} - -class BlueTheme extends ChartDefaultDarkTheme { - @override - Color get backgroundColor => Color(0xFF0A1929); - - @override - GridStyle get gridStyle => GridStyle( - gridLineColor: Color(0xFF1E3A5F), - xLabelStyle: textStyle( - textStyle: caption2, - color: Color(0xFF90CAF9), - fontSize: 13, - ), - yLabelStyle: textStyle( - textStyle: caption2, - color: Color(0xFF90CAF9), - fontSize: 13, - ), - ); - - @override - LineStyle get defaultLineStyle => LineStyle( - color: Color(0xFF2196F3), - thickness: 2, - ); - - @override - CandleStyle get defaultCandleStyle => CandleStyle( - positiveColor: Color(0xFF4CAF50), - negativeColor: Color(0xFFF44336), - wickWidth: 1, - bodyWidth: 8, - ); -} -``` - -## Best Practices - -When creating custom themes, consider the following best practices: - -1. **Maintain consistency**: Use a consistent color palette and style across all chart elements -2. **Consider accessibility**: Ensure sufficient contrast between text and background colors -3. **Test with different data**: Test your theme with different types of data to ensure it works well in all scenarios -4. **Provide theme options**: Give users the ability to choose between different themes -5. **Consider dark mode**: Provide both light and dark themes for your charts - ## Next Steps Now that you understand how to create and use custom themes in the Deriv Chart library, you can explore: - [Performance Optimization](performance_optimization.md) - Learn how to optimize chart performance - [Real-time Data](real_time_data.md) - Understand how to handle real-time data updates -- [API Reference](../api_reference/chart_widget.md) - Explore the complete API \ No newline at end of file +- [API Reference](../api_reference/chart_widget.md) - Explore the complete API From 7cd648b7a64e4b40998562713a33b2cbbd437604 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Mon, 23 Jun 2025 17:23:34 +0800 Subject: [PATCH 21/32] cleanup --- doc/README.md | 1 - doc/advanced_usage/custom_themes.md | 1 - .../performance_optimization.md | 477 ------------------ doc/advanced_usage/real_time_data.md | 1 - 4 files changed, 480 deletions(-) delete mode 100644 doc/advanced_usage/performance_optimization.md diff --git a/doc/README.md b/doc/README.md index 195e13996..17d724d74 100644 --- a/doc/README.md +++ b/doc/README.md @@ -119,7 +119,6 @@ Explore the features available in the Deriv Chart library: Take your charts to the next level with advanced techniques: - [Custom Themes](advanced_usage/custom_themes.md) - Create custom chart themes -- [Performance Optimization](advanced_usage/performance_optimization.md) - Optimize chart performance - [Real-time Data](advanced_usage/real_time_data.md) - Handle real-time data updates - [Custom Indicators](advanced_usage/custom_indicators.md) - Create your own indicators - [Custom Drawing Tools](advanced_usage/custom_drawing_tools.md) - Create your own drawing tools diff --git a/doc/advanced_usage/custom_themes.md b/doc/advanced_usage/custom_themes.md index d057a4a1a..4a46a50f6 100644 --- a/doc/advanced_usage/custom_themes.md +++ b/doc/advanced_usage/custom_themes.md @@ -381,6 +381,5 @@ class _ThemeSwitcherExampleState extends State { Now that you understand how to create and use custom themes in the Deriv Chart library, you can explore: -- [Performance Optimization](performance_optimization.md) - Learn how to optimize chart performance - [Real-time Data](real_time_data.md) - Understand how to handle real-time data updates - [API Reference](../api_reference/chart_widget.md) - Explore the complete API diff --git a/doc/advanced_usage/performance_optimization.md b/doc/advanced_usage/performance_optimization.md deleted file mode 100644 index 6c237c5d8..000000000 --- a/doc/advanced_usage/performance_optimization.md +++ /dev/null @@ -1,477 +0,0 @@ -# Performance Optimization - -This document provides guidance on optimizing the performance of charts created with the Deriv Chart library, ensuring smooth rendering and interaction even with large datasets or on resource-constrained devices. - -## Introduction - -Financial charts often need to display large amounts of data while maintaining smooth scrolling, zooming, and interaction. The Deriv Chart library is designed with performance in mind, but there are several techniques you can use to further optimize your charts. - -## Data Management Techniques - -### Limit Visible Data - -The most effective way to improve performance is to limit the amount of data being processed and rendered: - -```dart -// Instead of passing all data to the chart -Chart( - mainSeries: LineSeries(allTicks), // Could be thousands of points - pipSize: 2, -) - -// Only pass the data you need to display -Chart( - mainSeries: LineSeries(visibleTicks), // Only the visible range - pipSize: 2, - onVisibleAreaChanged: (leftEpoch, rightEpoch) { - // Load more data if needed - loadDataForRange(leftEpoch, rightEpoch); - }, -) -``` - -### Data Downsampling - -For very large datasets, consider downsampling the data to reduce the number of points: - -```dart -List downsampleTicks(List ticks, int targetCount) { - if (ticks.length <= targetCount) return ticks; - - final step = ticks.length / targetCount; - final result = []; - - for (int i = 0; i < ticks.length; i += step.round()) { - result.add(ticks[i]); - } - - return result; -} - -// Use downsampled data for the chart -final downsampledTicks = downsampleTicks(allTicks, 500); -Chart( - mainSeries: LineSeries(downsampledTicks), - pipSize: 2, -) -``` - -### Lazy Loading - -Implement lazy loading to fetch data only when needed: - -```dart -class LazyLoadingChartExample extends StatefulWidget { - @override - State createState() => _LazyLoadingChartExampleState(); -} - -class _LazyLoadingChartExampleState extends State { - List _ticks = []; - bool _isLoading = false; - DateTime _oldestLoadedDate = DateTime.now(); - - @override - void initState() { - super.initState(); - _loadInitialData(); - } - - Future _loadInitialData() async { - setState(() { - _isLoading = true; - }); - - // Load the most recent data - final now = DateTime.now(); - final oneWeekAgo = now.subtract(Duration(days: 7)); - - final ticks = await fetchTickData(oneWeekAgo, now); - - setState(() { - _ticks = ticks; - _oldestLoadedDate = oneWeekAgo; - _isLoading = false; - }); - } - - Future _loadMoreData(int leftEpoch) async { - if (_isLoading) return; - - final leftDate = DateTime.fromMillisecondsSinceEpoch(leftEpoch); - - // Only load more data if we're near the edge of our loaded data - if (leftDate.isAfter(_oldestLoadedDate.add(Duration(days: 1)))) return; - - setState(() { - _isLoading = true; - }); - - final newStartDate = _oldestLoadedDate.subtract(Duration(days: 7)); - final newTicks = await fetchTickData(newStartDate, _oldestLoadedDate); - - setState(() { - _ticks = [...newTicks, ..._ticks]; - _oldestLoadedDate = newStartDate; - _isLoading = false; - }); - } - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - onVisibleAreaChanged: (leftEpoch, rightEpoch) { - _loadMoreData(leftEpoch); - }, - ), - if (_isLoading) - Positioned( - left: 16, - top: 16, - child: CircularProgressIndicator(), - ), - ], - ); - } - - Future> fetchTickData(DateTime start, DateTime end) async { - // Implement your data fetching logic here - // This could be an API call, database query, etc. - await Future.delayed(Duration(milliseconds: 500)); // Simulate network delay - return generateMockTickData(start, end); - } - - List generateMockTickData(DateTime start, DateTime end) { - // Generate mock data for demonstration - final result = []; - var current = start; - double price = 100 + Random().nextDouble() * 10; - - while (current.isBefore(end)) { - price += (Random().nextDouble() - 0.5) * 2; - result.add(Tick(epoch: current, quote: price)); - current = current.add(Duration(minutes: 5)); - } - - return result; - } -} -``` - -## Rendering Optimizations - -### Use RepaintBoundary - -Wrap your chart in a `RepaintBoundary` to isolate its rendering from the rest of your UI: - -```dart -RepaintBoundary( - child: Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - ), -) -``` - -### Optimize Chart Size - -Larger charts require more rendering resources. Consider using a smaller chart size or implementing responsive sizing: - -```dart -LayoutBuilder( - builder: (context, constraints) { - // Limit the chart height based on available space - final chartHeight = min(constraints.maxHeight, 400.0); - - return SizedBox( - height: chartHeight, - child: Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - ), - ); - }, -) -``` - -### Reduce Indicator Count - -Each indicator adds computational and rendering overhead. Limit the number of indicators displayed simultaneously: - -```dart -// Instead of many indicators -Chart( - mainSeries: CandleSeries(candles), - overlayConfigs: [ - BollingerBandsIndicatorConfig(), - MAIndicatorConfig(period: 14), - MAIndicatorConfig(period: 50), - MAIndicatorConfig(period: 200), - ], - bottomConfigs: [ - RSIIndicatorConfig(), - MACDIndicatorConfig(), - StochasticIndicatorConfig(), - ], - pipSize: 2, -) - -// Limit to the most important indicators -Chart( - mainSeries: CandleSeries(candles), - overlayConfigs: [ - MAIndicatorConfig(period: 50), - ], - bottomConfigs: [ - RSIIndicatorConfig(), - ], - pipSize: 2, -) -``` - -### Simplify Drawing Tools - -Complex drawing tools can impact performance. Use simpler tools or limit the number of active drawing tools: - -```dart -// Implement a limit on the number of drawing tools -class LimitedDrawingToolsExample extends StatefulWidget { - @override - State createState() => _LimitedDrawingToolsExampleState(); -} - -class _LimitedDrawingToolsExampleState extends State { - final _drawingToolsRepo = AddOnsRepository( - createAddOn: (Map map) => DrawingToolConfig.fromJson(map), - onEditCallback: (int index) {}, - sharedPrefKey: 'drawing_tools', - ); - - static const int _maxDrawingTools = 10; - - @override - Widget build(BuildContext context) { - return DerivChart( - mainSeries: CandleSeries(candles), - drawingToolsRepo: _drawingToolsRepo, - onDrawingToolAdded: (DrawingToolConfig config) { - // Check if we've reached the limit - if (_drawingToolsRepo.items.length > _maxDrawingTools) { - // Remove the oldest drawing tool - _drawingToolsRepo.removeAt(0); - - // Show a notification - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Maximum number of drawing tools reached. Oldest tool removed.'), - ), - ); - } - }, - ); - } -} -``` - -## Device-Specific Optimizations - -### Detect Device Capabilities - -Adjust chart complexity based on device capabilities: - -```dart -bool isLowEndDevice() { - // This is a simplified example. In a real app, you would use more - // sophisticated detection based on device model, available memory, etc. - return Platform.isAndroid && - (defaultTargetPlatform == TargetPlatform.android) && - !kIsWeb; -} - -Widget buildChartBasedOnDevice() { - final isLowEnd = isLowEndDevice(); - - return Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - showGrid: !isLowEnd, // Disable grid on low-end devices - showCrosshair: !isLowEnd, // Disable crosshair on low-end devices - // Reduce other visual elements for low-end devices - ); -} -``` - -### Optimize for Web - -When deploying to web, consider additional optimizations: - -```dart -bool isWebPlatform() { - return kIsWeb; -} - -Widget buildChartForWeb() { - final isWeb = isWebPlatform(); - - // For web, use a more aggressive data downsampling - final optimizedTicks = isWeb ? downsampleTicks(ticks, 300) : ticks; - - return Chart( - mainSeries: LineSeries(optimizedTicks), - pipSize: 2, - ); -} -``` - -## Memory Management - -### Dispose Controllers - -Always dispose of chart controllers when they're no longer needed: - -```dart -class ChartScreenState extends State { - final _chartController = ChartController(); - - @override - void dispose() { - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - controller: _chartController, - ); - } -} -``` - -### Clear Unused Data - -Clear data that's no longer needed to free up memory: - -```dart -void clearOldData() { - // Keep only the last 1000 ticks - if (allTicks.length > 1000) { - allTicks = allTicks.sublist(allTicks.length - 1000); - } -} -``` - -## Measuring Performance - -### Use Performance Overlay - -Flutter's performance overlay can help identify rendering issues: - -```dart -MaterialApp( - showPerformanceOverlay: true, - home: Scaffold( - body: Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - ), - ), -) -``` - -### Profile with DevTools - -Use Flutter DevTools to profile your app and identify performance bottlenecks: - -1. Run your app in debug mode -2. Open DevTools -3. Use the Performance tab to record and analyze performance - -### Custom Performance Metrics - -Implement custom performance metrics to track chart performance: - -```dart -class PerformanceMetricsExample extends StatefulWidget { - @override - State createState() => _PerformanceMetricsExampleState(); -} - -class _PerformanceMetricsExampleState extends State { - int _frameCount = 0; - double _averageFrameTime = 0; - Stopwatch _stopwatch = Stopwatch(); - - @override - void initState() { - super.initState(); - _stopwatch.start(); - - // Set up a timer to calculate FPS - Timer.periodic(Duration(seconds: 1), (timer) { - if (mounted) { - setState(() { - _averageFrameTime = _frameCount > 0 - ? _stopwatch.elapsedMilliseconds / _frameCount - : 0; - _frameCount = 0; - _stopwatch.reset(); - _stopwatch.start(); - }); - } - }); - } - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - onPaint: () { - // Increment frame count each time the chart is painted - _frameCount++; - }, - ), - Positioned( - top: 16, - right: 16, - child: Container( - padding: EdgeInsets.all(8), - color: Colors.black.withOpacity(0.7), - child: Text( - 'Avg frame time: ${_averageFrameTime.toStringAsFixed(2)} ms', - style: TextStyle(color: Colors.white), - ), - ), - ), - ], - ); - } -} -``` - -## Best Practices - -1. **Start simple and add complexity as needed**: Begin with a basic chart and add features incrementally -2. **Test on target devices**: Always test performance on the actual devices your users will use -3. **Monitor memory usage**: Keep an eye on memory usage, especially with large datasets -4. **Use efficient data structures**: Choose appropriate data structures for your use case -5. **Implement pagination**: For historical data, implement pagination to load data in chunks -6. **Optimize UI updates**: Minimize setState calls and use more granular state management -7. **Consider using compute for heavy calculations**: Move heavy calculations to a separate isolate - -## Next Steps - -Now that you understand how to optimize the performance of your charts, you can explore: - -- [Real-time Data](real_time_data.md) - Learn how to handle real-time data updates -- [Custom Indicators](custom_indicators.md) - Create efficient custom indicators -- [Custom Drawing Tools](custom_drawing_tools.md) - Implement optimized drawing tools \ No newline at end of file diff --git a/doc/advanced_usage/real_time_data.md b/doc/advanced_usage/real_time_data.md index 5fbecb49e..dbe781b22 100644 --- a/doc/advanced_usage/real_time_data.md +++ b/doc/advanced_usage/real_time_data.md @@ -685,6 +685,5 @@ class _HistoricalAndRealTimeChartExampleState extends State Date: Mon, 23 Jun 2025 17:24:40 +0800 Subject: [PATCH 22/32] cleanup --- doc/advanced_usage/real_time_data.md | 689 --------------------------- 1 file changed, 689 deletions(-) delete mode 100644 doc/advanced_usage/real_time_data.md diff --git a/doc/advanced_usage/real_time_data.md b/doc/advanced_usage/real_time_data.md deleted file mode 100644 index dbe781b22..000000000 --- a/doc/advanced_usage/real_time_data.md +++ /dev/null @@ -1,689 +0,0 @@ -# Real-time Data - -This document explains how to handle real-time data updates in the Deriv Chart library, enabling you to create dynamic charts that update as new market data arrives. - -## Introduction - -Financial charts often need to display real-time data, such as live market prices or streaming data feeds. The Deriv Chart library provides several mechanisms to efficiently handle real-time data updates without sacrificing performance. - -## Basic Real-time Updates - -### Updating the Data Series - -The simplest way to handle real-time data is to update the data series when new data arrives: - -```dart -class RealTimeChartExample extends StatefulWidget { - @override - State createState() => _RealTimeChartExampleState(); -} - -class _RealTimeChartExampleState extends State { - List _ticks = []; - late StreamSubscription _subscription; - - @override - void initState() { - super.initState(); - - // Subscribe to a data stream - _subscription = tickDataStream.listen((tick) { - setState(() { - _ticks.add(tick); - - // Optional: Limit the number of ticks to prevent memory issues - if (_ticks.length > 1000) { - _ticks = _ticks.sublist(_ticks.length - 1000); - } - }); - }); - } - - @override - void dispose() { - _subscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - ); - } -} -``` - -### Auto-scrolling to Latest Data - -To ensure the chart automatically scrolls to show the latest data: - -```dart -class AutoScrollingChartExample extends StatefulWidget { - @override - State createState() => _AutoScrollingChartExampleState(); -} - -class _AutoScrollingChartExampleState extends State { - List _ticks = []; - late StreamSubscription _subscription; - final _chartController = ChartController(); - bool _autoScroll = true; - - @override - void initState() { - super.initState(); - - // Subscribe to a data stream - _subscription = tickDataStream.listen((tick) { - setState(() { - _ticks.add(tick); - - // Optional: Limit the number of ticks to prevent memory issues - if (_ticks.length > 1000) { - _ticks = _ticks.sublist(_ticks.length - 1000); - } - - // Auto-scroll to the latest tick if enabled - if (_autoScroll) { - _chartController.scrollToLastTick(); - } - }); - }); - } - - @override - void dispose() { - _subscription.cancel(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text('Auto-scroll'), - Switch( - value: _autoScroll, - onChanged: (value) { - setState(() { - _autoScroll = value; - - // If auto-scroll is re-enabled, scroll to the latest tick - if (_autoScroll) { - _chartController.scrollToLastTick(); - } - }); - }, - ), - ], - ), - Expanded( - child: Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - controller: _chartController, - onVisibleAreaChanged: (leftEpoch, rightEpoch) { - // Disable auto-scroll if the user manually scrolls away from the latest data - if (_autoScroll && _ticks.isNotEmpty) { - final latestEpoch = _ticks.last.epoch.millisecondsSinceEpoch; - final rightEdgeTime = DateTime.fromMillisecondsSinceEpoch(rightEpoch); - - // If the latest tick is not visible, disable auto-scroll - if (latestEpoch > rightEpoch) { - setState(() { - _autoScroll = false; - }); - } - } - }, - ), - ), - ], - ); - } -} -``` - -## Handling Different Data Types - -### Real-time Tick Data - -For streaming tick data (time-price pairs): - -```dart -class RealTimeTickChartExample extends StatefulWidget { - @override - State createState() => _RealTimeTickChartExampleState(); -} - -class _RealTimeTickChartExampleState extends State { - List _ticks = []; - late StreamSubscription _subscription; - final _chartController = ChartController(); - - @override - void initState() { - super.initState(); - - // Subscribe to a tick data stream - _subscription = tickDataStream.listen((tickData) { - setState(() { - _ticks.add(Tick( - epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), - quote: tickData.price, - )); - - _chartController.scrollToLastTick(); - }); - }); - } - - @override - void dispose() { - _subscription.cancel(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - controller: _chartController, - ); - } -} -``` - -### Real-time OHLC Data - -For streaming OHLC (candle) data: - -```dart -class RealTimeCandleChartExample extends StatefulWidget { - @override - State createState() => _RealTimeCandleChartExampleState(); -} - -class _RealTimeCandleChartExampleState extends State { - List _candles = []; - Candle? _currentCandle; - late StreamSubscription _subscription; - final _chartController = ChartController(); - final int _granularity = 60; // 1-minute candles - - @override - void initState() { - super.initState(); - - // Subscribe to a tick data stream - _subscription = tickDataStream.listen((tickData) { - final tickTime = DateTime.fromMillisecondsSinceEpoch(tickData.timestamp); - final tickPrice = tickData.price; - - setState(() { - // Update or create the current candle - if (_currentCandle == null || _isNewCandlePeriod(tickTime)) { - // If we have a current candle, add it to the list - if (_currentCandle != null) { - _candles.add(_currentCandle!); - } - - // Create a new candle - _currentCandle = Candle( - epoch: _normalizeTime(tickTime), - open: tickPrice, - high: tickPrice, - low: tickPrice, - close: tickPrice, - ); - } else { - // Update the current candle - _currentCandle = Candle( - epoch: _currentCandle!.epoch, - open: _currentCandle!.open, - high: max(_currentCandle!.high, tickPrice), - low: min(_currentCandle!.low, tickPrice), - close: tickPrice, - ); - } - - _chartController.scrollToLastTick(); - }); - }); - } - - bool _isNewCandlePeriod(DateTime time) { - if (_currentCandle == null) return true; - - final currentPeriodStart = _currentCandle!.epoch; - final currentPeriodEnd = currentPeriodStart.add(Duration(seconds: _granularity)); - - return time.isAfter(currentPeriodEnd) || time.isAtSameMomentAs(currentPeriodEnd); - } - - DateTime _normalizeTime(DateTime time) { - // Normalize time to the start of the candle period - final seconds = (time.second ~/ _granularity) * _granularity; - return DateTime( - time.year, - time.month, - time.day, - time.hour, - time.minute, - seconds, - ); - } - - @override - void dispose() { - _subscription.cancel(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - // Create a copy of the candles list with the current candle - final displayCandles = [..._candles]; - if (_currentCandle != null) { - displayCandles.add(_currentCandle!); - } - - return Chart( - mainSeries: CandleSeries(displayCandles), - pipSize: 2, - granularity: _granularity, - controller: _chartController, - ); - } -} -``` - -## Optimizing Real-time Updates - -### Throttling Updates - -To prevent excessive UI updates, consider throttling the data updates: - -```dart -class ThrottledUpdateChartExample extends StatefulWidget { - @override - State createState() => _ThrottledUpdateChartExampleState(); -} - -class _ThrottledUpdateChartExampleState extends State { - List _ticks = []; - List _pendingTicks = []; - late StreamSubscription _subscription; - Timer? _updateTimer; - final _chartController = ChartController(); - - @override - void initState() { - super.initState(); - - // Subscribe to a tick data stream - _subscription = tickDataStream.listen((tickData) { - final tick = Tick( - epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), - quote: tickData.price, - ); - - // Add to pending ticks - _pendingTicks.add(tick); - - // Set up a timer to update the UI if not already set - _updateTimer ??= Timer(Duration(milliseconds: 100), _updateChart); - }); - } - - void _updateChart() { - if (_pendingTicks.isEmpty) return; - - setState(() { - _ticks.addAll(_pendingTicks); - _pendingTicks.clear(); - - // Optional: Limit the number of ticks - if (_ticks.length > 1000) { - _ticks = _ticks.sublist(_ticks.length - 1000); - } - - _chartController.scrollToLastTick(); - }); - - _updateTimer = null; - } - - @override - void dispose() { - _subscription.cancel(); - _updateTimer?.cancel(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - controller: _chartController, - ); - } -} -``` - -### Using ValueNotifier - -For more efficient updates, consider using `ValueNotifier` instead of `setState`: - -```dart -class ValueNotifierChartExample extends StatefulWidget { - @override - State createState() => _ValueNotifierChartExampleState(); -} - -class _ValueNotifierChartExampleState extends State { - final ValueNotifier> _ticksNotifier = ValueNotifier>([]); - late StreamSubscription _subscription; - final _chartController = ChartController(); - - @override - void initState() { - super.initState(); - - // Subscribe to a tick data stream - _subscription = tickDataStream.listen((tickData) { - final tick = Tick( - epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), - quote: tickData.price, - ); - - // Update the notifier value - final ticks = [..._ticksNotifier.value, tick]; - - // Optional: Limit the number of ticks - if (ticks.length > 1000) { - _ticksNotifier.value = ticks.sublist(ticks.length - 1000); - } else { - _ticksNotifier.value = ticks; - } - - _chartController.scrollToLastTick(); - }); - } - - @override - void dispose() { - _subscription.cancel(); - _ticksNotifier.dispose(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder>( - valueListenable: _ticksNotifier, - builder: (context, ticks, _) { - return Chart( - mainSeries: LineSeries(ticks), - pipSize: 2, - controller: _chartController, - ); - }, - ); - } -} -``` - -## Handling WebSocket Connections - -For real-time data from WebSocket connections: - -```dart -class WebSocketChartExample extends StatefulWidget { - @override - State createState() => _WebSocketChartExampleState(); -} - -class _WebSocketChartExampleState extends State { - List _ticks = []; - WebSocketChannel? _channel; - final _chartController = ChartController(); - bool _isConnected = false; - - @override - void initState() { - super.initState(); - _connectWebSocket(); - } - - void _connectWebSocket() { - _channel = WebSocketChannel.connect( - Uri.parse('wss://your-websocket-endpoint.com'), - ); - - setState(() { - _isConnected = true; - }); - - _channel!.stream.listen( - (data) { - // Parse the data - final jsonData = jsonDecode(data); - final tickData = TickData.fromJson(jsonData); - - setState(() { - _ticks.add(Tick( - epoch: DateTime.fromMillisecondsSinceEpoch(tickData.timestamp), - quote: tickData.price, - )); - - // Optional: Limit the number of ticks - if (_ticks.length > 1000) { - _ticks = _ticks.sublist(_ticks.length - 1000); - } - - _chartController.scrollToLastTick(); - }); - }, - onDone: () { - setState(() { - _isConnected = false; - }); - - // Attempt to reconnect after a delay - Future.delayed(Duration(seconds: 5), _connectWebSocket); - }, - onError: (error) { - setState(() { - _isConnected = false; - }); - - // Attempt to reconnect after a delay - Future.delayed(Duration(seconds: 5), _connectWebSocket); - }, - ); - - // Send subscription message - _channel!.sink.add(jsonEncode({ - 'type': 'subscribe', - 'symbol': 'BTCUSD', - })); - } - - @override - void dispose() { - _channel?.sink.close(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Container( - padding: EdgeInsets.all(8), - color: _isConnected ? Colors.green : Colors.red, - child: Text( - _isConnected ? 'Connected' : 'Disconnected', - style: TextStyle(color: Colors.white), - ), - ), - Expanded( - child: Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - controller: _chartController, - ), - ), - ], - ); - } -} -``` - -## Handling Historical and Real-time Data Together - -To display historical data and append real-time updates: - -```dart -class HistoricalAndRealTimeChartExample extends StatefulWidget { - @override - State createState() => _HistoricalAndRealTimeChartExampleState(); -} - -class _HistoricalAndRealTimeChartExampleState extends State { - List _ticks = []; - late StreamSubscription _subscription; - final _chartController = ChartController(); - bool _isLoading = true; - - @override - void initState() { - super.initState(); - - // Load historical data first - _loadHistoricalData().then((_) { - // Then subscribe to real-time updates - _subscribeToRealTimeData(); - }); - } - - Future _loadHistoricalData() async { - try { - // Fetch historical data - final historicalData = await fetchHistoricalData(); - - setState(() { - _ticks = historicalData.map((data) => Tick( - epoch: DateTime.fromMillisecondsSinceEpoch(data.timestamp), - quote: data.price, - )).toList(); - - _isLoading = false; - }); - } catch (e) { - setState(() { - _isLoading = false; - }); - - // Show error - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to load historical data: $e')), - ); - } - } - - void _subscribeToRealTimeData() { - _subscription = tickDataStream.listen((tickData) { - // Check if this tick is newer than our latest historical tick - final tickTime = DateTime.fromMillisecondsSinceEpoch(tickData.timestamp); - - if (_ticks.isEmpty || tickTime.isAfter(_ticks.last.epoch)) { - setState(() { - _ticks.add(Tick( - epoch: tickTime, - quote: tickData.price, - )); - - _chartController.scrollToLastTick(); - }); - } - }); - } - - @override - void dispose() { - _subscription.cancel(); - _chartController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (_isLoading) { - return Center(child: CircularProgressIndicator()); - } - - return Chart( - mainSeries: LineSeries(_ticks), - pipSize: 2, - controller: _chartController, - ); - } - - Future> fetchHistoricalData() async { - // Implement your historical data fetching logic here - await Future.delayed(Duration(seconds: 1)); // Simulate network delay - return generateMockHistoricalData(); - } - - List generateMockHistoricalData() { - // Generate mock historical data - final now = DateTime.now(); - final result = []; - double price = 100; - - for (int i = 0; i < 100; i++) { - final time = now.subtract(Duration(minutes: 100 - i)); - price += (Random().nextDouble() - 0.5) * 2; - - result.add(TickData( - timestamp: time.millisecondsSinceEpoch, - price: price, - )); - } - - return result; - } -} -``` - -## Best Practices - -1. **Limit data points**: Keep the number of data points reasonable to maintain performance -2. **Throttle updates**: Don't update the UI on every tick; batch updates for better performance -3. **Handle connection issues**: Implement reconnection logic for WebSocket connections -4. **Provide visual feedback**: Show connection status and loading indicators -5. **Optimize memory usage**: Remove old data points that are no longer needed -6. **Use appropriate data structures**: Choose efficient data structures for your use case -7. **Test with realistic data rates**: Test with realistic data rates to ensure your app can handle the load - -## Next Steps - -Now that you understand how to handle real-time data in the Deriv Chart library, you can explore: - -- [Custom Indicators](custom_indicators.md) - Create custom indicators -- [Custom Drawing Tools](custom_drawing_tools.md) - Implement custom drawing tools \ No newline at end of file From 1b7195433b34eafa3a547a111db0a3623f05da65 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Mon, 23 Jun 2025 17:27:18 +0800 Subject: [PATCH 23/32] docs cleanup --- doc/README.md | 1 - doc/advanced_usage/custom_themes.md | 1 - doc/core_concepts/architecture.md | 18 +++++++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/doc/README.md b/doc/README.md index 17d724d74..9ce02837e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -119,7 +119,6 @@ Explore the features available in the Deriv Chart library: Take your charts to the next level with advanced techniques: - [Custom Themes](advanced_usage/custom_themes.md) - Create custom chart themes -- [Real-time Data](advanced_usage/real_time_data.md) - Handle real-time data updates - [Custom Indicators](advanced_usage/custom_indicators.md) - Create your own indicators - [Custom Drawing Tools](advanced_usage/custom_drawing_tools.md) - Create your own drawing tools diff --git a/doc/advanced_usage/custom_themes.md b/doc/advanced_usage/custom_themes.md index 4a46a50f6..4a9db7804 100644 --- a/doc/advanced_usage/custom_themes.md +++ b/doc/advanced_usage/custom_themes.md @@ -381,5 +381,4 @@ class _ThemeSwitcherExampleState extends State { Now that you understand how to create and use custom themes in the Deriv Chart library, you can explore: -- [Real-time Data](real_time_data.md) - Understand how to handle real-time data updates - [API Reference](../api_reference/chart_widget.md) - Explore the complete API diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index eb2fce42c..8d9cf60b7 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -156,7 +156,7 @@ These functions enable plotting any data point on the canvas and handling user i ## Data Visualization -The chart library uses a flexible data visualization system: +The chart library uses a flexible data visualization system built around the `ChartData` interface: ``` ┌─────────────┐ @@ -188,6 +188,11 @@ ChartData is an abstract class representing any data that can be displayed on th - Annotations (barriers) - Markers +The `ChartData` interface defines the core contract for all visual elements that can be rendered on a chart. It provides methods for: +- Data updates based on viewport changes +- Min/max value calculation for Y-axis scaling +- Custom painting on the canvas + ### Series Series is the base class for all chart series, handling: @@ -209,6 +214,17 @@ DataSeries extends Series to handle sequential data with: - **OHLCSeries**: Displays OHLC charts from OHLC data - **Indicator Series**: Displays technical indicators +### Rendering Process + +The data visualization system follows a 4-step rendering pipeline that is triggered whenever the viewport changes: + +1. **Viewport Changes**: User interactions trigger zoom or scroll events +2. **Data Update**: Chart data objects update their visible data based on new viewport bounds +3. **Y-Axis Calculation**: Overall min/max values are calculated for proper scaling +4. **Painting**: Custom painters render all visual elements on the canvas + +For detailed information about how ChartData rendering works, including the complete rendering pipeline, coordinate mapping, and optimization techniques, see [Rendering Pipeline](rendering_pipeline.md). + ## Painter System The chart uses a custom painting system to render data: From a0a3b730d9f63e6ede75e3f5812899613eadb0e9 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Mon, 23 Jun 2025 19:51:00 +0800 Subject: [PATCH 24/32] docs cleanup --- doc/README.md | 1 - doc/core_concepts/architecture.md | 1 - doc/core_concepts/coordinate_system.md | 1 - doc/core_concepts/data_models.md | 584 ------------------------- 4 files changed, 587 deletions(-) delete mode 100644 doc/core_concepts/data_models.md diff --git a/doc/README.md b/doc/README.md index 9ce02837e..12a346b89 100644 --- a/doc/README.md +++ b/doc/README.md @@ -83,7 +83,6 @@ If you're new to the Deriv Chart library, start here to learn the basics: Understand the fundamental concepts behind the Deriv Chart library: - [Architecture](core_concepts/architecture.md) - Overview of the library's architecture -- [Data Models](core_concepts/data_models.md) - Learn about the data structures - [Coordinate System](core_concepts/coordinate_system.md) - How coordinates are managed - [Rendering Pipeline](core_concepts/rendering_pipeline.md) - How data is rendered diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index 8d9cf60b7..e490b1fb0 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -386,6 +386,5 @@ AddOnsRepository is a ChangeNotifier that: Now that you understand the architecture of the Deriv Chart library, you can explore: -- [Data Models](data_models.md) - Learn about the data structures used in the chart - [Coordinate System](coordinate_system.md) - Understand how coordinates are managed - [Rendering Pipeline](rendering_pipeline.md) - Explore how data is rendered on the canvas \ No newline at end of file diff --git a/doc/core_concepts/coordinate_system.md b/doc/core_concepts/coordinate_system.md index 4bb7521c1..50d1efba3 100644 --- a/doc/core_concepts/coordinate_system.md +++ b/doc/core_concepts/coordinate_system.md @@ -229,5 +229,4 @@ Now that you understand the coordinate system used in the Deriv Chart library, y - [Rendering Pipeline](rendering_pipeline.md) - Learn how data is rendered on the canvas - [Architecture](architecture.md) - Understand the overall architecture of the library -- [Data Models](data_models.md) - Explore the data models used in the library - [Interactive Layer](../features/drawing_tools/overview.md) - Learn about the interactive drawing tools \ No newline at end of file diff --git a/doc/core_concepts/data_models.md b/doc/core_concepts/data_models.md deleted file mode 100644 index 5181ab670..000000000 --- a/doc/core_concepts/data_models.md +++ /dev/null @@ -1,584 +0,0 @@ -# Data Models - -This document explains the key data models used in the Deriv Chart library, their properties, and how they relate to each other. - -## Overview - -The Deriv Chart library uses several data models to represent financial data and chart elements: - -1. **Market Data Models**: Represent financial market data (Tick, Candle) -2. **Series Models**: Represent data series for visualization (LineSeries, CandleSeries) -3. **Annotation Models**: Represent chart annotations (Barriers, TickIndicator) -4. **Marker Models**: Represent point markers on the chart -5. **Indicator Models**: Represent technical indicators - -## Market Data Models - -### Tick - -The `Tick` class represents a single price point at a specific time: - -```dart -class Tick { - /// The timestamp of the tick - final DateTime epoch; - - /// The price value - final double quote; - - Tick({ - required this.epoch, - required this.quote, - }); -} -``` - -Ticks are used for: -- Line charts -- Real-time price updates -- Entry/exit points -- Tick indicators - -### Candle (OHLC) - -The `Candle` class represents price movement over a time period with open, high, low, and close values: - -```dart -class Candle { - /// The timestamp of the candle (typically the opening time) - final DateTime epoch; - - /// The opening price - final double open; - - /// The highest price during the period - final double high; - - /// The lowest price during the period - final double low; - - /// The closing price - final double close; - - Candle({ - required this.epoch, - required this.open, - required this.high, - required this.low, - required this.close, - }); - - /// Whether the candle is bullish (close > open) - bool get isBullish => close > open; - - /// Whether the candle is bearish (close < open) - bool get isBearish => close < open; -} -``` - -Candles are used for: -- Candlestick charts -- OHLC charts -- Hollow candlestick charts -- Technical indicators - -## Series Models - -### Series - -`Series` is the base class for all chart series: - -```dart -abstract class Series extends ChartData { - /// Creates a painter for this series - SeriesPainter createPainter(); - - /// The minimum value in the visible range - double get minValue; - - /// The maximum value in the visible range - double get maxValue; - - /// Updates the visible range - void updateVisibleRange(int leftEpoch, int rightEpoch); -} -``` - -### DataSeries - -`DataSeries` extends `Series` to handle sequential data: - -```dart -abstract class DataSeries extends Series { - /// The list of data points - final List data; - - /// The visible data points - List visibleData = []; - - DataSeries(this.data); - - /// Updates the visible data based on the visible range - @override - void updateVisibleRange(int leftEpoch, int rightEpoch) { - visibleData = _getVisibleData(leftEpoch, rightEpoch); - } - - /// Gets the data points within the visible range - List _getVisibleData(int leftEpoch, int rightEpoch); -} -``` - -### LineSeries - -`LineSeries` represents a line chart: - -```dart -class LineSeries extends DataSeries { - /// The style of the line - final LineStyle style; - - LineSeries( - List ticks, { - this.style = const LineStyle(), - }) : super(ticks); - - @override - SeriesPainter createPainter() => LinePainter(this); -} -``` - -### CandleSeries - -`CandleSeries` represents a candlestick chart: - -```dart -class CandleSeries extends DataSeries { - /// The style of the candles - final CandleStyle style; - - CandleSeries( - List candles, { - this.style = const CandleStyle(), - }) : super(candles); - - @override - SeriesPainter createPainter() => CandlePainter(this); -} -``` - -### OHLCSeries - -`OHLCSeries` represents an OHLC chart: - -```dart -class OHLCSeries extends DataSeries { - /// The style of the OHLC bars - final OHLCStyle style; - - OHLCSeries( - List candles, { - this.style = const OHLCStyle(), - }) : super(candles); - - @override - SeriesPainter createPainter() => OHLCPainter(this); -} -``` - -### HollowCandleSeries - -`HollowCandleSeries` represents a hollow candlestick chart: - -```dart -class HollowCandleSeries extends DataSeries { - /// The style of the hollow candles - final HollowCandleStyle style; - - HollowCandleSeries( - List candles, { - this.style = const HollowCandleStyle(), - }) : super(candles); - - @override - SeriesPainter createPainter() => HollowCandlePainter(this); -} -``` - -## Annotation Models - -### ChartAnnotation - -`ChartAnnotation` is the base class for all chart annotations: - -```dart -abstract class ChartAnnotation extends Series { - /// The unique identifier of the annotation - final String id; - - ChartAnnotation({String? id}) : id = id ?? uuid.v4(); - - /// Creates a chart object for this annotation - ChartObject createObject(); -} -``` - -### Barrier - -`Barrier` is the base class for horizontal and vertical barriers: - -```dart -abstract class Barrier extends ChartAnnotation { - /// The title of the barrier - final String? title; - - Barrier({ - this.title, - super.id, - }); -} -``` - -### HorizontalBarrier - -`HorizontalBarrier` represents a horizontal line at a specific price level: - -```dart -class HorizontalBarrier extends Barrier { - /// The price level of the barrier - final double value; - - /// The style of the barrier - final HorizontalBarrierStyle style; - - /// The visibility behavior of the barrier - final HorizontalBarrierVisibility visibility; - - HorizontalBarrier( - this.value, { - super.title, - super.id, - this.style = const HorizontalBarrierStyle(), - this.visibility = HorizontalBarrierVisibility.normal, - }); - - @override - ChartObject createObject() => BarrierObject( - value: value, - epoch: null, - title: title, - ); - - @override - SeriesPainter createPainter() => HorizontalBarrierPainter(this); -} -``` - -### VerticalBarrier - -`VerticalBarrier` represents a vertical line at a specific timestamp: - -```dart -class VerticalBarrier extends Barrier { - /// The timestamp of the barrier - final DateTime epoch; - - /// The style of the barrier - final VerticalBarrierStyle style; - - VerticalBarrier( - this.epoch, { - super.title, - super.id, - this.style = const VerticalBarrierStyle(), - }); - - @override - ChartObject createObject() => BarrierObject( - value: null, - epoch: epoch, - title: title, - ); - - @override - SeriesPainter createPainter() => VerticalBarrierPainter(this); -} -``` - -### TickIndicator - -`TickIndicator` is a special type of horizontal barrier that represents a specific tick: - -```dart -class TickIndicator extends HorizontalBarrier { - /// The tick being indicated - final Tick tick; - - TickIndicator( - this.tick, { - super.id, - super.style = const HorizontalBarrierStyle(), - super.visibility = HorizontalBarrierVisibility.forceToStayOnRange, - }) : super( - tick.quote, - title: tick.quote.toString(), - ); -} -``` - -## Marker Models - -### MarkerSeries - -`MarkerSeries` represents a collection of markers on the chart: - -```dart -class MarkerSeries extends Series { - /// The list of markers - final List markers; - - /// The active marker (highlighted) - final ActiveMarker? activeMarker; - - /// The entry tick marker - final Tick? entryTick; - - /// The exit tick marker - final Tick? exitTick; - - MarkerSeries( - this.markers, { - this.activeMarker, - this.entryTick, - this.exitTick, - }); - - @override - SeriesPainter createPainter() => MarkerPainter(this); -} -``` - -### Marker - -`Marker` represents a point marker on the chart: - -```dart -class Marker { - /// The timestamp of the marker - final DateTime epoch; - - /// The price level of the marker - final double quote; - - /// The type of marker (up, down, neutral) - final MarkerType type; - - /// Callback when the marker is tapped - final VoidCallback? onTap; - - Marker({ - required this.epoch, - required this.quote, - required this.type, - this.onTap, - }); - - /// Creates an up marker - factory Marker.up({ - required DateTime epoch, - required double quote, - VoidCallback? onTap, - }) => Marker( - epoch: epoch, - quote: quote, - type: MarkerType.up, - onTap: onTap, - ); - - /// Creates a down marker - factory Marker.down({ - required DateTime epoch, - required double quote, - VoidCallback? onTap, - }) => Marker( - epoch: epoch, - quote: quote, - type: MarkerType.down, - onTap: onTap, - ); -} -``` - -### ActiveMarker - -`ActiveMarker` represents a highlighted marker on the chart: - -```dart -class ActiveMarker { - /// The timestamp of the marker - final DateTime epoch; - - /// The price level of the marker - final double quote; - - /// Callback when the marker is tapped - final VoidCallback? onTap; - - /// Callback when the user taps outside the marker - final VoidCallback? onOutsideTap; - - ActiveMarker({ - required this.epoch, - required this.quote, - this.onTap, - this.onOutsideTap, - }); -} -``` - -## Indicator Models - -### IndicatorConfig - -`IndicatorConfig` is the base class for all indicator configurations: - -```dart -abstract class IndicatorConfig { - /// The unique identifier of the indicator - final String id; - - /// The name of the indicator - String get name; - - /// Whether the indicator is displayed on the main chart or in a separate chart - bool get isOverlay; - - IndicatorConfig({String? id}) : id = id ?? uuid.v4(); - - /// Creates a series for this indicator - Series createSeries(List candles); - - /// Converts the config to JSON for storage - Map toJson(); - - /// Creates a config from JSON - factory IndicatorConfig.fromJson(Map json); -} -``` - -### Moving Average Indicator - -Example of a specific indicator configuration: - -```dart -class MAIndicatorConfig extends IndicatorConfig { - /// The period of the moving average - final int period; - - /// The type of moving average - final MovingAverageType type; - - /// The style of the line - final LineStyle lineStyle; - - MAIndicatorConfig({ - required this.period, - this.type = MovingAverageType.simple, - this.lineStyle = const LineStyle(), - super.id, - }); - - @override - String get name => '$type MA ($period)'; - - @override - bool get isOverlay => true; - - @override - Series createSeries(List candles) => MASeries( - candles, - period: period, - type: type, - lineStyle: lineStyle, - ); - - @override - Map toJson() => { - 'id': id, - 'type': 'MA', - 'period': period, - 'maType': type.index, - 'lineStyle': lineStyle.toJson(), - }; - - factory MAIndicatorConfig.fromJson(Map json) => MAIndicatorConfig( - id: json['id'], - period: json['period'], - type: MovingAverageType.values[json['maType']], - lineStyle: LineStyle.fromJson(json['lineStyle']), - ); -} -``` - -## Drawing Tool Models - -### DrawingToolConfig - -`DrawingToolConfig` is the base class for all drawing tool configurations: - -```dart -abstract class DrawingToolConfig { - /// The unique identifier of the drawing tool - final String id; - - /// The name of the drawing tool - String get name; - - DrawingToolConfig({String? id}) : id = id ?? uuid.v4(); - - /// Creates a drawing for this tool - Drawing createDrawing(); - - /// Converts the config to JSON for storage - Map toJson(); - - /// Creates a config from JSON - factory DrawingToolConfig.fromJson(Map json); -} -``` - -### InteractableDrawing - -`InteractableDrawing` is the base class for all interactive drawing tools: - -```dart -abstract class InteractableDrawing { - /// The current state of the drawing - DrawingToolState state; - - /// The style of the drawing - final DrawingPaintStyle style; - - InteractableDrawing({ - this.state = DrawingToolState.normal, - required this.style, - }); - - /// Tests if the point is inside the drawing - bool hitTest(Offset point); - - /// Paints the drawing on the canvas - void paint(Canvas canvas, Size size); - - /// Handles drag operations - void onDrag(Offset delta); -} -``` - -## Next Steps - -Now that you understand the data models used in the Deriv Chart library, you can explore: - -- [Coordinate System](coordinate_system.md) - Learn how coordinates are managed -- [Rendering Pipeline](rendering_pipeline.md) - Understand how data is rendered -- [API Reference](../api_reference/series_classes.md) - Explore the complete API \ No newline at end of file From 8f1dde2cd30e76f768119abd00e19e867c81bb98 Mon Sep 17 00:00:00 2001 From: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:23:47 +0800 Subject: [PATCH 25/32] Update architecture.md --- doc/core_concepts/architecture.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index e490b1fb0..42227758c 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -26,6 +26,7 @@ The Deriv Chart library follows a layered architecture with clear separation of - Manages chart data entries and live data state - Handles data fit mode and zoom level (msPerPx) - Controls scroll animation and visible area changes + - Provides the conversion function for coordinate system to work to convert epoch to X position in the canvas and vice versa. These conversion functions are shared among all components of the chart. 2. **GestureManager**: The middle layer that: - Handles user interactions (pan, zoom, tap) @@ -37,6 +38,7 @@ The Deriv Chart library follows a layered architecture with clear separation of - Coordinates shared X-axis between charts - Manages Y-axis for each chart section - Renders data visualization + - Each chart provides the conversion function for coordinate system to work to convert the quote to Y position in canvas and vice versa This layered structure ensures: - Clear separation of concerns @@ -387,4 +389,4 @@ AddOnsRepository is a ChangeNotifier that: Now that you understand the architecture of the Deriv Chart library, you can explore: - [Coordinate System](coordinate_system.md) - Understand how coordinates are managed -- [Rendering Pipeline](rendering_pipeline.md) - Explore how data is rendered on the canvas \ No newline at end of file +- [Rendering Pipeline](rendering_pipeline.md) - Explore how data is rendered on the canvas From 34ead98818bc0d9ca0e43f4223bcd26576b1adee Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 24 Jun 2025 13:30:48 +0800 Subject: [PATCH 26/32] some changes on doc --- doc/core_concepts/architecture.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index 42227758c..2fc0e04a9 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -149,8 +149,11 @@ The Y-axis is managed by each chart (MainChart and BottomCharts) and uses: ### Coordinate Conversion The chart provides conversion functions: -- `xFromEpoch`: Converts timestamp to X-coordinate -- `yFromQuote`: Converts price to Y-coordinate +Provided by XAxisWrapper and sepcifically XAxisModel which is provided at the root of the chart widget hirerachy: + - `xFromEpoch`: Converts timestamp to X-coordinate + - `yFromQuote`: Converts price to Y-coordinate + +Provided by each chart widget (main chart and bottom charts) to componenet in each chart. - `epochFromX`: Converts X-coordinate to timestamp - `quoteFromY`: Converts Y-coordinate to price @@ -204,10 +207,8 @@ Series is the base class for all chart series, handling: ### DataSeries -DataSeries extends Series to handle sequential data with: -- Sorted data management -- Visible data calculation -- Min/max value determination +DataSeries extends Series to handle sequential sorted data with. +Every chart type and many indicators are an implementation of this class. ### Specific Series Types From 07efc5e68dfe4e91e8d5f74b14626a378261ed5f Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 24 Jun 2025 13:32:25 +0800 Subject: [PATCH 27/32] some changes on doc --- doc/core_concepts/architecture.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index 2fc0e04a9..20e562ddc 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -26,7 +26,7 @@ The Deriv Chart library follows a layered architecture with clear separation of - Manages chart data entries and live data state - Handles data fit mode and zoom level (msPerPx) - Controls scroll animation and visible area changes - - Provides the conversion function for coordinate system to work to convert epoch to X position in the canvas and vice versa. These conversion functions are shared among all components of the chart. + - Provides the conversion functions for the coordinate system to convert epoch to X position in the canvas and vice versa. These conversion functions are shared among all components of the chart. 2. **GestureManager**: The middle layer that: - Handles user interactions (pan, zoom, tap) @@ -38,7 +38,7 @@ The Deriv Chart library follows a layered architecture with clear separation of - Coordinates shared X-axis between charts - Manages Y-axis for each chart section - Renders data visualization - - Each chart provides the conversion function for coordinate system to work to convert the quote to Y position in canvas and vice versa + - Each chart provides the conversion functions for the coordinate system to convert the quote to Y position in the canvas and vice versa This layered structure ensures: - Clear separation of concerns @@ -149,11 +149,11 @@ The Y-axis is managed by each chart (MainChart and BottomCharts) and uses: ### Coordinate Conversion The chart provides conversion functions: -Provided by XAxisWrapper and sepcifically XAxisModel which is provided at the root of the chart widget hirerachy: +Provided by XAxisWrapper and specifically XAxisModel which is provided at the root of the chart widget hierarchy: - `xFromEpoch`: Converts timestamp to X-coordinate - `yFromQuote`: Converts price to Y-coordinate -Provided by each chart widget (main chart and bottom charts) to componenet in each chart. +Provided by each chart widget (main chart and bottom charts) to components in each chart. - `epochFromX`: Converts X-coordinate to timestamp - `quoteFromY`: Converts Y-coordinate to price @@ -207,7 +207,7 @@ Series is the base class for all chart series, handling: ### DataSeries -DataSeries extends Series to handle sequential sorted data with. +DataSeries extends Series to handle sequential sorted data. Every chart type and many indicators are an implementation of this class. ### Specific Series Types From 48619f3fcecf8954799a287c43edbbf458bb5c07 Mon Sep 17 00:00:00 2001 From: ramin-deriv Date: Tue, 24 Jun 2025 13:34:50 +0800 Subject: [PATCH 28/32] some changes on doc --- doc/core_concepts/architecture.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/core_concepts/architecture.md b/doc/core_concepts/architecture.md index 20e562ddc..3dd995547 100644 --- a/doc/core_concepts/architecture.md +++ b/doc/core_concepts/architecture.md @@ -159,6 +159,8 @@ Provided by each chart widget (main chart and bottom charts) to components in ea These functions enable plotting any data point on the canvas and handling user interactions. +For detailed information about the coordinate system implementation and usage, see [Coordinate System](coordinate_system.md). + ## Data Visualization The chart library uses a flexible data visualization system built around the `ChartData` interface: @@ -358,6 +360,8 @@ Users can create custom themes by: - Implementing the ChartTheme interface - Extending one of the default themes and overriding specific properties +For detailed information about creating and customizing themes, see [Custom Themes](../advanced_usage/custom_themes.md). + ## DerivChart DerivChart is a wrapper around the Chart widget that: From 794218bc421a7d6bfcc9a58bcf1185ca6cec1f8e Mon Sep 17 00:00:00 2001 From: raminvakili8 <55975218+raminvakili8@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:47:01 +0800 Subject: [PATCH 29/32] Update README.md Remove redundant doc --- doc/README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/doc/README.md b/doc/README.md index 12a346b89..fb56478be 100644 --- a/doc/README.md +++ b/doc/README.md @@ -99,15 +99,8 @@ Explore the features available in the Deriv Chart library: ### Technical Analysis - [Indicators](features/indicators/overview.md) - Add technical indicators - - Moving Averages - - Oscillators - - Volatility Indicators - - Trend Indicators -- [Drawing Tools](features/drawing_tools/overview.md) - Use interactive drawing tools - - Lines and Channels - - Fibonacci Tools - - Geometric Shapes +- [Drawing Tools](features/drawing_tools/overview.md) - Use interactive layer drawing tools ### Interactive Layer @@ -191,4 +184,4 @@ If you need help with the Deriv Chart library, you can: ## License -The Deriv Chart library is licensed under the [MIT License](../LICENSE). \ No newline at end of file +The Deriv Chart library is licensed under the [MIT License](../LICENSE). From 16bf2856c17d30031cece9ea58f7dbd260a02ecd Mon Sep 17 00:00:00 2001 From: raminvakili8 <55975218+raminvakili8@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:50:04 +0800 Subject: [PATCH 30/32] Update README.md --- doc/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/README.md b/doc/README.md index fb56478be..98e354d52 100644 --- a/doc/README.md +++ b/doc/README.md @@ -142,13 +142,6 @@ The library includes several examples to help you get started: - [Chart with Annotations](../example/lib/examples/features/annotations_example.dart) - [Chart with Markers](../example/lib/examples/features/markers_example.dart) -### Advanced Examples - -- [Real-time Chart](../example/lib/examples/advanced/real_time_chart_example.dart) -- [Custom Theme](../example/lib/examples/advanced/custom_theme_example.dart) -- [Custom Indicator](../example/lib/examples/advanced/custom_indicator_example.dart) -- [Custom Drawing Tool](../example/lib/examples/advanced/custom_drawing_tool_example.dart) - You can find these examples in the `example` directory of the repository. ## Showcase App From fefb1a832588f879e5d81e5b32728c05c48b53cf Mon Sep 17 00:00:00 2001 From: raminvakili8 <55975218+raminvakili8@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:52:31 +0800 Subject: [PATCH 31/32] Update README.md --- doc/README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/doc/README.md b/doc/README.md index 98e354d52..0345ce394 100644 --- a/doc/README.md +++ b/doc/README.md @@ -157,24 +157,13 @@ The `showcase_app` directory contains a complete Flutter application that demons Learn how to contribute to the Deriv Chart library: - [Contribution Guidelines](contribution.md) - How to contribute -- [Development Setup](development_setup.md) - Set up your development environment -- [Code Style](code_style.md) - Follow the code style guidelines -- [Testing](testing.md) - Write and run tests ## Compatibility - **Flutter**: 3.10.1 or higher - **Dart**: 3.0.0 or higher - **Platforms**: iOS, Android, Web, macOS, Windows, Linux - -## Support - -If you need help with the Deriv Chart library, you can: - -- Check the [FAQ](faq.md) for common questions -- Open an issue on [GitHub](https://github.com/your-organization/deriv-chart/issues) -- Contact the maintainers at support@example.com - + ## License The Deriv Chart library is licensed under the [MIT License](../LICENSE). From d4cf57375184c2c4e8b140c17f17451736bcd009 Mon Sep 17 00:00:00 2001 From: raminvakili8 <55975218+raminvakili8@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:57:56 +0800 Subject: [PATCH 32/32] Update README.md --- doc/README.md | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/doc/README.md b/doc/README.md index 0345ce394..50708d3a5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -110,39 +110,7 @@ Explore the features available in the Deriv Chart library: Take your charts to the next level with advanced techniques: -- [Custom Themes](advanced_usage/custom_themes.md) - Create custom chart themes -- [Custom Indicators](advanced_usage/custom_indicators.md) - Create your own indicators -- [Custom Drawing Tools](advanced_usage/custom_drawing_tools.md) - Create your own drawing tools - -## API Reference - -Detailed API documentation for the Deriv Chart library: - -- [Chart Widget](api_reference/chart_widget.md) - The main chart widget -- [Series Classes](api_reference/series_classes.md) - Data series classes -- [Indicator Classes](api_reference/indicator_classes.md) - Technical indicator classes -- [Drawing Tool Classes](api_reference/drawing_tool_classes.md) - Drawing tool classes -- [Theme Classes](api_reference/theme_classes.md) - Theme customization classes - -## Examples - -The library includes several examples to help you get started: - -### Basic Examples - -- [Line Chart](../example/lib/examples/basic/line_chart_example.dart) -- [Candlestick Chart](../example/lib/examples/basic/candle_chart_example.dart) -- [OHLC Chart](../example/lib/examples/basic/ohlc_chart_example.dart) -- [Hollow Candlestick Chart](../example/lib/examples/basic/hollow_candle_example.dart) - -### Feature Examples - -- [Chart with Indicators](../example/lib/examples/features/indicators_example.dart) -- [Chart with Drawing Tools](../example/lib/examples/features/drawing_tools_example.dart) -- [Chart with Annotations](../example/lib/examples/features/annotations_example.dart) -- [Chart with Markers](../example/lib/examples/features/markers_example.dart) - -You can find these examples in the `example` directory of the repository. +- [Custom Themes guid](advanced_usage/custom_themes.md) - Create custom chart themes ## Showcase App