diff --git a/CHANGELOG.md b/CHANGELOG.md index ac071598e..bed13a59b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [0.1.0] - TODO: Add release date. + +* Add drawing tools. +* Make the chart compatible with the `flutter_web` platform. + ## [0.0.1] - TODO: Add release date. -* TODO: Describe initial release. +* TODO: Describe initial release. \ No newline at end of file diff --git a/docs/drawing_tools.md b/docs/drawing_tools.md new file mode 100644 index 000000000..10a5f9d84 --- /dev/null +++ b/docs/drawing_tools.md @@ -0,0 +1,42 @@ +# Drawing Tools + +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. + +The GestureDetector on the Deriv chart, utilized by the `drawingCreator` captures user input. Within the `onTap` method, every captured input will be added to the list of edgePoints. Simultaneously, the `drawingParts` list is created to store the drawing parts. Both lists are then passed to the `onAddDrawing` callback, which adds the complete drawing to the drawing repository and saves it in shared preferences based on the active Symbol, so the drawing data can be retrieved based on the chart's symbol. + +During the addition of drawing parts to the `drawingParts` list, each instance of the drawing is initialized. This initialization triggers the relevant `onPaint` method, allowing each drawing part to be painted to the chart. Since we maintain a list of `drawingParts`, each of which is an instance of a drawing, every drawing part has its own `onPaint` and `hitTest` methods inherited from CustomPaint. Consequently, any modifications in the drawing's position, such as dragging, result in the `repaint` and `hitTest` methods being invoked for each drawing part. For a more detailed explanation, please refer to the sections titled DrawingPainter, EdgePoints, and DraggableEdgePoints. + +Any modifications or adjustments to the drawing can be made by the user through the drawing tools dialog, it will end up in triggering an update in the drawing configuration within drawing_tools_dialog widget. + +To enable the drawings to be draggable, a distinct gesture is assigned to each drawing added to the chart. This gesture, embedded within the DrawingPainter, identifies any user taps on the drawing and designates the drawing as selected or deselected. The user can then drag the selected drawing across the chart. + +To update the position of the dragged drawing, the drawing must be repainted on the chart. This operation is performed by the CustomPaint component within the DrawingPainter. + +## Drawing Tool Chart + +DrawingToolChart widget is embedded within MainChart, enabling users to sketch particular shapes on DerivChart. This feature comprises two main parts: DrawingToolWidget and DrawingPainter. The DrawingPainter is specifically tasked with painting and repainting the drawings and is invoked for every drawing added to the chart. +In other words, for each drawing a DrawingPainter widget is added on the DrawingToolChart stack, and DrawingToolWidget is for when any drawing tool is selected. + +## DrawingToolWidget + +It assigns the task of drawing creation to the respective drawing tool creator. Each creator employs the chart's gestures to detect user inputs and initially adds the drawing to the list by invoking the onAddDrawing callback. Every drawing tool creator extends from the DrawingCreator class. + +## DrawingCreator + +It is a base class for all drawing tool creators. It is responsible for adding the drawing to the list and invoking the onAddDrawing callback. It also provides the drawing tool creator with the chart's gestures to detect user inputs. + +## DrawingPainter + +A stateful widget which is responsible for painting and repainting the drawings based on theirs configs using `CustomPaint`. Since the CustomPaint is wrapped within GestureDetector, each drawing created possesses its dedicated gesture, designed specifically for enabling the drawings to be draggable. + +In this widget we make drawings selectable by means of gesture and `hitTest` of `CustomPainter`. `hitTest` method checks if the user has clicked on a drawing or not, if yes it will return true. Inside `CustomPaint` we are calling `onPaint` and `hitTest` for each drawingPart. + +![plot](drawing_tools.png) + +## EdgePoints + +EdgePoint is a term we coined to keep the data of points required to create a drawing. For instance, a line necessitates two points, while a channel requires three. Whenever a user taps on Deriv-chart to create a drawing, a new instance of this class is generated within each drawing creator. This instance contains data obtained from Deriv_chart's gesture detector. These EdgePoints are then passed as a list to the onAddDrawing callback for drawing creation. + +## DraggableEdgePints + +This class extends from EdgePoint. It incorporates functions to compute the edgePoints' positions after they are dragged (utilizing data obtained from the DrawingPainter gestureDetector). These instances will be created as the state of the drawing_painter widget. Subsequently, they will be passed to CustomPaint, where onPaint and hitTest methods utilize them for drawing selection and repainting the drawings on the chart. diff --git a/docs/drawing_tools.png b/docs/drawing_tools.png new file mode 100644 index 000000000..d9037dd9b Binary files /dev/null and b/docs/drawing_tools.png differ diff --git a/docs/how_chart_lib_works.md b/docs/how_chart_lib_works.md index 70fc57090..cfd8e75a3 100644 --- a/docs/how_chart_lib_works.md +++ b/docs/how_chart_lib_works.md @@ -1,48 +1,53 @@ # Market data -The market data(input data of chart) is a list of *Ticks* or *OHLC*. + +The market data(input data of chart) is a list of _Ticks_ or _OHLC_. + - A **Tick** data element has two properties, epoch (time-stamp) and quote (price). - An **OHLC** (candle) data which shows the price changes in a period of time, each candle element has five properties, epoch (time-stamp) and open, close, high, low price values. -These four values respectively represent starting, ending, highest and lowest price in that timeframe. - + These four values respectively represent starting, ending, highest and lowest price in that timeframe. # Chart Scheme + Chart widget is a Canvas that we paint all data of chart inside this Canvas. ![plot](chart_scheme.png) -this widget has X-Axis and Y-Axis enabled by default. - +this widget has X-Axis and Y-Axis enabled by default. # X-Axis -X-Axis coordination system works with *rightBoundEpoch* and *msPerPx* variables. + +X-Axis coordination system works with _rightBoundEpoch_ and _msPerPx_ variables. + 1. **rightBoundEpoch**: The time-stamp of the chart screen right edge. We initially set it to point to `maxRightBoundEpoch`, The last Tick/OHLC epoch on closed markets, or the last element of the series (overlay/bottom) that has the most positive offset plus a constant offset in pixel (maxCurrentTickOffset). - 2. **msPerPx**: which specifies each pixel of the chart screen horizontally consists of how many milliseconds. 3. **leftBoundEpoch**: The time-stamp of the chart screen left edge. -By knowing **msPerPx**(chart's width in pixels) and **rightBoundEpoch**, We can then calculate the **leftBoundEpoch** like this: -**leftBoundEpoch = rightBoundEpoch - screenWidth * msPerPx** - Also, we can find out which data is inside this range and is going to be visible. + By knowing **msPerPx**(chart's width in pixels) and **rightBoundEpoch**, We can then calculate the **leftBoundEpoch** like this: + **leftBoundEpoch = rightBoundEpoch - screenWidth \* msPerPx** + Also, we can find out which data is inside this range and is going to be visible. # Y-Axis + For Y-Axis coordination we would need to have min and max quote values that are in the visible area of chart. -1. **topBoundEpoch**: The maximum quote(price) of the data between *rightBoundEpoch* and *leftBoundEpoch*. -2. **bottomBoundEpoch**: The minimum quote(price) of the data between *rightBoundEpoch* and *leftBoundEpoch*. +1. **topBoundEpoch**: The maximum quote(price) of the data between _rightBoundEpoch_ and _leftBoundEpoch_. +2. **bottomBoundEpoch**: The minimum quote(price) of the data between _rightBoundEpoch_ and _leftBoundEpoch_. - **now we can have the two conversion functions which can give us (x, y) positions inside the chart canvas for any epoch and quote values.** +**now we can have the two conversion functions which can give us (x, y) positions inside the chart canvas for any epoch and quote values.** # X-Axis labels -- **gridTimestamps** calculates the X-Axis labels. it Creates a list of [DateTime] between rightBoundEpoch and leftBoundEpoch with gaps of [timeGridInterval]. -- **timeGridInterval** is calculating by function with same name. it Returns the first time interval which has the enough distance between lines. +- **gridTimestamps** calculates the X-Axis labels. it Creates a list of [DateTime] between rightBoundEpoch and leftBoundEpoch with gaps of [timeGridInterval]. +- **timeGridInterval** is calculating by function with same name. it Returns the first time interval which has the enough distance between lines. # Y-Axis labels + **YAxisModel** is a Model for calculating the grid intervals and quotes(labels). by knowing the **topBoundQuote** and **bottomBoundQuote** we calculate the labels based on interval. this intervals calculates by **quoteGridInterval**. + - **quoteGridInterval** Calculates the grid interval of a quote by getting the [quotePerPx] value. - **quotePerPx** Calculates the quotes that can be placed per pixel by division of distance between topBound and bottomBound in quote and pixel. @@ -51,48 +56,47 @@ by knowing the **topBoundQuote** and **bottomBoundQuote** we calculate the label One of these intervals will be selected to be the distance between Y-Axis labels. the number from `intervals` list is selected as an interval that using it with the given [quotePerPx] will give us distance more than `minDistanceBetweenLines`. - # X-Axis scrolling + Scrolling in the chart happens by updating **rightBoundEpoch** of the chart's X-Axis. changing the **rightBoundEpoch** amount will change the chart’s scroll position. **rightBoundEpoch** be on the last tick when we first load the chart. - # Zooming + Zooming in the chart happens by updating **msPerPx**. **msPerPx** is for changing the zoom level of the chart, increasing it will result in zoom-out and decreasing to zoom-in. - - -# *Painting data* - +# _Painting data_ ## Data Visualisation ![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. +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. - -**DataSeries** is a Super class of any data series that has ***one*** list of sorted data to paint (by epoch). - **LineSeries**, **CandleSeries**, **OHLCSeries**,AbstractSingleIndicatorSeries(all indicator series that shows only one sequential data like **MASeries**(for moving average), **RSISeries**) are all subclasses of DataSeries directly or not. +**DataSeries** is a Super class of any data series that has **_one_** list of sorted data to paint (by epoch). +**LineSeries**, **CandleSeries**, **OHLCSeries**,AbstractSingleIndicatorSeries(all indicator series that shows only one sequential data like **MASeries**(for moving average), **RSISeries**) are all subclasses of DataSeries directly or not. **DataSeries** holds the common functionalities of managing this list of sorted data. **Series** is the Base class of all chart series paintings. -The series that have ***more than one*** list of sorted data to paint (like AlligatorIndicatorSeries) extend from **Series** and have some SingleIndicatorSeries inside. - +The series that have **_more than one_** list of sorted data to paint (like AlligatorIndicatorSeries) extend from **Series** and have some SingleIndicatorSeries inside. ### ChartObject + Any component which can take a rectangle area on the chart's canvas. It has `isOnEpochRange` and `isOnValueRange` method that shows whether this chart object is in chart horizontal or vertical visible area or not. ### BarrierObject + A **ChartObject** for defining position of a horizontal or vertical barrier. ### Chart annotations + Annotations are ChartData without any sequential data that added to the chart, like **Barriers**. **ChartAnnotation** is a Base class of chart annotations that extends from **Series**. ### Barriers + **Barrier** is a base class of barrier. Its properties are title, epoch and value. We have two kinds of barriers: **VerticalBarrier** and **HorizontalBarrier**. **VerticalBarrier**: is a vertical line in the chart that draws on a specific timestamp. It extends from the **Barrier** class. @@ -104,10 +108,9 @@ The reason that we have **TickIndicator** is to recognize the difference between To add horizontal/vertical barriers, specify them in the `annotations` parameter of the chart. They have the `createPainter` object to paint the **BarrierObject** that gets initiated in their `createObject` method. - ### Markers -**MarkerSeries** extends from **Series** to show the markers that are added to the chart. +**MarkerSeries** extends from **Series** to show the markers that are added to the chart. # Painter classes @@ -115,11 +118,11 @@ They have the `createPainter` object to paint the **BarrierObject** that gets in **SeriesPainter** is an abstract class responsible for painting its [series] data. -We have an abstract class named **DataPainter** that extends from **SeriesPainter** and it is a class for painting common options of [DataSeries] data. +We have an abstract class named **DataPainter** that extends from **SeriesPainter** and it is a class for painting common options of [DataSeries] data. Other painters like **LinePainter**( A [DataPainter] for painting line data), **CandlePainter**(A [DataPainter] for painting CandleStick data) and **ScatterPainter**, all extend from **DataPainter**. -**DataPainter** has a method called `onPaint` that calls `onPaintData`. It actually paints what's inside `onPaintData` that is overridden by each painter. for example **LinePainter** paints line in `onPaintData` method and **CandlePainter** paints Candles and `onPaintData` method. `onPaint` is a method where **DataPainters** need to do some common things before painting. +**DataPainter** has a method called `onPaint` that calls `onPaintData`. It actually paints what's inside `onPaintData` that is overridden by each painter. for example **LinePainter** paints line in `onPaintData` method and **CandlePainter** paints Candles and `onPaintData` method. `onPaint` is a method where **DataPainters** need to do some common things before painting. For painting Barriers, we have **VerticalBarrierPainter** and **HorizontalBarrierPainter** that also extend from **SeriesPainter**. They override the `onPaint` method to draw a Vertical/Horizontal Barrier. @@ -128,25 +131,25 @@ We have a `StatefulWidget` named **MarkerArea** to draw markers inside it. **MarkerArea** is a Layer with markers. For painting markers we have the **MarkerPainter** class extends from `CustomPainter`. -***The data in Visible area are between **rightBoundEpoch**, **leftBoundEpoch** and **topBoundEpoch**, **bottomBoundEpoch**, will be painted by these painters.*** - +**\*The data in Visible area are between **rightBoundEpoch**, **leftBoundEpoch** and **topBoundEpoch**, **bottomBoundEpoch**, will be painted by these painters.\*** # Painting chart data + when the list of data changes(by scrolling, zooming, or receiving new data) we need to update the chart. There are 3 steps that the chart requires to do when these variables change in order to update its components(including mainSeries, indicators, Barrier, markers, ... ). 1. The chart goes through its components and notifies them about the change. Each of these components then updates their visible data inside the new (leftEpoch, rightEpoch) range. - Then they can determine what are their min/max value (quote/price). + Then they can determine what are their min/max value (quote/price). 2. The chart then asks every component their min/max values through their `minValue` and `maxValue` getters to calculate the overall min/max of its Y-Axis range. - Any component that is not willing to be included in defining the Y-Axis range can return `double.NaN` values as its min/max. - Then if this component had any element outside of the chart's Y-Axis range that element will be invisible. + Any component that is not willing to be included in defining the Y-Axis range can return `double.NaN` values as its min/max. + Then if this component had any element outside of the chart's Y-Axis range that element will be invisible. 3. The conversion functions always return the converted x, y values based on the updated variables (Left/right bound epoch, min/max quote, top/bottom padding). - The chart will pass these conversion functions along with a reference to its canvas and some other variables to ChartData class to paint their visible data. - + The chart will pass these conversion functions along with a reference to its canvas and some other variables to ChartData class to paint their visible data. # Cross-hair + We have a `StatefulWidget` **CrosshairArea** that places this area on top of the chart to display candle/point details on longpress. It contains three other StatelessWidgets named **CrosshairDetails** (The details to show on a crosshair) and **CrosshairLinePainter** (A custom painter to paint the crosshair `line`) and **CrosshairDotPainter** (A custom painter to paint the crosshair `dot`). @@ -157,50 +160,52 @@ In `onLongPressUpdate` we call `updatePanSpeed`, then we calculate how much time In `onLongPressEnd`, `onCrosshairDisappeare` is called when the candle or the pointer is dismissed and auto-panning starts again and [crosshairTick] will clear. # Theme + Chart has its own default dark and light themes that switch depending on Theme.of(context).brightness value. If the user creates their own themes, they would have to handle switching it themselves. `chart_theme` is the interface, `chart_default_theme` is a default implementation of the `chart_theme` which is instantiated and used inside the `Chart` widget if no theme is passed from the app to the `Chart` widget. `painting_styles` are some style classes that are used to specify how certain components of the chart should be styled. e.g. `barrier_style` contains style parameters of barriers. - # BasicChart + **BasicChart** is a simple chart widget that takes only one `Series` class to paint. It handles common functionalities of handling Y-axis range, scaling and its animation, top/bottom padding. # MainChart + **MainChart** is a subclass of **BasicChart** and a customized widget that can show multiple `ChartData` (like `overlaySeries and `markerSeries`) and adds crosshair feature and some other minor features. # BottomChart + Sometimes we need to show two charts on the screen, for example for showing bottom indicators. In that case, we use **BottomChart** that extends from **BasicChart** to show the other chart widgets. ![plot](basic-chart.png) # Chart -**Chart** is a widget that manages showing **MainChart** and multiple **BottomChart**s (to have `bottomSeries`, series that have different Y-scale than the MainChart) vertically. + +**Chart** is a widget that manages showing **MainChart** and multiple **BottomChart**s (to have `bottomSeries`, series that have different Y-scale than the MainChart) vertically. **MainChart** and **BottomChart**s use the same **XAxis** (and it's provided in the root of the `Chart` widget to be accessible on the widgets at the bottom) but they have different YAxis. # DerivChart -**DerivChart** A wrapper around the **chart** widget which provides the UI to add/remove indicators and to manage saving/restoring selected ones on storage. -*if you want to have indicators in the chart, you should use ***DerivChart** instead of **Chart**** +**DerivChart** A wrapper around the **chart** widget which provides the UI to add/remove addons (indicators and drawing tools) and to manage saving/restoring selected ones on storage. -![plot](deriv-chart.png) +\*if you want to have indicators and drawing tools in the chart, you should use **\*DerivChart** instead of **Chart\*\*** +![plot](deriv-chart.png) # Widgets + ## Market Selector Widget + The widget that we have included it in the chart project to be accessable inside any other project which is going to use the chart, because this widget is supposed to show the asset (symbols) list to be shown by the chart. ## AnimatedPopupDialog + AnimatedPopupDialog is just a wrapper widget to wrap around any widget to show as a dialog. The dialog will pop up with animation. ## CustomDraggableSheet -CustomDraggableSheet is a wrapper widget to be used combined with a bottom sheet that makes to give the widget inside the bottom sheet the behavior that we want. - - - - - - - +CustomDraggableSheet is a wrapper widget to be used combined with a bottom sheet that makes to give the widget inside the bottom sheet the behavior that we want. +## Drawing Tool Chart +For brief explanation of how drawing tools work, please refer to [Drawing Tools](drawing_tools.md) section. diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 5d77f4100..2ee70cb26 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -44,4 +44,5 @@ android:name="flutterEmbedding" android:value="2" /> + diff --git a/example/lib/main.dart b/example/lib/main.dart index b5132f261..a905b8c21 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -310,7 +310,7 @@ class _FullscreenChartState extends State { _updateSampleSLAndTP(); - WidgetsBinding.instance.addPostFrameCallback( + WidgetsBinding.instance?.addPostFrameCallback( (Duration timeStamp) => _controller.scrollToLastTick(), ); } on BaseAPIException catch (e) { @@ -369,6 +369,21 @@ class _FullscreenChartState extends State { }); } + DataSeries _getDataSeries(ChartStyle style) { + if (ticks is List && style != ChartStyle.line) { + switch (style) { + case ChartStyle.hollow: + return HollowCandleSeries(ticks as List); + case ChartStyle.ohlc: + return OhlcCandleSeries(ticks as List); + default: + return CandleSeries(ticks as List); + } + } + return LineSeries(ticks, style: const LineStyle(hasArea: true)) + as DataSeries; + } + @override Widget build(BuildContext context) => Material( color: const Color(0xFF0E0E0E), @@ -379,7 +394,6 @@ class _FullscreenChartState extends State { child: Row( children: [ Expanded( - // ignore: unnecessary_null_comparison child: _markets == null ? const SizedBox.shrink() : _buildMarketSelectorButton(), @@ -394,18 +408,13 @@ class _FullscreenChartState extends State { children: [ ClipRect( child: DerivChart( - mainSeries: - style == ChartStyle.candles && ticks is List - ? CandleSeries(ticks as List) - : LineSeries( - ticks, - style: const LineStyle(hasArea: true), - ) as DataSeries, + mainSeries: _getDataSeries(style), markerSeries: MarkerSeries( _markers, activeMarker: _activeMarker, markerIconPainter: MultipliersMarkerIconPainter(), ), + activeSymbol: _symbol.name, annotations: ticks.length > 4 ? >[ ..._sampleBarriers, @@ -701,16 +710,31 @@ class _FullscreenChartState extends State { IconButton _buildChartTypeButton() => IconButton( icon: Icon( - style == ChartStyle.line ? Icons.show_chart : Icons.insert_chart, + style == ChartStyle.line + ? Icons.show_chart + : style == ChartStyle.candles + ? Icons.insert_chart + : style == ChartStyle.hollow + ? Icons.insert_chart_outlined_outlined + : Icons.add_chart, color: Colors.white, ), onPressed: () { Vibration.vibrate(duration: 50); setState(() { - if (style == ChartStyle.candles) { - style = ChartStyle.line; - } else { - style = ChartStyle.candles; + switch (style) { + case ChartStyle.ohlc: + style = ChartStyle.line; + return; + case ChartStyle.line: + style = ChartStyle.candles; + return; + case ChartStyle.candles: + style = ChartStyle.hollow; + return; + default: + style = ChartStyle.ohlc; + return; } }); }, diff --git a/lib/deriv_chart.dart b/lib/deriv_chart.dart index 9e7994cae..e378c6a35 100644 --- a/lib/deriv_chart.dart +++ b/lib/deriv_chart.dart @@ -1,6 +1,7 @@ library deriv_chart; export 'generated/l10n.dart'; +export 'src/deriv_chart/deriv_chart.dart'; export 'src/deriv_chart/chart/chart.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_indicator.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator.dart'; @@ -13,12 +14,37 @@ export 'src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal export 'src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/tick_indicator.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/vertical_barrier/vertical_barrier.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_data.dart'; export 'src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/awesome_oscillator_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/adx_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/alligator_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/aroon_series.dart'; export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/bollinger_bands_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/cci_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/donchian_channels_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/dpo_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fcb_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/gator_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ichimoku_cloud_series.dart'; export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_env_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/macd_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_rainbow_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/parabolic_sar/parabolic_sar_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/roc_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/rsi_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/smi_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/stochastic_oscillator_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/williams_r_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/indicators_series/zigzag_series.dart'; export 'src/deriv_chart/chart/data_visualization/chart_series/line_series/line_series.dart'; export 'src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_series.dart'; export 'src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; +export 'src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; export 'src/deriv_chart/chart/data_visualization/markers/active_marker.dart'; export 'src/deriv_chart/chart/data_visualization/markers/marker.dart'; export 'src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/accumulators_marker_icon_painter.dart'; @@ -26,16 +52,50 @@ export 'src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/ma export 'src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/multipliers_marker_icon_painter.dart'; export 'src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/options_marker_icon_painter.dart'; export 'src/deriv_chart/chart/data_visualization/markers/marker_series.dart'; +export 'src/deriv_chart/chart/data_visualization/models/animation_info.dart'; export 'src/deriv_chart/chart/data_visualization/models/chart_object.dart'; +export 'src/deriv_chart/chart/helpers/functions/helper_functions.dart'; +export 'src/deriv_chart/chart/helpers/paint_functions/paint_text.dart'; export 'src/deriv_chart/chart/helpers/paint_functions/paint_line.dart'; export 'src/deriv_chart/chart/worm_chart/worm_chart.dart'; export 'src/deriv_chart/chart/x_axis/min_candle_duration_for_data_fit.dart'; -export 'src/deriv_chart/deriv_chart.dart'; export 'src/misc/chart_controller.dart'; +export 'src/misc/callbacks.dart'; export 'src/models/candle.dart'; export 'src/models/chart_axis_config.dart'; export 'src/models/chart_style.dart'; +export 'src/models/indicator_input.dart'; export 'src/models/tick.dart'; +export 'src/add_ons/add_on_config.dart'; +export 'src/add_ons/add_ons_repository.dart'; +export 'src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +export 'src/deriv_chart/drawing_tool_chart/drawing_tool_chart.dart'; +export 'src/deriv_chart/chart/data_visualization/drawing_tools/drawing_tool_widget.dart'; +export 'src/add_ons/indicators_ui/adx/adx_indicator_config.dart'; +export 'src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart'; +export 'src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart'; +export 'src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart'; +export 'src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart'; +export 'src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart'; +export 'src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart'; +export 'src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart'; +export 'src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart'; +export 'src/add_ons/indicators_ui/gator/gator_indicator_config.dart'; +export 'src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart'; +export 'src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart'; +export 'src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart'; +export 'src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart'; +export 'src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.dart'; +export 'src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart'; +export 'src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart'; +export 'src/add_ons/indicators_ui/roc/roc_indicator_config.dart'; +export 'src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart'; +export 'src/add_ons/indicators_ui/smi/smi_indicator_config.dart'; +export 'src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart'; +export 'src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart'; +export 'src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart'; +export 'src/add_ons/indicators_ui/indicator_config.dart'; +export 'src/add_ons/repository.dart'; export 'src/theme/chart_default_dark_theme.dart'; export 'src/theme/chart_default_light_theme.dart'; export 'src/theme/chart_theme.dart'; @@ -49,3 +109,15 @@ export 'src/widgets/market_selector/market_selector.dart'; export 'src/widgets/market_selector/market_selector_button.dart'; export 'src/widgets/market_selector/models.dart'; export 'src/widgets/market_selector/symbol_icon.dart'; +export 'src/deriv_chart/drawing_tool_chart/drawing_tools.dart'; +export 'src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/distance_constants.dart'; +export 'src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart'; +export 'src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart'; +export 'src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 83eae7405..7b43c5db8 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -30,6 +30,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Base Line Period"), "labelChannelFill": MessageLookupByLibrary.simpleMessage("Channel Fill"), + "labelColor": MessageLookupByLibrary.simpleMessage("Color"), "labelConversionLinePeriod": MessageLookupByLibrary.simpleMessage("Conversion Line Period"), "labelDistance": MessageLookupByLibrary.simpleMessage("Distance"), @@ -38,6 +39,7 @@ class MessageLookup extends MessageLookupByLibrary { "labelFastMAPeriod": MessageLookupByLibrary.simpleMessage("Fast MA Period"), "labelField": MessageLookupByLibrary.simpleMessage("Field"), + "labelFillColor": MessageLookupByLibrary.simpleMessage("Fill Color"), "labelHighPeriod": MessageLookupByLibrary.simpleMessage("High Period"), "labelHistogram": MessageLookupByLibrary.simpleMessage("Histogram"), "labelIsSmooth": MessageLookupByLibrary.simpleMessage("Is Smooth"), @@ -81,8 +83,12 @@ class MessageLookup extends MessageLookupByLibrary { "labelTeethPeriod": MessageLookupByLibrary.simpleMessage("Teeth Period"), "labelType": MessageLookupByLibrary.simpleMessage("Type"), + "selectDrawingTool": + MessageLookupByLibrary.simpleMessage("Select drawing tool"), "warnCheckAssetSearchingText": MessageLookupByLibrary.simpleMessage( "Try checking your spelling or use a different term"), + "warnFailedLoadingDrawingTools": MessageLookupByLibrary.simpleMessage( + "Failed to load drawing tools."), "warnFailedLoadingIndicators": MessageLookupByLibrary.simpleMessage("Failed to load indicators.") }; diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 2df604f32..921540b0e 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -469,6 +469,46 @@ class ChartLocalization { args: [], ); } + + /// `Failed to load drawing tools.` + String get warnFailedLoadingDrawingTools { + return Intl.message( + 'Failed to load drawing tools.', + name: 'warnFailedLoadingDrawingTools', + desc: '', + args: [], + ); + } + + /// `Select drawing tool` + String get selectDrawingTool { + return Intl.message( + 'Select drawing tool', + name: 'selectDrawingTool', + desc: '', + args: [], + ); + } + + /// `Color` + String get labelColor { + return Intl.message( + 'Color', + name: 'labelColor', + desc: '', + args: [], + ); + } + + /// `Fill Color` + String get labelFillColor { + return Intl.message( + 'Fill Color', + name: 'labelFillColor', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1ad4abccc..d7b286d2f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -41,5 +41,9 @@ "labelShowLines": "Show Lines", "labelShowFractals": "Show Fractals", "labelIsSmooth": "Is Smooth", - "warnFailedLoadingIndicators": "Failed to load indicators." -} \ No newline at end of file + "warnFailedLoadingIndicators": "Failed to load indicators.", + "warnFailedLoadingDrawingTools": "Failed to load drawing tools.", + "selectDrawingTool": "Select drawing tool", + "labelColor": "Color", + "labelFillColor": "Fill Color" +} diff --git a/lib/src/add_ons/add_on_config.dart b/lib/src/add_ons/add_on_config.dart new file mode 100644 index 000000000..836dcb7b1 --- /dev/null +++ b/lib/src/add_ons/add_on_config.dart @@ -0,0 +1,21 @@ +import 'package:equatable/equatable.dart'; + +/// Config for add-ons such as indicators and drawing tools +abstract class AddOnConfig with EquatableMixin { + /// Initializes [AddOnConfig]. + const AddOnConfig({ + this.isOverlay = true, + }); + + /// Whether the add-on is an overlay on the main chart or displays on a + /// separate chart. Default is set to `true`. + final bool isOverlay; + + /// Serialization to JSON. Serves as value in key-value storage. + /// + /// Must specify add-on `name` with `nameKey`. + Map toJson(); + + @override + List get props => [toJson()]; +} diff --git a/lib/src/add_ons/add_ons_repository.dart b/lib/src/add_ons/add_ons_repository.dart new file mode 100644 index 000000000..5e9d75109 --- /dev/null +++ b/lib/src/add_ons/add_ons_repository.dart @@ -0,0 +1,132 @@ +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_chart/src/add_ons/add_on_config.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Called to create an AddOnConfig object from a map. +typedef CreateAddOn = T Function( + Map map); + +/// Holds indicators/drawing tools that were added to the Chart during runtime. +class AddOnsRepository extends ChangeNotifier + implements Repository { + /// Initializes + AddOnsRepository({ + required this.createAddOn, + required this.currentSymbol, + this.onEditCallback, + }) : _addOns = []; + + /// Current symbol. + String currentSymbol; + + /// List containing addOns + final List _addOns; + SharedPreferences? _prefs; + + /// List of indicators. + @override + List get items => _addOns; + + /// Storage key of saved indicators/drawing tools. + String get addOnsKey => 'addOns_${T.toString()}_$currentSymbol'; + + /// Called to create an AddOnConfig object from a map. + CreateAddOn createAddOn; + + /// Called when the edit icon is clicked. + VoidCallback? onEditCallback; + + /// Loads user selected indicators or drawing tools from shared preferences. + void loadFromPrefs(SharedPreferences prefs, String symbol) { + _prefs = prefs; + currentSymbol = symbol; + + items.clear(); + + if (!prefs.containsKey(addOnsKey)) { + // No saved indicators or drawing tools. + return; + } + + final List encodedAddOns = prefs.getStringList(addOnsKey)!; + + final List> decodedAddons = encodedAddOns + .map>( + (String encodedAddOn) => jsonDecode(encodedAddOn)) + .toList(); + + for (final Map decodedAddon in decodedAddons) { + final T addOnConfig = createAddOn.call(decodedAddon); + items.add(addOnConfig); + } + } + + /// Adds a new indicator or drawing tool and updates storage. + @override + void add(T addOnConfig) { + items.add(addOnConfig); + _writeToPrefs(); + notifyListeners(); + } + + /// Called when the edit icon is clicked. + @override + void editAt(int index) { + onEditCallback?.call(); + } + + /// Updates indicator or drawing tool at [index] and updates storage. + @override + void updateAt(int index, T addOnConfig) { + if (index < 0 || index >= items.length) { + return; + } + items[index] = addOnConfig; + _writeToPrefs(); + notifyListeners(); + } + + + + /// Removes indicator/drawing tool at [index] from repository and + /// updates storage. + @override + void removeAt(int index) { + if (index < 0 || index >= items.length) { + return; + } + items.removeAt(index); + _writeToPrefs(); + notifyListeners(); + } + + + /// Removes all indicator/drawing tool from repository and + /// updates storage. + @override + void clear() { + items.clear(); + _writeToPrefs(); + notifyListeners(); + } + + /// Swaps two elements of a list and updates storage. + @override + void swap(int index1, int index2) { + items.swap(index1, index2); + _writeToPrefs(); + notifyListeners(); + } + + Future _writeToPrefs() async { + if (_prefs != null) { + await _prefs!.setStringList( + addOnsKey, + items.map((T config) => jsonEncode(config.toJson())).toList(), + ); + } + } +} diff --git a/lib/src/add_ons/drawing_tools_ui/callbacks.dart b/lib/src/add_ons/drawing_tools_ui/callbacks.dart new file mode 100644 index 000000000..80cc48834 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/callbacks.dart @@ -0,0 +1,4 @@ +import 'drawing_tool_config.dart'; + +/// Callback to update drawing tool with new [drawingToolConfig]. +typedef UpdateDrawingTool = Function(DrawingToolConfig drawingToolConfig); diff --git a/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart new file mode 100644 index 000000000..9783ecac1 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart @@ -0,0 +1,81 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/callbacks.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'channel_drawing_tool_item.dart'; + +part 'channel_drawing_tool_config.g.dart'; + +/// Channel drawing tool config +@JsonSerializable() +class ChannelDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const ChannelDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.fillStyle = const LineStyle(thickness: 0.9, color: Colors.blue), + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory ChannelDrawingToolConfig.fromJson(Map json) => + _$ChannelDrawingToolConfigFromJson(json); + + /// Drawing tool name + static const String name = 'dt_channel'; + + @override + Map toJson() => _$ChannelDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool fill style + final LineStyle fillStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + // TODO(maryia-binary): implement 'dotted' and 'dashed' patterns + final DrawingPatterns pattern; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + ChannelDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + ChannelDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + ChannelDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + fillStyle: fillStyle ?? this.fillStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.g.dart new file mode 100644 index 000000000..8345da9fe --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'channel_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ChannelDrawingToolConfig _$ChannelDrawingToolConfigFromJson( + Map json) => + ChannelDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + fillStyle: json['fillStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.blue) + : LineStyle.fromJson(json['fillStyle'] as Map), + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + ); + +Map _$ChannelDrawingToolConfigToJson( + ChannelDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'fillStyle': instance.fillStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_item.dart new file mode 100644 index 000000000..3a4a4c0e3 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_item.dart @@ -0,0 +1,89 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'channel_drawing_tool_config.dart'; +import '../callbacks.dart'; + +/// Channel drawing tool item in the list of drawing tools +class ChannelDrawingToolItem extends DrawingToolItem { + /// Initializes + const ChannelDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + ChannelDrawingToolConfig config = const ChannelDrawingToolConfig(), + }) : super( + key: key, + title: 'Channel', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + ChannelDrawingToolItemState(); +} + +/// ChannelDrawingToolItem State class +class ChannelDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _fillStyle; + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + + @override + ChannelDrawingToolConfig createDrawingToolConfig() => + ChannelDrawingToolConfig( + fillStyle: _currentFillStyle, + lineStyle: _currentLineStyle, + pattern: _currentPattern, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField( + ChartLocalization.of(context).labelColor, _currentLineStyle), + _buildColorField( + ChartLocalization.of(context).labelFillColor, _currentFillStyle), + // TODO(maryia-binary): implement _buildPatternField() to set pattern + ], + ); + + Widget _buildColorField(String label, LineStyle style) => Row( + children: [ + Text( + label, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: style.color, + onColorChanged: (Color selectedColor) { + setState(() { + final LineStyle newColor = style.copyWith(color: selectedColor); + if (label == ChartLocalization.of(context).labelColor) { + _lineStyle = newColor; + } else { + _fillStyle = newColor; + } + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentFillStyle => + _fillStyle ?? (widget.config as ChannelDrawingToolConfig).fillStyle; + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as ChannelDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as ChannelDrawingToolConfig).pattern; +} diff --git a/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart new file mode 100644 index 000000000..4eb908a7a --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart @@ -0,0 +1,76 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/callbacks.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'continuous_drawing_tool_item.dart'; + +part 'continuous_drawing_tool_config.g.dart'; + +/// Continuous drawing tool config +@JsonSerializable() +class ContinuousDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const ContinuousDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory ContinuousDrawingToolConfig.fromJson(Map json) => + _$ContinuousDrawingToolConfigFromJson(json); + + /// Drawing tool name + static const String name = 'dt_continuous'; + + @override + Map toJson() => _$ContinuousDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + // TODO(maryia-binary): implement 'dotted' and 'dashed' patterns + final DrawingPatterns pattern; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + ContinuousDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + ContinuousDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + ContinuousDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.g.dart new file mode 100644 index 000000000..20d0dc6ad --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'continuous_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ContinuousDrawingToolConfig _$ContinuousDrawingToolConfigFromJson( + Map json) => + ContinuousDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + ); + +Map _$ContinuousDrawingToolConfigToJson( + ContinuousDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_item.dart new file mode 100644 index 000000000..fe2055221 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_item.dart @@ -0,0 +1,76 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'continuous_drawing_tool_config.dart'; +import '../callbacks.dart'; + +/// Continuous drawing tool item in the list of drawing tools +class ContinuousDrawingToolItem extends DrawingToolItem { + /// Initializes + const ContinuousDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + ContinuousDrawingToolConfig config = const ContinuousDrawingToolConfig(), + }) : super( + key: key, + title: 'Continuous', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + ContinuousDrawingToolItemState(); +} + +/// ContinuousDrawingToolItem State class +class ContinuousDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + + @override + ContinuousDrawingToolConfig createDrawingToolConfig() => + ContinuousDrawingToolConfig( + lineStyle: _currentLineStyle, + pattern: _currentPattern, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField(), + // TODO(maryia-binary): implement _buildPatternField() to set pattern + ], + ); + + Widget _buildColorField() => Row( + children: [ + Text( + ChartLocalization.of(context).labelColor, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: _currentLineStyle.color, + onColorChanged: (Color selectedColor) { + setState(() { + _lineStyle = _currentLineStyle.copyWith(color: selectedColor); + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as ContinuousDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as ContinuousDrawingToolConfig).pattern; +} diff --git a/lib/src/add_ons/drawing_tools_ui/distance_constants.dart b/lib/src/add_ons/drawing_tools_ui/distance_constants.dart new file mode 100644 index 000000000..92e53b5a0 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/distance_constants.dart @@ -0,0 +1,7 @@ +/// This involves calculating the distance from the marker to its +/// edges for the purpose of creating lines ( horizontal , line drawing tool) + +class DrawingToolDistance { + /// horizontal distance + static const double horizontalDistance = 99999; +} diff --git a/lib/src/add_ons/drawing_tools_ui/drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/drawing_tool_config.dart new file mode 100644 index 000000000..851d5596c --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/drawing_tool_config.dart @@ -0,0 +1,96 @@ +import 'package:deriv_chart/src/add_ons/add_on_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/callbacks.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart'; + +import 'package:flutter/material.dart'; +import 'line/line_drawing_tool_config.dart'; +import 'vertical/vertical_drawing_tool_config.dart'; + +/// Drawing tools config +@immutable +abstract class DrawingToolConfig extends AddOnConfig { + /// Initializes + const DrawingToolConfig({ + required this.configId, + required this.drawingData, + // TODO(Bahar-Deriv): Move edgePoints to drawingData. + required this.edgePoints, + bool isOverlay = true, + }) : super(isOverlay: isOverlay); + + /// Creates a concrete drawing tool config from JSON. + factory DrawingToolConfig.fromJson(Map json) { + if (!json.containsKey(nameKey)) { + throw ArgumentError.value(json, 'json', 'Missing drawing tool name.'); + } + + switch (json[nameKey]) { + case ChannelDrawingToolConfig.name: + return ChannelDrawingToolConfig.fromJson(json); + case ContinuousDrawingToolConfig.name: + return ContinuousDrawingToolConfig.fromJson(json); + case FibfanDrawingToolConfig.name: + return FibfanDrawingToolConfig.fromJson(json); + case HorizontalDrawingToolConfig.name: + return HorizontalDrawingToolConfig.fromJson(json); + case LineDrawingToolConfig.name: + return LineDrawingToolConfig.fromJson(json); + case RayDrawingToolConfig.name: + return RayDrawingToolConfig.fromJson(json); + case RectangleDrawingToolConfig.name: + return RectangleDrawingToolConfig.fromJson(json); + case TrendDrawingToolConfig.name: + return TrendDrawingToolConfig.fromJson(json); + case VerticalDrawingToolConfig.name: + return VerticalDrawingToolConfig.fromJson(json); + + // Add new drawing tools here. + default: + throw ArgumentError.value( + json, 'json', 'Unidentified drawing tool name.'); + } + } + + /// Drawing tool data. + final DrawingData? drawingData; + + /// Drawing tool edge points. + final List edgePoints; + + /// Drawing tool config id. + final String? configId; + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'name'; + + /// Key of drawing tool config id property in JSON. + static String configIdKey = 'configId'; + + /// Creates a copy of this object. + DrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }); + + /// Creates drawing tool. + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/drawing_tool_item.dart new file mode 100644 index 000000000..dd0896391 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/drawing_tool_item.dart @@ -0,0 +1,77 @@ +import 'package:deriv_chart/src/add_ons/repository.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'callbacks.dart'; +import 'drawing_tool_config.dart'; + +/// Represents a drawing tool item on the list of drawing tools in dialog. +abstract class DrawingToolItem extends StatefulWidget { + /// Initializes + const DrawingToolItem({ + required this.title, + required this.config, + required this.updateDrawingTool, + required this.deleteDrawingTool, + Key? key, + }) : super(key: key); + + /// Title + final String title; + + /// Contains drawing tool configuration. + final DrawingToolConfig config; + + /// Called when config values were updated. + final UpdateDrawingTool updateDrawingTool; + + /// Called when user removed drawing tool. + final VoidCallback deleteDrawingTool; + + @override + DrawingToolItemState createState() => + createDrawingToolItemState(); + + /// Create state object for this widget + @protected + DrawingToolItemState createDrawingToolItemState(); +} + +/// State class of [DrawingToolItem] +abstract class DrawingToolItemState + extends State { + /// Drawing tools repository + @protected + late Repository drawingToolsRepo; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + drawingToolsRepo = Provider.of>(context); + } + + @override + Widget build(BuildContext context) => ListTile( + contentPadding: const EdgeInsets.all(0), + leading: Text(widget.title, style: const TextStyle(fontSize: 16)), + title: getDrawingToolOptions(), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: removeDrawingTool, + ), + ); + + /// Updates drawing tool based on its current config values. + void updateDrawingTool() => + widget.updateDrawingTool.call(createDrawingToolConfig()); + + /// Removes this drawing tool. + void removeDrawingTool() => widget.deleteDrawingTool.call(); + + /// Returns the [DrawingToolConfig] which can be used to create the Series for + /// this drawing tool. + T createDrawingToolConfig(); + + /// Creates the menu options widget for this drawing tool. + Widget getDrawingToolOptions(); +} diff --git a/lib/src/add_ons/drawing_tools_ui/drawing_tools_dialog.dart b/lib/src/add_ons/drawing_tools_ui/drawing_tools_dialog.dart new file mode 100644 index 000000000..5e76ef902 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/drawing_tools_dialog.dart @@ -0,0 +1,135 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/drawing_tool_chart/drawing_tools.dart'; +import 'package:deriv_chart/src/widgets/animated_popup.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; + +/// Drawing tools dialog with available drawing tools. +class DrawingToolsDialog extends StatefulWidget { + /// Creates drawing tools dialog. + const DrawingToolsDialog({ + required this.drawingTools, + Key? key, + }) : super(key: key); + + /// Keep the reference to the drawing tools class for + /// sharing data between the DerivChart and the DrawingToolsDialog + final DrawingTools drawingTools; + + @override + _DrawingToolsDialogState createState() => _DrawingToolsDialogState(); +} + +class _DrawingToolsDialogState extends State { + DrawingToolConfig? _selectedDrawingTool; + + @override + Widget build(BuildContext context) { + final Repository repo = + context.watch>(); + + return AnimatedPopupDialog( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButton( + value: _selectedDrawingTool, + hint: Text(ChartLocalization.of(context).selectDrawingTool), + items: const >[ + DropdownMenuItem( + child: Text('Channel'), + value: ChannelDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Continuous'), + value: ContinuousDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Fib Fan'), + value: FibfanDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Horizontal'), + value: HorizontalDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Line'), + value: LineDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Ray'), + value: RayDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Rectangle'), + value: RectangleDrawingToolConfig()), + DropdownMenuItem( + child: Text('Trend'), + value: TrendDrawingToolConfig(), + ), + DropdownMenuItem( + child: Text('Vertical'), + value: VerticalDrawingToolConfig(), + ) + // TODO(maryia-binary): add the rest of drawing tools above + ], + onChanged: (DrawingToolConfig? config) { + setState(() { + _selectedDrawingTool = config; + }); + }, + ), + const SizedBox(width: 16), + ElevatedButton( + child: const Text('Add'), + onPressed: _selectedDrawingTool != null + ? () { + widget.drawingTools + .onDrawingToolSelection(_selectedDrawingTool!); + Navigator.of(context).pop(); + } + : null, + ), + ], + ), + Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: repo.items.length, + itemBuilder: (BuildContext context, int index) => + repo.items[index].getItem( + (DrawingToolConfig updatedConfig) { + DrawingToolConfig config = updatedConfig; + + config = config.copyWith( + configId: repo.items[index].configId, + edgePoints: repo.items[index].edgePoints, + drawingData: repo.items[index].drawingData, + ); + + repo.updateAt(index, config); + }, + () { + repo.removeAt(index); + }, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart new file mode 100644 index 000000000..8dcd41589 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart @@ -0,0 +1,75 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/callbacks.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'fibfan_drawing_tool_item.dart'; + +part 'fibfan_drawing_tool_config.g.dart'; + +/// Fibfan drawing tool config +@JsonSerializable() +class FibfanDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const FibfanDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.fillStyle = const LineStyle(thickness: 0.9, color: Colors.blue), + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory FibfanDrawingToolConfig.fromJson(Map json) => + _$FibfanDrawingToolConfigFromJson(json); + + /// Drawing tool name + static const String name = 'dt_fibfan'; + + @override + Map toJson() => _$FibfanDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool fill style + final LineStyle fillStyle; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + FibfanDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + FibfanDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + FibfanDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + fillStyle: fillStyle ?? this.fillStyle, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.g.dart new file mode 100644 index 000000000..24fefc37e --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fibfan_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FibfanDrawingToolConfig _$FibfanDrawingToolConfigFromJson( + Map json) => + FibfanDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + fillStyle: json['fillStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.blue) + : LineStyle.fromJson(json['fillStyle'] as Map), + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + ); + +Map _$FibfanDrawingToolConfigToJson( + FibfanDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'fillStyle': instance.fillStyle, + }; diff --git a/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_item.dart new file mode 100644 index 000000000..a7d26f070 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_item.dart @@ -0,0 +1,81 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import '../callbacks.dart'; + +/// Fibfan drawing tool item in the list of drawing tools +class FibfanDrawingToolItem extends DrawingToolItem { + /// Initializes + const FibfanDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + FibfanDrawingToolConfig config = const FibfanDrawingToolConfig(), + }) : super( + key: key, + title: 'Fib fan', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + FibfanDrawingToolItemState(); +} + +/// FibfanDrawingToolItem State class +class FibfanDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _fillStyle; + LineStyle? _lineStyle; + + @override + FibfanDrawingToolConfig createDrawingToolConfig() => FibfanDrawingToolConfig( + fillStyle: _currentFillStyle, + lineStyle: _currentLineStyle, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField( + ChartLocalization.of(context).labelColor, _currentLineStyle), + _buildColorField( + ChartLocalization.of(context).labelFillColor, _currentFillStyle), + ], + ); + + Widget _buildColorField(String label, LineStyle style) => Row( + children: [ + Text( + label, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: style.color, + onColorChanged: (Color selectedColor) { + setState(() { + final LineStyle newColor = style.copyWith(color: selectedColor); + if (label == ChartLocalization.of(context).labelColor) { + _lineStyle = newColor; + } else { + _fillStyle = newColor; + } + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentFillStyle => + _fillStyle ?? (widget.config as FibfanDrawingToolConfig).fillStyle; + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as FibfanDrawingToolConfig).lineStyle; +} diff --git a/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart new file mode 100644 index 000000000..3783d1b35 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart @@ -0,0 +1,81 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../callbacks.dart'; + +part 'horizontal_drawing_tool_config.g.dart'; + +/// Horizontal drawing tool configurations. +@JsonSerializable() +class HorizontalDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const HorizontalDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + this.enableLabel = true, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory HorizontalDrawingToolConfig.fromJson(Map json) => + _$HorizontalDrawingToolConfigFromJson(json); + + /// Unique name for this drawing tool. + static const String name = 'dt_horizontal'; + + @override + Map toJson() => _$HorizontalDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + final DrawingPatterns pattern; + + /// For enabling the label + final bool enableLabel; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + HorizontalDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + HorizontalDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + HorizontalDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + enableLabel: enableLabel ?? this.enableLabel, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.g.dart new file mode 100644 index 000000000..61823260f --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'horizontal_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +HorizontalDrawingToolConfig _$HorizontalDrawingToolConfigFromJson( + Map json) => + HorizontalDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + enableLabel: json['enableLabel'] as bool? ?? true, + ); + +Map _$HorizontalDrawingToolConfigToJson( + HorizontalDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + 'enableLabel': instance.enableLabel, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_item.dart new file mode 100644 index 000000000..816d67713 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_item.dart @@ -0,0 +1,103 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; + +import 'package:flutter/material.dart'; + +import '../callbacks.dart'; +import '../drawing_tool_item.dart'; + +/// Horizontal drawing tool item in the list of drawing tool which provide this +/// drawing tools options menu. +class HorizontalDrawingToolItem extends DrawingToolItem { + /// Initializes + const HorizontalDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + HorizontalDrawingToolConfig config = const HorizontalDrawingToolConfig(), + }) : super( + key: key, + title: 'Horizontal', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + HorizontalDrawingToolItemState(); +} + +/// Vertival drawing tool Item State class +class HorizontalDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + bool? _enableLabel; + + @override + HorizontalDrawingToolConfig createDrawingToolConfig() => + HorizontalDrawingToolConfig( + lineStyle: _currentLineStyle, + pattern: _currentPattern, + enableLabel: _getEnableLanel, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField(), + _buildEnableLabel(), + ], + ); + + Widget _buildEnableLabel() => Row( + children: [ + const Text( + 'Enable Label', + style: TextStyle(fontSize: 10), + ), + Switch( + value: _getEnableLanel, + onChanged: (bool value) { + setState(() { + _enableLabel = value; + }); + updateDrawingTool(); + }, + ), + ], + ); + + Widget _buildColorField() => Row( + children: [ + Text( + ChartLocalization.of(context).labelColor, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: _currentLineStyle.color, + onColorChanged: (Color selectedColor) { + setState(() { + _lineStyle = _currentLineStyle.copyWith(color: selectedColor); + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as HorizontalDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as HorizontalDrawingToolConfig).pattern; + + bool get _getEnableLanel => + _enableLabel ?? + (widget.config as HorizontalDrawingToolConfig).enableLabel; +} diff --git a/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart new file mode 100644 index 000000000..baa84cea2 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart @@ -0,0 +1,76 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/callbacks.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'line_drawing_tool_item.dart'; + +part 'line_drawing_tool_config.g.dart'; + +/// Line drawing tool config +@JsonSerializable() +class LineDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const LineDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory LineDrawingToolConfig.fromJson(Map json) => + _$LineDrawingToolConfigFromJson(json); + + /// Drawing tool name + static const String name = 'dt_line'; + + @override + Map toJson() => _$LineDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + // TODO(maryia-binary): implement 'dotted' and 'dashed' patterns + final DrawingPatterns pattern; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + LineDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + LineDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + LineDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.g.dart new file mode 100644 index 000000000..b6d91581d --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'line_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LineDrawingToolConfig _$LineDrawingToolConfigFromJson( + Map json) => + LineDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + ); + +Map _$LineDrawingToolConfigToJson( + LineDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_item.dart new file mode 100644 index 000000000..d4524ba11 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/line/line_drawing_tool_item.dart @@ -0,0 +1,75 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'line_drawing_tool_config.dart'; +import '../callbacks.dart'; + +/// Line drawing tool item in the list of drawing tools +class LineDrawingToolItem extends DrawingToolItem { + /// Initializes + const LineDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + LineDrawingToolConfig config = const LineDrawingToolConfig(), + }) : super( + key: key, + title: 'Line', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + LineDrawingToolItemState(); +} + +/// LineDrawingToolItem State class +class LineDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + + @override + LineDrawingToolConfig createDrawingToolConfig() => LineDrawingToolConfig( + lineStyle: _currentLineStyle, + pattern: _currentPattern, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField(), + // TODO(maryia-binary): implement _buildPatternField() to set pattern + ], + ); + + Widget _buildColorField() => Row( + children: [ + Text( + ChartLocalization.of(context).labelColor, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: _currentLineStyle.color, + onColorChanged: (Color selectedColor) { + setState(() { + _lineStyle = _currentLineStyle.copyWith(color: selectedColor); + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as LineDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as LineDrawingToolConfig).pattern; +} diff --git a/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart new file mode 100644 index 000000000..e26ab8c50 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart @@ -0,0 +1,76 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/callbacks.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'ray_drawing_tool_item.dart'; + +part 'ray_drawing_tool_config.g.dart'; + +/// Ray drawing tool config +@JsonSerializable() +class RayDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const RayDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory RayDrawingToolConfig.fromJson(Map json) => + _$RayDrawingToolConfigFromJson(json); + + /// Drawing tool name + static const String name = 'dt_ray'; + + @override + Map toJson() => _$RayDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + // TODO(maryia-binary): implement 'dotted' and 'dashed' patterns + final DrawingPatterns pattern; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + RayDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + RayDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + RayDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.g.dart new file mode 100644 index 000000000..796ec554d --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ray_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RayDrawingToolConfig _$RayDrawingToolConfigFromJson( + Map json) => + RayDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + ); + +Map _$RayDrawingToolConfigToJson( + RayDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_item.dart new file mode 100644 index 000000000..e3e98a6db --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_item.dart @@ -0,0 +1,75 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'ray_drawing_tool_config.dart'; +import '../callbacks.dart'; + +/// Ray drawing tool item in the list of drawing tools +class RayDrawingToolItem extends DrawingToolItem { + /// Initializes + const RayDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + RayDrawingToolConfig config = const RayDrawingToolConfig(), + }) : super( + key: key, + title: 'Ray', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + RayDrawingToolItemState(); +} + +/// RayDrawingToolItem State class +class RayDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + + @override + RayDrawingToolConfig createDrawingToolConfig() => RayDrawingToolConfig( + lineStyle: _currentLineStyle, + pattern: _currentPattern, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField(), + // TODO(maryia-binary): implement _buildPatternField() to set pattern + ], + ); + + Widget _buildColorField() => Row( + children: [ + Text( + ChartLocalization.of(context).labelColor, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: _currentLineStyle.color, + onColorChanged: (Color selectedColor) { + setState(() { + _lineStyle = _currentLineStyle.copyWith(color: selectedColor); + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as RayDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as RayDrawingToolConfig).pattern; +} diff --git a/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart new file mode 100644 index 000000000..91c914cc5 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart @@ -0,0 +1,81 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../callbacks.dart'; + +part 'rectangle_drawing_tool_config.g.dart'; + +/// Rectangle drawing tool configurations. +@JsonSerializable() +class RectangleDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const RectangleDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.fillStyle = const LineStyle(thickness: 0.9, color: Colors.blue), + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory RectangleDrawingToolConfig.fromJson(Map json) => + _$RectangleDrawingToolConfigFromJson(json); + + /// Unique name for this drawing tool. + static const String name = 'dt_rectangle'; + + @override + Map toJson() => _$RectangleDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool fill style + final LineStyle fillStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + final DrawingPatterns pattern; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + RectangleDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + RectangleDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + RectangleDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + lineStyle: lineStyle ?? this.lineStyle, + fillStyle: fillStyle ?? this.fillStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.g.dart new file mode 100644 index 000000000..e2b9397e6 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'rectangle_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RectangleDrawingToolConfig _$RectangleDrawingToolConfigFromJson( + Map json) => + RectangleDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + fillStyle: json['fillStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.blue) + : LineStyle.fromJson(json['fillStyle'] as Map), + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + ); + +Map _$RectangleDrawingToolConfigToJson( + RectangleDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'fillStyle': instance.fillStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_item.dart new file mode 100644 index 000000000..49afedccb --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_item.dart @@ -0,0 +1,94 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; + +import 'package:flutter/material.dart'; + +import '../callbacks.dart'; +import '../drawing_tool_item.dart'; + +/// Rectangle drawing tool item in the list of drawing tool which provide this +/// drawing tools options menu. +class RectangleDrawingToolItem extends DrawingToolItem { + /// Initializes + const RectangleDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + RectangleDrawingToolConfig config = const RectangleDrawingToolConfig(), + }) : super( + key: key, + title: 'Rectangle', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + RectangleDrawingToolItemState(); +} + +/// Rectangle drawing tool Item State class +class RectangleDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _fillStyle; + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + + @override + RectangleDrawingToolConfig createDrawingToolConfig() => + RectangleDrawingToolConfig( + fillStyle: _currentFillStyle, + lineStyle: _currentLineStyle, + pattern: _currentPattern, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField( + ChartLocalization.of(context).labelColor, _currentLineStyle), + _buildColorField( + ChartLocalization.of(context).labelFillColor, _currentFillStyle), + // TODO(maryia-deriv): implement _buildPatternField() to set pattern + ], + ); + Widget _buildColorField(String label, LineStyle style) => Row( + children: [ + Text( + label, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: style.color, + onColorChanged: (Color selectedColor) { + setState( + () { + final LineStyle newColor = + style.copyWith(color: selectedColor); + if (label == ChartLocalization.of(context).labelColor) { + _lineStyle = newColor; + } else { + _fillStyle = newColor; + } + }, + ); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentFillStyle => + _fillStyle ?? (widget.config as RectangleDrawingToolConfig).fillStyle; + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as RectangleDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as RectangleDrawingToolConfig).pattern; +} diff --git a/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart new file mode 100644 index 000000000..266e92f4b --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart @@ -0,0 +1,79 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_item.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../callbacks.dart'; + +part 'trend_drawing_tool_config.g.dart'; + +/// Trend drawing tool configurations. +@JsonSerializable() +class TrendDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const TrendDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.fillStyle = const LineStyle(thickness: 0.9, color: Colors.blue), + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory TrendDrawingToolConfig.fromJson(Map json) => + _$TrendDrawingToolConfigFromJson(json); + + /// Unique name for this drawing tool. + static const String name = 'dt_trend'; + + @override + Map toJson() => _$TrendDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool fill style + final LineStyle fillStyle; + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + final DrawingPatterns pattern; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + TrendDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + TrendDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? fillStyle, + LineStyle? lineStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + TrendDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + fillStyle: fillStyle ?? this.fillStyle, + lineStyle: lineStyle ?? this.lineStyle, + pattern: pattern ?? this.pattern, + edgePoints: edgePoints ?? this.edgePoints, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.g.dart new file mode 100644 index 000000000..721194fe3 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'trend_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TrendDrawingToolConfig _$TrendDrawingToolConfigFromJson( + Map json) => + TrendDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + fillStyle: json['fillStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.blue) + : LineStyle.fromJson(json['fillStyle'] as Map), + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + ); + +Map _$TrendDrawingToolConfigToJson( + TrendDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'fillStyle': instance.fillStyle, + 'lineStyle': instance.lineStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_item.dart new file mode 100644 index 000000000..3917d1789 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_item.dart @@ -0,0 +1,89 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; + +import '../callbacks.dart'; +import '../drawing_tool_item.dart'; + +/// Trend drawing tool item in the list of drawing tool which provide this +/// drawing tools options menu. +class TrendDrawingToolItem extends DrawingToolItem { + /// Initializes + const TrendDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + TrendDrawingToolConfig config = const TrendDrawingToolConfig(), + }) : super( + key: key, + title: 'Trend', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + TrendDrawingToolItemState(); +} + +/// Trend drawing tool Item State class +class TrendDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _fillStyle; + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + + @override + TrendDrawingToolConfig createDrawingToolConfig() => TrendDrawingToolConfig( + fillStyle: _currentFillStyle, + lineStyle: _currentLineStyle, + pattern: _currentPattern, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [ + _buildColorField( + ChartLocalization.of(context).labelColor, _currentLineStyle), + _buildColorField( + ChartLocalization.of(context).labelFillColor, _currentFillStyle), + // TODO(maryia-deriv): implement _buildPatternField() to set pattern + ], + ); + Widget _buildColorField(String label, LineStyle style) => Row( + children: [ + Text( + label, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: style.color, + onColorChanged: (Color selectedColor) { + setState(() { + final LineStyle newColor = style.copyWith(color: selectedColor); + if (label == ChartLocalization.of(context).labelColor) { + _lineStyle = newColor; + } else { + _fillStyle = newColor; + } + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentFillStyle => + _fillStyle ?? (widget.config as TrendDrawingToolConfig).fillStyle; + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as TrendDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as TrendDrawingToolConfig).pattern; +} diff --git a/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart b/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart new file mode 100644 index 000000000..b6aafb901 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart @@ -0,0 +1,81 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_item.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_item.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../callbacks.dart'; + +part 'vertical_drawing_tool_config.g.dart'; + +/// Vertical drawing tool configurations. +@JsonSerializable() +class VerticalDrawingToolConfig extends DrawingToolConfig { + /// Initializes + const VerticalDrawingToolConfig({ + String? configId, + DrawingData? drawingData, + List edgePoints = const [], + this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.white), + this.pattern = DrawingPatterns.solid, + this.enableLabel = true, + }) : super( + configId: configId, + drawingData: drawingData, + edgePoints: edgePoints, + ); + + /// Initializes from JSON. + factory VerticalDrawingToolConfig.fromJson(Map json) => + _$VerticalDrawingToolConfigFromJson(json); + + /// Unique name for this drawing tool. + static const String name = 'dt_vertical'; + + @override + Map toJson() => _$VerticalDrawingToolConfigToJson(this) + ..putIfAbsent(DrawingToolConfig.nameKey, () => name); + + /// Drawing tool line style + final LineStyle lineStyle; + + /// Drawing tool line pattern: 'solid', 'dotted', 'dashed' + final DrawingPatterns pattern; + + /// For enabling the label + final bool enableLabel; + + @override + DrawingToolItem getItem( + UpdateDrawingTool updateDrawingTool, + VoidCallback deleteDrawingTool, + ) => + VerticalDrawingToolItem( + config: this, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + VerticalDrawingToolConfig copyWith({ + String? configId, + DrawingData? drawingData, + LineStyle? lineStyle, + LineStyle? fillStyle, + DrawingPatterns? pattern, + List? edgePoints, + bool? enableLabel, + }) => + VerticalDrawingToolConfig( + configId: configId ?? this.configId, + drawingData: drawingData ?? this.drawingData, + edgePoints: edgePoints ?? this.edgePoints, + lineStyle: lineStyle ?? this.lineStyle, + pattern: pattern ?? this.pattern, + enableLabel: enableLabel ?? this.enableLabel, + ); +} diff --git a/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.g.dart b/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.g.dart new file mode 100644 index 000000000..4a1028d77 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'vertical_drawing_tool_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VerticalDrawingToolConfig _$VerticalDrawingToolConfigFromJson( + Map json) => + VerticalDrawingToolConfig( + configId: json['configId'] as String?, + drawingData: json['drawingData'] == null + ? null + : DrawingData.fromJson(json['drawingData'] as Map), + edgePoints: (json['edgePoints'] as List?) + ?.map((e) => EdgePoint.fromJson(e as Map)) + .toList() ?? + const [], + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pattern: $enumDecodeNullable(_$DrawingPatternsEnumMap, json['pattern']) ?? + DrawingPatterns.solid, + enableLabel: json['enableLabel'] as bool? ?? true, + ); + +Map _$VerticalDrawingToolConfigToJson( + VerticalDrawingToolConfig instance) => + { + 'drawingData': instance.drawingData, + 'edgePoints': instance.edgePoints, + 'configId': instance.configId, + 'lineStyle': instance.lineStyle, + 'pattern': _$DrawingPatternsEnumMap[instance.pattern]!, + 'enableLabel': instance.enableLabel, + }; + +const _$DrawingPatternsEnumMap = { + DrawingPatterns.solid: 'solid', + DrawingPatterns.dotted: 'dotted', + DrawingPatterns.dashed: 'dashed', +}; diff --git a/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_item.dart b/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_item.dart new file mode 100644 index 000000000..77693b843 --- /dev/null +++ b/lib/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_item.dart @@ -0,0 +1,99 @@ +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; + +import 'package:flutter/material.dart'; + +import '../callbacks.dart'; +import '../drawing_tool_item.dart'; + +/// Vertical drawing tool item in the list of drawing tool which provide this +/// drawing tools options menu. +class VerticalDrawingToolItem extends DrawingToolItem { + /// Initializes + const VerticalDrawingToolItem({ + required UpdateDrawingTool updateDrawingTool, + required VoidCallback deleteDrawingTool, + Key? key, + VerticalDrawingToolConfig config = const VerticalDrawingToolConfig(), + }) : super( + key: key, + title: 'Vertical', + config: config, + updateDrawingTool: updateDrawingTool, + deleteDrawingTool: deleteDrawingTool, + ); + + @override + DrawingToolItemState createDrawingToolItemState() => + VerticalDrawingToolItemState(); +} + +/// Vertival drawing tool Item State class +class VerticalDrawingToolItemState + extends DrawingToolItemState { + LineStyle? _lineStyle; + DrawingPatterns? _pattern; + bool? _enableLabel; + + @override + VerticalDrawingToolConfig createDrawingToolConfig() => + VerticalDrawingToolConfig( + lineStyle: _currentLineStyle, + pattern: _currentPattern, + enableLabel: _getEnableLanel, + ); + + @override + Widget getDrawingToolOptions() => Column( + children: [_buildColorField(), _buildEnableLabel()], + ); + + Widget _buildEnableLabel() => Row( + children: [ + const Text( + 'Enable Label', + style: TextStyle(fontSize: 10), + ), + Switch( + value: _getEnableLanel, + onChanged: (bool value) { + setState(() { + _enableLabel = value; + }); + updateDrawingTool(); + }, + ), + ], + ); + + Widget _buildColorField() => Row( + children: [ + Text( + ChartLocalization.of(context).labelColor, + style: const TextStyle(fontSize: 16), + ), + ColorSelector( + currentColor: _currentLineStyle.color, + onColorChanged: (Color selectedColor) { + setState(() { + _lineStyle = _currentLineStyle.copyWith(color: selectedColor); + }); + updateDrawingTool(); + }, + ) + ], + ); + + LineStyle get _currentLineStyle => + _lineStyle ?? (widget.config as VerticalDrawingToolConfig).lineStyle; + + DrawingPatterns get _currentPattern => + _pattern ?? (widget.config as VerticalDrawingToolConfig).pattern; + + bool get _getEnableLanel => + _enableLabel ?? (widget.config as VerticalDrawingToolConfig).enableLabel; +} diff --git a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart index 73c6c17d9..5d80dd0c1 100644 --- a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart @@ -2,6 +2,8 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/adx_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/bar_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -22,7 +24,20 @@ class ADXIndicatorConfig extends IndicatorConfig { this.showSeries = true, this.showChannelFill = false, this.showHistogram = false, - }) : super(isOverlay: false); + this.showShading = false, + this.lineStyle = const LineStyle(color: Colors.white), + this.positiveLineStyle = const LineStyle(color: Colors.green), + this.negativeLineStyle = const LineStyle(color: Colors.red), + this.barStyle = const BarStyle(), + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + showLastIndicator: showLastIndicator, + pipSize: pipSize, + title: title ?? ADXIndicatorConfig.name, + ); /// Initializes from JSON. factory ADXIndicatorConfig.fromJson(Map json) => @@ -41,15 +56,31 @@ class ADXIndicatorConfig extends IndicatorConfig { /// The period value for smoothing the ADX series. final int smoothingPeriod; - /// Wether to add channel fill between the Positive and Negative DI Indicator. + /// Whether to add channel fill between the Positive and Negative DI + /// Indicator. final bool showChannelFill; - /// Wether to show the histogram Series or not. + /// Whether to show the histogram Series or not. final bool showHistogram; + /// Whether to show the shading or not. + final bool showShading; + /// Wether to show the Series or not. final bool showSeries; + /// Line style. + final LineStyle lineStyle; + + /// Positive line style. + final LineStyle positiveLineStyle; + + /// Negative line style. + final LineStyle negativeLineStyle; + + /// Histogram bar style + final BarStyle barStyle; + @override Series getSeries(IndicatorInput indicatorInput) => ADXSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart index bef041b6d..15973a030 100644 --- a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart @@ -6,21 +6,46 @@ part of 'adx_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -ADXIndicatorConfig _$ADXIndicatorConfigFromJson(Map json) { - return ADXIndicatorConfig( - period: json['period'] as int, - smoothingPeriod: json['smoothingPeriod'] as int, - showSeries: json['showSeries'] as bool, - showChannelFill: json['showChannelFill'] as bool, - showHistogram: json['showHistogram'] as bool, - ); -} +ADXIndicatorConfig _$ADXIndicatorConfigFromJson(Map json) => + ADXIndicatorConfig( + period: json['period'] as int? ?? 14, + smoothingPeriod: json['smoothingPeriod'] as int? ?? 14, + showSeries: json['showSeries'] as bool? ?? true, + showChannelFill: json['showChannelFill'] as bool? ?? false, + showHistogram: json['showHistogram'] as bool? ?? false, + showShading: json['showShading'] as bool? ?? false, + lineStyle: json['lineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + positiveLineStyle: json['positiveLineStyle'] == null + ? const LineStyle(color: Colors.green) + : LineStyle.fromJson( + json['positiveLineStyle'] as Map), + negativeLineStyle: json['negativeLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson( + json['negativeLineStyle'] as Map), + barStyle: json['barStyle'] == null + ? const BarStyle() + : BarStyle.fromJson(json['barStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$ADXIndicatorConfigToJson(ADXIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'smoothingPeriod': instance.smoothingPeriod, 'showChannelFill': instance.showChannelFill, 'showHistogram': instance.showHistogram, + 'showShading': instance.showShading, 'showSeries': instance.showSeries, + 'lineStyle': instance.lineStyle, + 'positiveLineStyle': instance.positiveLineStyle, + 'negativeLineStyle': instance.negativeLineStyle, + 'barStyle': instance.barStyle, }; diff --git a/lib/src/add_ons/indicators_ui/adx/adx_indicator_item.dart b/lib/src/add_ons/indicators_ui/adx/adx_indicator_item.dart index 8b6a955ba..03eb154c0 100644 --- a/lib/src/add_ons/indicators_ui/adx/adx_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/adx/adx_indicator_item.dart @@ -1,5 +1,4 @@ import 'package:deriv_chart/generated/l10n.dart'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/field_widget.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart index cdf9d9de2..26c50f3d7 100644 --- a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart @@ -2,6 +2,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/alligator_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -25,7 +26,15 @@ class AlligatorIndicatorConfig extends IndicatorConfig { this.lipsOffset = 3, this.showLines = true, this.showFractal = false, - }) : super(); + this.jawLineStyle = const LineStyle(color: Colors.blue), + this.teethLineStyle = const LineStyle(color: Colors.red), + this.lipsLineStyle = const LineStyle(color: Colors.green), + bool showLastIndicator = false, + String? title, + }) : super( + showLastIndicator: showLastIndicator, + title: title ?? AlligatorIndicatorConfig.name, + ); /// Initializes from JSON. factory AlligatorIndicatorConfig.fromJson(Map json) => @@ -62,18 +71,31 @@ class AlligatorIndicatorConfig extends IndicatorConfig { /// show fractal indicator or not final bool showFractal; + /// Jaw line style. + final LineStyle jawLineStyle; + + /// Teeth line style. + final LineStyle teethLineStyle; + + /// Lips line style. + final LineStyle lipsLineStyle; + @override Series getSeries(IndicatorInput indicatorInput) => AlligatorSeries( indicatorInput, - jawOffset: jawOffset, - teethOffset: teethOffset, - lipsOffset: lipsOffset, alligatorOptions: AlligatorOptions( jawPeriod: jawPeriod, teethPeriod: teethPeriod, lipsPeriod: lipsPeriod, showLines: showLines, showFractal: showFractal, + jawOffset: jawOffset, + teethOffset: teethOffset, + lipsOffset: lipsOffset, + jawLineStyle: jawLineStyle, + teethLineStyle: teethLineStyle, + lipsLineStyle: lipsLineStyle, + showLastIndicator: showLastIndicator, ), ); diff --git a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart index 0adbe0fe7..375f5aca7 100644 --- a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart @@ -7,22 +7,34 @@ part of 'alligator_indicator_config.dart'; // ************************************************************************** AlligatorIndicatorConfig _$AlligatorIndicatorConfigFromJson( - Map json) { - return AlligatorIndicatorConfig( - jawPeriod: json['jawPeriod'] as int, - teethPeriod: json['teethPeriod'] as int, - lipsPeriod: json['lipsPeriod'] as int, - jawOffset: json['jawOffset'] as int, - teethOffset: json['teethOffset'] as int, - lipsOffset: json['lipsOffset'] as int, - showLines: json['showLines'] as bool, - showFractal: json['showFractal'] as bool, - ); -} + Map json) => + AlligatorIndicatorConfig( + jawPeriod: json['jawPeriod'] as int? ?? 13, + teethPeriod: json['teethPeriod'] as int? ?? 8, + lipsPeriod: json['lipsPeriod'] as int? ?? 5, + jawOffset: json['jawOffset'] as int? ?? 8, + teethOffset: json['teethOffset'] as int? ?? 5, + lipsOffset: json['lipsOffset'] as int? ?? 3, + showLines: json['showLines'] as bool? ?? true, + showFractal: json['showFractal'] as bool? ?? false, + jawLineStyle: json['jawLineStyle'] == null + ? const LineStyle(color: Colors.blue) + : LineStyle.fromJson(json['jawLineStyle'] as Map), + teethLineStyle: json['teethLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson(json['teethLineStyle'] as Map), + lipsLineStyle: json['lipsLineStyle'] == null + ? const LineStyle(color: Colors.green) + : LineStyle.fromJson(json['lipsLineStyle'] as Map), + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$AlligatorIndicatorConfigToJson( AlligatorIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, 'jawOffset': instance.jawOffset, 'jawPeriod': instance.jawPeriod, 'teethOffset': instance.teethOffset, @@ -31,4 +43,7 @@ Map _$AlligatorIndicatorConfigToJson( 'lipsPeriod': instance.lipsPeriod, 'showLines': instance.showLines, 'showFractal': instance.showFractal, + 'jawLineStyle': instance.jawLineStyle, + 'teethLineStyle': instance.teethLineStyle, + 'lipsLineStyle': instance.lipsLineStyle, }; diff --git a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_item.dart b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_item.dart index f99af5179..54ddce46f 100644 --- a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; diff --git a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart index 0d831f72a..81c057d26 100644 --- a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart @@ -5,6 +5,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/aroon_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -18,7 +19,17 @@ class AroonIndicatorConfig extends IndicatorConfig { /// Initializes const AroonIndicatorConfig({ this.period = 14, - }) : super(isOverlay: false); + this.upLineStyle = const LineStyle(color: Colors.green), + this.downLineStyle = const LineStyle(color: Colors.red), + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? AroonIndicatorConfig.name, + ); /// Initializes from JSON. factory AroonIndicatorConfig.fromJson(Map json) => @@ -34,6 +45,12 @@ class AroonIndicatorConfig extends IndicatorConfig { /// The period final int period; + /// Up line style. + final LineStyle upLineStyle; + + /// Down line style. + final LineStyle downLineStyle; + @override Series getSeries(IndicatorInput indicatorInput) => AroonSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart index c2b89ad47..d38c5bf33 100644 --- a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart @@ -6,14 +6,28 @@ part of 'aroon_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -AroonIndicatorConfig _$AroonIndicatorConfigFromJson(Map json) { - return AroonIndicatorConfig( - period: json['period'] as int, - ); -} +AroonIndicatorConfig _$AroonIndicatorConfigFromJson( + Map json) => + AroonIndicatorConfig( + period: json['period'] as int? ?? 14, + upLineStyle: json['upLineStyle'] == null + ? const LineStyle(color: Colors.green) + : LineStyle.fromJson(json['upLineStyle'] as Map), + downLineStyle: json['downLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson(json['downLineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$AroonIndicatorConfigToJson( AroonIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, + 'upLineStyle': instance.upLineStyle, + 'downLineStyle': instance.downLineStyle, }; diff --git a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_item.dart b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_item.dart index f44fda83d..ceeb154fd 100644 --- a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_item.dart @@ -1,5 +1,4 @@ import 'package:deriv_chart/generated/l10n.dart'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart index d363a3b60..f1906272d 100644 --- a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart @@ -16,8 +16,15 @@ part 'awesome_oscillator_indicator_config.g.dart'; @JsonSerializable() class AwesomeOscillatorIndicatorConfig extends IndicatorConfig { /// Initializes - const AwesomeOscillatorIndicatorConfig({this.barStyle = const BarStyle()}) - : super(isOverlay: false); + const AwesomeOscillatorIndicatorConfig({ + this.barStyle = const BarStyle(), + int pipSize = 4, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + title: title ?? AwesomeOscillatorIndicatorConfig.name, + ); /// Initializes from JSON. factory AwesomeOscillatorIndicatorConfig.fromJson( @@ -28,8 +35,10 @@ class AwesomeOscillatorIndicatorConfig extends IndicatorConfig { final BarStyle barStyle; @override - Series getSeries(IndicatorInput indicatorInput) => - AwesomeOscillatorSeries(indicatorInput, barStyle: barStyle); + Series getSeries(IndicatorInput indicatorInput) => AwesomeOscillatorSeries( + indicatorInput, + barStyle: barStyle, + ); /// Unique name for this indicator. static const String name = 'AwesomeOscillator'; diff --git a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart index a2e86a40d..3616667f3 100644 --- a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart @@ -7,14 +7,19 @@ part of 'awesome_oscillator_indicator_config.dart'; // ************************************************************************** AwesomeOscillatorIndicatorConfig _$AwesomeOscillatorIndicatorConfigFromJson( - Map json) { - return AwesomeOscillatorIndicatorConfig( - barStyle: BarStyle.fromJson(json['barStyle'] as Map), - ); -} + Map json) => + AwesomeOscillatorIndicatorConfig( + barStyle: json['barStyle'] == null + ? const BarStyle() + : BarStyle.fromJson(json['barStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + title: json['title'] as String?, + ); Map _$AwesomeOscillatorIndicatorConfigToJson( AwesomeOscillatorIndicatorConfig instance) => { + 'title': instance.title, + 'pipSize': instance.pipSize, 'barStyle': instance.barStyle, }; diff --git a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart index 7d86a31fa..c98205a9c 100644 --- a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart @@ -3,7 +3,9 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/bollinger_bands_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/color_converter.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -16,6 +18,7 @@ part 'bollinger_bands_indicator_config.g.dart'; /// Bollinger Bands Indicator Config @JsonSerializable() +@ColorConverter() class BollingerBandsIndicatorConfig extends MAIndicatorConfig { /// Initializes const BollingerBandsIndicatorConfig({ @@ -23,10 +26,17 @@ class BollingerBandsIndicatorConfig extends MAIndicatorConfig { MovingAverageType movingAverageType = MovingAverageType.simple, String fieldType = 'close', this.standardDeviation = 2, + this.upperLineStyle = const LineStyle(color: Colors.white), + this.middleLineStyle = const LineStyle(color: Colors.white), + this.lowerLineStyle = const LineStyle(color: Colors.white), + this.fillColor = Colors.white12, + this.showChannelFill = true, + bool showLastIndicator = false, }) : super( period: period, movingAverageType: movingAverageType, fieldType: fieldType, + showLastIndicator: showLastIndicator, ); /// Initializes from JSON. @@ -43,6 +53,21 @@ class BollingerBandsIndicatorConfig extends MAIndicatorConfig { /// Standard Deviation value final double standardDeviation; + /// Upper line style. + final LineStyle upperLineStyle; + + /// Middle line style. + final LineStyle middleLineStyle; + + /// Lower line style. + final LineStyle lowerLineStyle; + + /// Fill color. + final Color fillColor; + + /// Whether the area between upper and lower channel is filled. + final bool showChannelFill; + @override Series getSeries(IndicatorInput indicatorInput) => BollingerBandSeries.fromIndicator( @@ -51,6 +76,12 @@ class BollingerBandsIndicatorConfig extends MAIndicatorConfig { period: period, movingAverageType: movingAverageType, standardDeviationFactor: standardDeviation, + upperLineStyle: upperLineStyle, + middleLineStyle: middleLineStyle, + lowerLineStyle: lowerLineStyle, + fillColor: fillColor, + showChannelFill: showChannelFill, + showLastIndicator: showLastIndicator, ), ); diff --git a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart index 4de336912..7ce855ac5 100644 --- a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart @@ -7,52 +7,46 @@ part of 'bollinger_bands_indicator_config.dart'; // ************************************************************************** BollingerBandsIndicatorConfig _$BollingerBandsIndicatorConfigFromJson( - Map json) { - return BollingerBandsIndicatorConfig( - period: json['period'] as int, - movingAverageType: - _$enumDecode(_$MovingAverageTypeEnumMap, json['movingAverageType']), - fieldType: json['fieldType'] as String, - standardDeviation: (json['standardDeviation'] as num).toDouble(), - ); -} + Map json) => + BollingerBandsIndicatorConfig( + period: json['period'] as int? ?? 50, + movingAverageType: $enumDecodeNullable( + _$MovingAverageTypeEnumMap, json['movingAverageType']) ?? + MovingAverageType.simple, + fieldType: json['fieldType'] as String? ?? 'close', + standardDeviation: (json['standardDeviation'] as num?)?.toDouble() ?? 2, + upperLineStyle: json['upperLineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['upperLineStyle'] as Map), + middleLineStyle: json['middleLineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['middleLineStyle'] as Map), + lowerLineStyle: json['lowerLineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lowerLineStyle'] as Map), + fillColor: json['fillColor'] == null + ? Colors.white12 + : const ColorConverter().fromJson(json['fillColor'] as int), + showChannelFill: json['showChannelFill'] as bool? ?? true, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + ); Map _$BollingerBandsIndicatorConfigToJson( BollingerBandsIndicatorConfig instance) => { + 'showLastIndicator': instance.showLastIndicator, 'period': instance.period, 'movingAverageType': - _$MovingAverageTypeEnumMap[instance.movingAverageType], + _$MovingAverageTypeEnumMap[instance.movingAverageType]!, 'fieldType': instance.fieldType, 'standardDeviation': instance.standardDeviation, + 'upperLineStyle': instance.upperLineStyle, + 'middleLineStyle': instance.middleLineStyle, + 'lowerLineStyle': instance.lowerLineStyle, + 'fillColor': const ColorConverter().toJson(instance.fillColor), + 'showChannelFill': instance.showChannelFill, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - const _$MovingAverageTypeEnumMap = { MovingAverageType.simple: 'simple', MovingAverageType.exponential: 'exponential', diff --git a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_item.dart b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_item.dart index 89e68b0c7..472c25ee6 100644 --- a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/ma_indicator/ma_indicator_item.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart index a9ed4216d..28f508bca 100644 --- a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart @@ -26,7 +26,15 @@ class CCIIndicatorConfig extends IndicatorConfig { ), this.showZones = true, this.lineStyle = const LineStyle(color: Colors.white), - }) : super(isOverlay: false); + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? CCIIndicatorConfig.name, + ); /// Initializes from JSON. factory CCIIndicatorConfig.fromJson(Map json) => @@ -60,6 +68,8 @@ class CCIIndicatorConfig extends IndicatorConfig { overboughtLineStyle: oscillatorLinesConfig.overboughtStyle, oversoldLineStyle: oscillatorLinesConfig.oversoldStyle, showZones: showZones, + cciLineStyle: lineStyle, + showLastIndicator: showLastIndicator, ); @override diff --git a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart index ddd730537..36ec29a18 100644 --- a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart @@ -6,18 +6,28 @@ part of 'cci_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -CCIIndicatorConfig _$CCIIndicatorConfigFromJson(Map json) { - return CCIIndicatorConfig( - period: json['period'] as int, - oscillatorLinesConfig: OscillatorLinesConfig.fromJson( - json['oscillatorLinesConfig'] as Map), - showZones: json['showZones'] as bool, - lineStyle: LineStyle.fromJson(json['lineStyle'] as Map), - ); -} +CCIIndicatorConfig _$CCIIndicatorConfigFromJson(Map json) => + CCIIndicatorConfig( + period: json['period'] as int? ?? 20, + oscillatorLinesConfig: json['oscillatorLinesConfig'] == null + ? const OscillatorLinesConfig( + overboughtValue: 100, oversoldValue: -100) + : OscillatorLinesConfig.fromJson( + json['oscillatorLinesConfig'] as Map), + showZones: json['showZones'] as bool? ?? true, + lineStyle: json['lineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$CCIIndicatorConfigToJson(CCIIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'oscillatorLinesConfig': instance.oscillatorLinesConfig, 'lineStyle': instance.lineStyle, diff --git a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_item.dart b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_item.dart index 5461ae23b..8a1100424 100644 --- a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_item.dart @@ -1,12 +1,13 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/field_widget.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/oscillator_limit.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'cci_indicator_config.dart'; diff --git a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart index 46257ad3c..bfd877eb6 100644 --- a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart @@ -1,13 +1,15 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/callbacks.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/donchian_channels_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/color_converter.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'donchian_channel_indicator_item.dart'; @@ -26,7 +28,12 @@ class DonchianChannelIndicatorConfig extends IndicatorConfig { this.middleLineStyle = const LineStyle(color: Colors.white), this.lowerLineStyle = const LineStyle(color: Colors.green), this.fillColor = Colors.white12, - }) : super(); + bool showLastIndicator = false, + String? title, + }) : super( + title: title ?? DonchianChannelIndicatorConfig.name, + showLastIndicator: showLastIndicator, + ); /// Initializes from JSON. factory DonchianChannelIndicatorConfig.fromJson(Map json) => diff --git a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart index e1e770ca6..9ad774ddb 100644 --- a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart @@ -7,24 +7,32 @@ part of 'donchian_channel_indicator_config.dart'; // ************************************************************************** DonchianChannelIndicatorConfig _$DonchianChannelIndicatorConfigFromJson( - Map json) { - return DonchianChannelIndicatorConfig( - highPeriod: json['highPeriod'] as int, - lowPeriod: json['lowPeriod'] as int, - showChannelFill: json['showChannelFill'] as bool, - upperLineStyle: - LineStyle.fromJson(json['upperLineStyle'] as Map), - middleLineStyle: - LineStyle.fromJson(json['middleLineStyle'] as Map), - lowerLineStyle: - LineStyle.fromJson(json['lowerLineStyle'] as Map), - fillColor: const ColorConverter().fromJson(json['fillColor'] as int), - ); -} + Map json) => + DonchianChannelIndicatorConfig( + highPeriod: json['highPeriod'] as int? ?? 10, + lowPeriod: json['lowPeriod'] as int? ?? 10, + showChannelFill: json['showChannelFill'] as bool? ?? true, + upperLineStyle: json['upperLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson(json['upperLineStyle'] as Map), + middleLineStyle: json['middleLineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['middleLineStyle'] as Map), + lowerLineStyle: json['lowerLineStyle'] == null + ? const LineStyle(color: Colors.green) + : LineStyle.fromJson(json['lowerLineStyle'] as Map), + fillColor: json['fillColor'] == null + ? Colors.white12 + : const ColorConverter().fromJson(json['fillColor'] as int), + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$DonchianChannelIndicatorConfigToJson( DonchianChannelIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, 'highPeriod': instance.highPeriod, 'lowPeriod': instance.lowPeriod, 'showChannelFill': instance.showChannelFill, diff --git a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_item.dart b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_item.dart index 3428d142f..03a8528af 100644 --- a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_item.dart @@ -1,5 +1,4 @@ import 'package:deriv_chart/generated/l10n.dart'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart index 1cdbb429c..08dd7ebba 100644 --- a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart @@ -4,6 +4,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/dpo_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -23,11 +24,19 @@ class DPOIndicatorConfig extends MAIndicatorConfig { MovingAverageType movingAverageType = MovingAverageType.simple, String fieldType = 'close', this.isCentered = true, + LineStyle? lineStyle, + int pipSize = 4, + bool showLastIndicator = false, + String? title, }) : super( period: period, movingAverageType: movingAverageType, fieldType: fieldType, isOverlay: false, + lineStyle: lineStyle, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? DPOIndicatorConfig.name, ); /// Initializes from JSON. @@ -51,6 +60,9 @@ class DPOIndicatorConfig extends MAIndicatorConfig { period: period, movingAverageType: movingAverageType, isCentered: isCentered, + lineStyle: lineStyle, + pipSize: pipSize, + showLastIndicator: showLastIndicator, ), ); diff --git a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart index 82372ef4e..ee5a1cde2 100644 --- a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart @@ -6,51 +6,35 @@ part of 'dpo_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -DPOIndicatorConfig _$DPOIndicatorConfigFromJson(Map json) { - return DPOIndicatorConfig( - period: json['period'] as int, - movingAverageType: - _$enumDecode(_$MovingAverageTypeEnumMap, json['movingAverageType']), - fieldType: json['fieldType'] as String, - isCentered: json['isCentered'] as bool, - ); -} +DPOIndicatorConfig _$DPOIndicatorConfigFromJson(Map json) => + DPOIndicatorConfig( + period: json['period'] as int? ?? 14, + movingAverageType: $enumDecodeNullable( + _$MovingAverageTypeEnumMap, json['movingAverageType']) ?? + MovingAverageType.simple, + fieldType: json['fieldType'] as String? ?? 'close', + isCentered: json['isCentered'] as bool? ?? true, + lineStyle: json['lineStyle'] == null + ? null + : LineStyle.fromJson(json['lineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$DPOIndicatorConfigToJson(DPOIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'movingAverageType': - _$MovingAverageTypeEnumMap[instance.movingAverageType], + _$MovingAverageTypeEnumMap[instance.movingAverageType]!, 'fieldType': instance.fieldType, + 'lineStyle': instance.lineStyle, 'isCentered': instance.isCentered, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - const _$MovingAverageTypeEnumMap = { MovingAverageType.simple: 'simple', MovingAverageType.exponential: 'exponential', diff --git a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart index d3957b0bd..4f1adfdb6 100644 --- a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart @@ -1,6 +1,8 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fcb_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/color_converter.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -13,20 +15,42 @@ part 'fcb_indicator_config.g.dart'; /// Fractal Chaos Band Indicator Config @JsonSerializable() +@ColorConverter() class FractalChaosBandIndicatorConfig extends IndicatorConfig { /// Initializes - const FractalChaosBandIndicatorConfig({this.channelFill = false}) : super(); + const FractalChaosBandIndicatorConfig({ + this.highLineStyle = const LineStyle(color: Colors.blue), + this.lowLineStyle = const LineStyle(color: Colors.blue), + this.fillColor = Colors.white12, + this.showChannelFill = false, + bool showLastIndicator = false, + String? title, + }) : super( + showLastIndicator: showLastIndicator, + title: title ?? FractalChaosBandIndicatorConfig.name, + ); /// Initializes from JSON. factory FractalChaosBandIndicatorConfig.fromJson(Map json) => _$FractalChaosBandIndicatorConfigFromJson(json); + /// Upper line style. + final LineStyle highLineStyle; + + /// Lower line style. + final LineStyle lowLineStyle; + + /// Fill color. + final Color fillColor; + /// if it's true the channel between two lines will be filled - final bool channelFill; + final bool showChannelFill; @override - Series getSeries(IndicatorInput indicatorInput) => - FractalChaosBandSeries(indicatorInput); + Series getSeries(IndicatorInput indicatorInput) => FractalChaosBandSeries( + indicatorInput, + config: this, + ); /// Unique name for this indicator. static const String name = 'fcb'; diff --git a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart index a00a8889b..cff249bcd 100644 --- a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart @@ -7,14 +7,29 @@ part of 'fcb_indicator_config.dart'; // ************************************************************************** FractalChaosBandIndicatorConfig _$FractalChaosBandIndicatorConfigFromJson( - Map json) { - return FractalChaosBandIndicatorConfig( - channelFill: json['channelFill'] as bool, - ); -} + Map json) => + FractalChaosBandIndicatorConfig( + highLineStyle: json['highLineStyle'] == null + ? const LineStyle(color: Colors.blue) + : LineStyle.fromJson(json['highLineStyle'] as Map), + lowLineStyle: json['lowLineStyle'] == null + ? const LineStyle(color: Colors.blue) + : LineStyle.fromJson(json['lowLineStyle'] as Map), + fillColor: json['fillColor'] == null + ? Colors.white12 + : const ColorConverter().fromJson(json['fillColor'] as int), + showChannelFill: json['showChannelFill'] as bool? ?? false, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$FractalChaosBandIndicatorConfigToJson( FractalChaosBandIndicatorConfig instance) => { - 'channelFill': instance.channelFill, + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'highLineStyle': instance.highLineStyle, + 'lowLineStyle': instance.lowLineStyle, + 'fillColor': const ColorConverter().toJson(instance.fillColor), + 'showChannelFill': instance.showChannelFill, }; diff --git a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_item.dart b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_item.dart index 146010e03..b7b46cc8a 100644 --- a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; @@ -39,7 +39,7 @@ class FractalChaosBandIndicatorItemState @override FractalChaosBandIndicatorConfig createIndicatorConfig() => FractalChaosBandIndicatorConfig( - channelFill: currentChannelFill, + showChannelFill: currentChannelFill, ); @override @@ -73,5 +73,5 @@ class FractalChaosBandIndicatorItemState @protected bool get currentChannelFill => _channelFill ?? - (widget.config as FractalChaosBandIndicatorConfig).channelFill; + (widget.config as FractalChaosBandIndicatorConfig).showChannelFill; } diff --git a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart index de4e73ac1..e19773f4f 100644 --- a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart @@ -2,6 +2,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/alligator_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/bar_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -23,7 +24,14 @@ class GatorIndicatorConfig extends IndicatorConfig { this.jawOffset = 8, this.teethOffset = 5, this.lipsOffset = 3, - }) : super(isOverlay: false); + this.barStyle = const BarStyle(), + int pipSize = 4, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + title: title ?? GatorIndicatorConfig.name, + ); /// Initializes from JSON. factory GatorIndicatorConfig.fromJson(Map json) => @@ -54,6 +62,9 @@ class GatorIndicatorConfig extends IndicatorConfig { /// Smoothing period for lips series final int lipsPeriod; + /// Histogram bar style + final BarStyle barStyle; + @override Series getSeries(IndicatorInput indicatorInput) => GatorSeries( indicatorInput, @@ -63,6 +74,7 @@ class GatorIndicatorConfig extends IndicatorConfig { teethPeriod: teethPeriod, lipsPeriod: lipsPeriod, ), + barStyle: barStyle, ); @override diff --git a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart index c38f481f9..fcc41abc9 100644 --- a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart @@ -6,24 +6,32 @@ part of 'gator_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -GatorIndicatorConfig _$GatorIndicatorConfigFromJson(Map json) { - return GatorIndicatorConfig( - jawPeriod: json['jawPeriod'] as int, - teethPeriod: json['teethPeriod'] as int, - lipsPeriod: json['lipsPeriod'] as int, - jawOffset: json['jawOffset'] as int, - teethOffset: json['teethOffset'] as int, - lipsOffset: json['lipsOffset'] as int, - ); -} +GatorIndicatorConfig _$GatorIndicatorConfigFromJson( + Map json) => + GatorIndicatorConfig( + jawPeriod: json['jawPeriod'] as int? ?? 13, + teethPeriod: json['teethPeriod'] as int? ?? 8, + lipsPeriod: json['lipsPeriod'] as int? ?? 5, + jawOffset: json['jawOffset'] as int? ?? 8, + teethOffset: json['teethOffset'] as int? ?? 5, + lipsOffset: json['lipsOffset'] as int? ?? 3, + barStyle: json['barStyle'] == null + ? const BarStyle() + : BarStyle.fromJson(json['barStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + title: json['title'] as String?, + ); Map _$GatorIndicatorConfigToJson( GatorIndicatorConfig instance) => { + 'title': instance.title, + 'pipSize': instance.pipSize, 'jawOffset': instance.jawOffset, 'jawPeriod': instance.jawPeriod, 'teethOffset': instance.teethOffset, 'teethPeriod': instance.teethPeriod, 'lipsOffset': instance.lipsOffset, 'lipsPeriod': instance.lipsPeriod, + 'barStyle': instance.barStyle, }; diff --git a/lib/src/add_ons/indicators_ui/gator/gator_indicator_item.dart b/lib/src/add_ons/indicators_ui/gator/gator_indicator_item.dart index 3df06e1bd..64fb72732 100644 --- a/lib/src/add_ons/indicators_ui/gator/gator_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/gator/gator_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/gator/gator_indicator_config.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart index 8e0f7ad69..6981d5bc3 100644 --- a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart @@ -2,7 +2,8 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ichimoku_clouds_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; -import 'package:flutter/foundation.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import '../callbacks.dart'; @@ -21,7 +22,17 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { this.conversionLinePeriod = 9, this.laggingSpanOffset = -26, this.spanBPeriod = 52, - }) : super(); + this.conversionLineStyle = const LineStyle(color: Colors.indigo), + this.baseLineStyle = const LineStyle(color: Colors.redAccent), + this.spanALineStyle = const LineStyle(color: Colors.green), + this.spanBLineStyle = const LineStyle(color: Colors.red), + this.laggingLineStyle = const LineStyle(color: Colors.lime), + bool showLastIndicator = false, + String? title, + }) : super( + showLastIndicator: showLastIndicator, + title: title ?? IchimokuCloudIndicatorConfig.name, + ); /// Initializes from JSON. factory IchimokuCloudIndicatorConfig.fromJson(Map json) => @@ -46,6 +57,21 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { /// The period to calculate the Base Line value. final int laggingSpanOffset; + /// Conversion line style. + final LineStyle conversionLineStyle; + + /// Base line style. + final LineStyle baseLineStyle; + + /// Span A style. + final LineStyle spanALineStyle; + + /// Span B style. + final LineStyle spanBLineStyle; + + /// Lagging line style. + final LineStyle laggingLineStyle; + @override Series getSeries(IndicatorInput indicatorInput) => IchimokuCloudSeries(indicatorInput, @@ -54,6 +80,7 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { baseLinePeriod: baseLinePeriod, conversionLinePeriod: conversionLinePeriod, leadingSpanBPeriod: spanBPeriod, + showLastIndicator: showLastIndicator, )); @override diff --git a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart index b030f703a..d791ec386 100644 --- a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart @@ -7,20 +7,45 @@ part of 'ichimoku_cloud_indicator_config.dart'; // ************************************************************************** IchimokuCloudIndicatorConfig _$IchimokuCloudIndicatorConfigFromJson( - Map json) { - return IchimokuCloudIndicatorConfig( - baseLinePeriod: json['baseLinePeriod'] as int, - conversionLinePeriod: json['conversionLinePeriod'] as int, - laggingSpanOffset: json['laggingSpanOffset'] as int, - spanBPeriod: json['spanBPeriod'] as int, - ); -} + Map json) => + IchimokuCloudIndicatorConfig( + baseLinePeriod: json['baseLinePeriod'] as int? ?? 26, + conversionLinePeriod: json['conversionLinePeriod'] as int? ?? 9, + laggingSpanOffset: json['laggingSpanOffset'] as int? ?? -26, + spanBPeriod: json['spanBPeriod'] as int? ?? 52, + conversionLineStyle: json['conversionLineStyle'] == null + ? const LineStyle(color: Colors.indigo) + : LineStyle.fromJson( + json['conversionLineStyle'] as Map), + baseLineStyle: json['baseLineStyle'] == null + ? const LineStyle(color: Colors.redAccent) + : LineStyle.fromJson(json['baseLineStyle'] as Map), + spanALineStyle: json['spanALineStyle'] == null + ? const LineStyle(color: Colors.green) + : LineStyle.fromJson(json['spanALineStyle'] as Map), + spanBLineStyle: json['spanBLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson(json['spanBLineStyle'] as Map), + laggingLineStyle: json['laggingLineStyle'] == null + ? const LineStyle(color: Colors.lime) + : LineStyle.fromJson( + json['laggingLineStyle'] as Map), + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$IchimokuCloudIndicatorConfigToJson( IchimokuCloudIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, 'conversionLinePeriod': instance.conversionLinePeriod, 'baseLinePeriod': instance.baseLinePeriod, 'spanBPeriod': instance.spanBPeriod, 'laggingSpanOffset': instance.laggingSpanOffset, + 'conversionLineStyle': instance.conversionLineStyle, + 'baseLineStyle': instance.baseLineStyle, + 'spanALineStyle': instance.spanALineStyle, + 'spanBLineStyle': instance.spanBLineStyle, + 'laggingLineStyle': instance.laggingLineStyle, }; diff --git a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_item.dart b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_item.dart index 002d6ee75..c15577fb0 100644 --- a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; diff --git a/lib/src/add_ons/indicators_ui/indicator_config.dart b/lib/src/add_ons/indicators_ui/indicator_config.dart index cefa7fb6c..ecf5f2b44 100644 --- a/lib/src/add_ons/indicators_ui/indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/indicator_config.dart @@ -1,5 +1,4 @@ -import 'dart:convert'; - +import 'package:deriv_chart/src/add_ons/add_on_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/roc/roc_indicator_config.dart'; @@ -32,11 +31,14 @@ import 'zigzag_indicator/zigzag_indicator_config.dart'; /// Indicator config @immutable -abstract class IndicatorConfig { +abstract class IndicatorConfig extends AddOnConfig { /// Initializes const IndicatorConfig({ - this.isOverlay = true, - }); + required this.title, + bool isOverlay = true, + this.showLastIndicator = false, + this.pipSize = 4, + }) : super(isOverlay: isOverlay); /// Creates a concrete indicator config from JSON. factory IndicatorConfig.fromJson(Map json) { @@ -95,25 +97,17 @@ abstract class IndicatorConfig { } } - /// Whether the indicator is an overlay on the main chart or displays on a - /// separate chart. Default is set to `true`. - final bool isOverlay; - - /// Key of indicator name property in JSON. - static const String nameKey = 'name'; + /// The title of the indicator. + final String title; - /// Serialization to JSON. Serves as value in key-value storage. - /// - /// Must specify indicator `name` with `nameKey`. - Map toJson(); + /// Whether to show last indicator or not. + final bool showLastIndicator; - @override - bool operator ==(dynamic other) => - other is IndicatorConfig && - jsonEncode(other.toJson()) == jsonEncode(toJson()); + /// Number of digits after decimal point in price. + final int pipSize; - @override - int get hashCode => jsonEncode(toJson()).hashCode; + /// Key of indicator name property in JSON. + static const String nameKey = 'name'; /// Indicators supported field types static final Map supportedFieldTypes = diff --git a/lib/src/add_ons/indicators_ui/indicator_item.dart b/lib/src/add_ons/indicators_ui/indicator_item.dart index 149fe3146..14acf486b 100644 --- a/lib/src/add_ons/indicators_ui/indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/indicator_item.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; import 'callbacks.dart'; import 'indicator_config.dart'; -import 'indicator_repository.dart'; /// Representing and indicator item in indicators list dialog. abstract class IndicatorItem extends StatefulWidget { @@ -42,13 +42,13 @@ abstract class IndicatorItemState extends State { /// Indicators repository @protected - late IndicatorsRepository indicatorsRepo; + late Repository indicatorsRepo; @override void didChangeDependencies() { super.didChangeDependencies(); - indicatorsRepo = Provider.of(context); + indicatorsRepo = Provider.of>(context); } @override diff --git a/lib/src/add_ons/indicators_ui/indicator_repository.dart b/lib/src/add_ons/indicators_ui/indicator_repository.dart deleted file mode 100644 index 9113a9bcb..000000000 --- a/lib/src/add_ons/indicators_ui/indicator_repository.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'indicator_config.dart'; - -/// Storage key of saved indicators. -const String indicatorsKey = 'indicators'; - -/// Holds indicators that were added to the Chart during runtime. -class IndicatorsRepository extends ChangeNotifier { - /// Initializes - IndicatorsRepository() : _indicators = []; - - final List _indicators; - SharedPreferences? _prefs; - - /// List of indicators. - List get indicators => _indicators; - - /// Loads user selected indicators from shared preferences. - void loadFromPrefs(SharedPreferences prefs) { - _prefs = prefs; - - if (!prefs.containsKey(indicatorsKey)) { - // No saved indicators. - return; - } - - final List strings = prefs.getStringList(indicatorsKey)!; - _indicators.clear(); - - for (final String string in strings) { - final IndicatorConfig indicatorConfig = - IndicatorConfig.fromJson(jsonDecode(string)); - _indicators.add(indicatorConfig); - } - notifyListeners(); - } - - /// Adds a new indicator and updates storage. - void add(IndicatorConfig indicatorConfig) { - _indicators.add(indicatorConfig); - _writeToPrefs(); - notifyListeners(); - } - - /// Updates indicator at [index] and updates storage. - void updateAt(int index, IndicatorConfig indicatorConfig) { - if (index < 0 || index >= _indicators.length) { - return; - } - _indicators[index] = indicatorConfig; - _writeToPrefs(); - notifyListeners(); - } - - /// Removes indicator at [index] from repository and updates storage. - void removeAt(int index) { - if (index < 0 || index >= _indicators.length) { - return; - } - _indicators.removeAt(index); - _writeToPrefs(); - notifyListeners(); - } - - Future _writeToPrefs() async { - if (_prefs != null) { - await _prefs!.setStringList( - indicatorsKey, - _indicators - .map((IndicatorConfig config) => jsonEncode(config)) - .toList(), - ); - } - } -} diff --git a/lib/src/add_ons/indicators_ui/indicators_dialog.dart b/lib/src/add_ons/indicators_ui/indicators_dialog.dart index 57d5f6638..d61464d67 100644 --- a/lib/src/add_ons/indicators_ui/indicators_dialog.dart +++ b/lib/src/add_ons/indicators_ui/indicators_dialog.dart @@ -6,6 +6,7 @@ import 'package:deriv_chart/src/add_ons/indicators_ui/dpo_indicator/dpo_indicato import 'package:deriv_chart/src/add_ons/indicators_ui/gator/gator_indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/smi/smi_indicator_config.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; import 'package:deriv_chart/src/widgets/animated_popup.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -18,7 +19,6 @@ import 'donchian_channel/donchian_channel_indicator_config.dart'; import 'fcb_indicator/fcb_indicator_config.dart'; import 'ichimoku_clouds/ichimoku_cloud_indicator_config.dart'; import 'indicator_config.dart'; -import 'indicator_repository.dart'; import 'ma_env_indicator/ma_env_indicator_config.dart'; import 'macd_indicator/macd_indicator_config.dart'; import 'parabolic_sar/parabolic_sar_indicator_config.dart'; @@ -38,7 +38,8 @@ class _IndicatorsDialogState extends State { @override Widget build(BuildContext context) { - final IndicatorsRepository repo = context.watch(); + final Repository repo = + context.watch>(); return AnimatedPopupDialog( child: Column( @@ -49,92 +50,92 @@ class _IndicatorsDialogState extends State { DropdownButton( value: _selectedIndicator, hint: const Text('Select indicator'), - items: >[ - const DropdownMenuItem( + items: const >[ + DropdownMenuItem( child: Text('Moving average'), value: MAIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Moving average envelope'), value: MAEnvIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Bollinger bands'), value: BollingerBandsIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Donchian channel'), value: DonchianChannelIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Alligator'), value: AlligatorIndicatorConfig(), ), DropdownMenuItem( - child: const Text('Rainbow'), + child: Text('Rainbow'), value: RainbowIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('ZigZag'), value: ZigZagIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Ichimoku Clouds'), value: IchimokuCloudIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Parabolic SAR'), value: ParabolicSARConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('RSI'), value: RSIIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Commodity Channel Index'), value: CCIIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('FCB'), value: FractalChaosBandIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('StochasticOscillator'), value: StochasticOscillatorIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('ADX'), value: ADXIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('DPO'), value: DPOIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Stochastic Momentum Index'), value: SMIIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Williams %R'), value: WilliamsRIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('AwesomeOscillator'), value: AwesomeOscillatorIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('MACD'), value: MACDIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Aroon'), value: AroonIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Price Rate Of Changes'), value: ROCIndicatorConfig(), ), - const DropdownMenuItem( + DropdownMenuItem( child: Text('Gator Oscillator'), value: GatorIndicatorConfig(), ) @@ -161,9 +162,9 @@ class _IndicatorsDialogState extends State { Expanded( child: ListView.builder( shrinkWrap: true, - itemCount: repo.indicators.length, + itemCount: repo.items.length, itemBuilder: (BuildContext context, int index) => - repo.indicators[index].getItem( + repo.items[index].getItem( (IndicatorConfig updatedConfig) => repo.updateAt(index, updatedConfig), () { diff --git a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart index 27e075439..6a8c303e8 100644 --- a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart @@ -3,7 +3,9 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ma_env_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/color_converter.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -17,6 +19,7 @@ part 'ma_env_indicator_config.g.dart'; /// Moving Average Envelope Indicator Config @JsonSerializable() +@ColorConverter() class MAEnvIndicatorConfig extends MAIndicatorConfig { /// Initializes const MAEnvIndicatorConfig({ @@ -25,10 +28,17 @@ class MAEnvIndicatorConfig extends MAIndicatorConfig { String fieldType = 'close', this.shift = 5, this.shiftType = ShiftType.percent, + this.upperLineStyle = const LineStyle(color: Colors.green), + this.middleLineStyle = const LineStyle(color: Colors.blue), + this.lowerLineStyle = const LineStyle(color: Colors.red), + this.fillColor = Colors.white12, + this.showChannelFill = true, + bool showLastIndicator = false, }) : super( period: period, movingAverageType: movingAverageType, fieldType: fieldType, + showLastIndicator: showLastIndicator, ); /// Initializes from JSON. @@ -48,6 +58,21 @@ class MAEnvIndicatorConfig extends MAIndicatorConfig { /// Moving Average Envelope shift final double shift; + /// Upper line style. + final LineStyle upperLineStyle; + + /// Middle line style. + final LineStyle middleLineStyle; + + /// Lower line style. + final LineStyle lowerLineStyle; + + /// Fill color. + final Color fillColor; + + /// Whether the area between upper and lower channel is filled. + final bool showChannelFill; + @override Series getSeries(IndicatorInput indicatorInput) => MAEnvSeries.fromIndicator( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), @@ -56,6 +81,12 @@ class MAEnvIndicatorConfig extends MAIndicatorConfig { movingAverageType: movingAverageType, shift: shift, shiftType: shiftType, + upperLineStyle: upperLineStyle, + middleLineStyle: middleLineStyle, + lowerLineStyle: lowerLineStyle, + fillColor: fillColor, + showChannelFill: showChannelFill, + showLastIndicator: showLastIndicator, )); @override diff --git a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart index 827686515..a44bfd28e 100644 --- a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart @@ -6,54 +6,50 @@ part of 'ma_env_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -MAEnvIndicatorConfig _$MAEnvIndicatorConfigFromJson(Map json) { - return MAEnvIndicatorConfig( - period: json['period'] as int, - movingAverageType: - _$enumDecode(_$MovingAverageTypeEnumMap, json['movingAverageType']), - fieldType: json['fieldType'] as String, - shift: (json['shift'] as num).toDouble(), - shiftType: _$enumDecode(_$ShiftTypeEnumMap, json['shiftType']), - ); -} +MAEnvIndicatorConfig _$MAEnvIndicatorConfigFromJson( + Map json) => + MAEnvIndicatorConfig( + period: json['period'] as int? ?? 50, + movingAverageType: $enumDecodeNullable( + _$MovingAverageTypeEnumMap, json['movingAverageType']) ?? + MovingAverageType.simple, + fieldType: json['fieldType'] as String? ?? 'close', + shift: (json['shift'] as num?)?.toDouble() ?? 5, + shiftType: $enumDecodeNullable(_$ShiftTypeEnumMap, json['shiftType']) ?? + ShiftType.percent, + upperLineStyle: json['upperLineStyle'] == null + ? const LineStyle(color: Colors.green) + : LineStyle.fromJson(json['upperLineStyle'] as Map), + middleLineStyle: json['middleLineStyle'] == null + ? const LineStyle(color: Colors.blue) + : LineStyle.fromJson(json['middleLineStyle'] as Map), + lowerLineStyle: json['lowerLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson(json['lowerLineStyle'] as Map), + fillColor: json['fillColor'] == null + ? Colors.white12 + : const ColorConverter().fromJson(json['fillColor'] as int), + showChannelFill: json['showChannelFill'] as bool? ?? true, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + ); Map _$MAEnvIndicatorConfigToJson( MAEnvIndicatorConfig instance) => { + 'showLastIndicator': instance.showLastIndicator, 'period': instance.period, 'movingAverageType': - _$MovingAverageTypeEnumMap[instance.movingAverageType], + _$MovingAverageTypeEnumMap[instance.movingAverageType]!, 'fieldType': instance.fieldType, - 'shiftType': _$ShiftTypeEnumMap[instance.shiftType], + 'shiftType': _$ShiftTypeEnumMap[instance.shiftType]!, 'shift': instance.shift, + 'upperLineStyle': instance.upperLineStyle, + 'middleLineStyle': instance.middleLineStyle, + 'lowerLineStyle': instance.lowerLineStyle, + 'fillColor': const ColorConverter().toJson(instance.fillColor), + 'showChannelFill': instance.showChannelFill, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - const _$MovingAverageTypeEnumMap = { MovingAverageType.simple: 'simple', MovingAverageType.exponential: 'exponential', diff --git a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart index aeae5fc4c..ab27d8ae3 100644 --- a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart @@ -24,13 +24,21 @@ class MAIndicatorConfig extends IndicatorConfig { LineStyle? lineStyle, int? offset, bool isOverlay = true, + int pipSize = 4, + bool showLastIndicator = false, + String? title, }) : period = period ?? 50, movingAverageType = movingAverageType ?? MovingAverageType.simple, fieldType = fieldType ?? 'close', offset = offset ?? 0, lineStyle = lineStyle ?? const LineStyle(color: Colors.yellow, thickness: 0.6), - super(isOverlay: isOverlay); + super( + isOverlay: isOverlay, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? MAIndicatorConfig.name, + ); /// Initializes from JSON. factory MAIndicatorConfig.fromJson(Map json) => @@ -61,7 +69,11 @@ class MAIndicatorConfig extends IndicatorConfig { @override Series getSeries(IndicatorInput indicatorInput) => MASeries.fromIndicator( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), - MAOptions(period: period, type: movingAverageType), + MAOptions( + period: period, + type: movingAverageType, + showLastIndicator: showLastIndicator, + ), offset: offset, style: lineStyle, ); diff --git a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart index 2c28c1bd3..f7d015d44 100644 --- a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart @@ -6,68 +6,36 @@ part of 'ma_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -MAIndicatorConfig _$MAIndicatorConfigFromJson(Map json) { - return MAIndicatorConfig( - period: json['period'] as int?, - movingAverageType: _$enumDecodeNullable( - _$MovingAverageTypeEnumMap, json['movingAverageType']), - fieldType: json['fieldType'] as String?, - lineStyle: json['lineStyle'] == null - ? null - : LineStyle.fromJson(json['lineStyle'] as Map), - offset: json['offset'] as int?, - isOverlay: json['isOverlay'] as bool, - ); -} +MAIndicatorConfig _$MAIndicatorConfigFromJson(Map json) => + MAIndicatorConfig( + period: json['period'] as int?, + movingAverageType: $enumDecodeNullable( + _$MovingAverageTypeEnumMap, json['movingAverageType']), + fieldType: json['fieldType'] as String?, + lineStyle: json['lineStyle'] == null + ? null + : LineStyle.fromJson(json['lineStyle'] as Map), + offset: json['offset'] as int?, + isOverlay: json['isOverlay'] as bool? ?? true, + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$MAIndicatorConfigToJson(MAIndicatorConfig instance) => { 'isOverlay': instance.isOverlay, + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'movingAverageType': - _$MovingAverageTypeEnumMap[instance.movingAverageType], + _$MovingAverageTypeEnumMap[instance.movingAverageType]!, 'fieldType': instance.fieldType, 'lineStyle': instance.lineStyle, 'offset': instance.offset, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - -K? _$enumDecodeNullable( - Map enumValues, - dynamic source, { - K? unknownValue, -}) { - if (source == null) { - return null; - } - return _$enumDecode(enumValues, source, unknownValue: unknownValue); -} - const _$MovingAverageTypeEnumMap = { MovingAverageType.simple: 'simple', MovingAverageType.exponential: 'exponential', diff --git a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_item.dart b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_item.dart index 9ac210b12..356ebfb77 100644 --- a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_item.dart @@ -1,9 +1,11 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/color_selector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'ma_indicator_config.dart'; diff --git a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart index 2b810bfee..322fa2c22 100644 --- a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart @@ -2,6 +2,8 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/macd_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/bar_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -20,20 +22,38 @@ class MACDIndicatorConfig extends IndicatorConfig { this.fastMAPeriod = 12, this.slowMAPeriod = 26, this.signalPeriod = 9, - }) : super(isOverlay: false); + this.barStyle = const BarStyle(), + this.lineStyle = const LineStyle(color: Colors.white), + this.signalLineStyle = const LineStyle(color: Colors.redAccent), + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? MACDIndicatorConfig.name, + ); /// Initializes from JSON. factory MACDIndicatorConfig.fromJson(Map json) => _$MACDIndicatorConfigFromJson(json); @override - Series getSeries(IndicatorInput indicatorInput) => MACDSeries(indicatorInput, - config: this, - options: MACDOptions( - fastMAPeriod: fastMAPeriod, - slowMAPeriod: slowMAPeriod, - signalPeriod: signalPeriod, - )); + Series getSeries(IndicatorInput indicatorInput) => MACDSeries( + indicatorInput, + config: this, + options: MACDOptions( + fastMAPeriod: fastMAPeriod, + slowMAPeriod: slowMAPeriod, + signalPeriod: signalPeriod, + barStyle: barStyle, + lineStyle: lineStyle, + signalLineStyle: signalLineStyle, + showLastIndicator: showLastIndicator, + pipSize: pipSize, + ), + ); /// Unique name for this indicator. static const String name = 'macd'; @@ -51,6 +71,15 @@ class MACDIndicatorConfig extends IndicatorConfig { /// The period to calculate the Signal value. final int signalPeriod; + /// Histogram bar style + final BarStyle barStyle; + + /// Line style. + final LineStyle lineStyle; + + /// Signal line style. + final LineStyle signalLineStyle; + @override IndicatorItem getItem( UpdateIndicator updateIndicator, diff --git a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart index 751ad3834..0c625e2ad 100644 --- a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart @@ -6,18 +6,35 @@ part of 'macd_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -MACDIndicatorConfig _$MACDIndicatorConfigFromJson(Map json) { - return MACDIndicatorConfig( - fastMAPeriod: json['fastMAPeriod'] as int, - slowMAPeriod: json['slowMAPeriod'] as int, - signalPeriod: json['signalPeriod'] as int, - ); -} +MACDIndicatorConfig _$MACDIndicatorConfigFromJson(Map json) => + MACDIndicatorConfig( + fastMAPeriod: json['fastMAPeriod'] as int? ?? 12, + slowMAPeriod: json['slowMAPeriod'] as int? ?? 26, + signalPeriod: json['signalPeriod'] as int? ?? 9, + barStyle: json['barStyle'] == null + ? const BarStyle() + : BarStyle.fromJson(json['barStyle'] as Map), + lineStyle: json['lineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + signalLineStyle: json['signalLineStyle'] == null + ? const LineStyle(color: Colors.redAccent) + : LineStyle.fromJson(json['signalLineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$MACDIndicatorConfigToJson( MACDIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'fastMAPeriod': instance.fastMAPeriod, 'slowMAPeriod': instance.slowMAPeriod, 'signalPeriod': instance.signalPeriod, + 'barStyle': instance.barStyle, + 'lineStyle': instance.lineStyle, + 'signalLineStyle': instance.signalLineStyle, }; diff --git a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_item.dart b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_item.dart index 033e80043..e8c38e05e 100644 --- a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; diff --git a/lib/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.g.dart b/lib/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.g.dart index 08465b133..a48e2f784 100644 --- a/lib/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.g.dart +++ b/lib/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.g.dart @@ -7,16 +7,17 @@ part of 'oscillator_lines_config.dart'; // ************************************************************************** OscillatorLinesConfig _$OscillatorLinesConfigFromJson( - Map json) { - return OscillatorLinesConfig( - overboughtValue: (json['overboughtValue'] as num).toDouble(), - oversoldValue: (json['oversoldValue'] as num).toDouble(), - overboughtStyle: - LineStyle.fromJson(json['overboughtStyle'] as Map), - oversoldStyle: - LineStyle.fromJson(json['oversoldStyle'] as Map), - ); -} + Map json) => + OscillatorLinesConfig( + overboughtValue: (json['overboughtValue'] as num).toDouble(), + oversoldValue: (json['oversoldValue'] as num).toDouble(), + overboughtStyle: json['overboughtStyle'] == null + ? const LineStyle(thickness: 0.5) + : LineStyle.fromJson(json['overboughtStyle'] as Map), + oversoldStyle: json['oversoldStyle'] == null + ? const LineStyle(thickness: 0.5) + : LineStyle.fromJson(json['oversoldStyle'] as Map), + ); Map _$OscillatorLinesConfigToJson( OscillatorLinesConfig instance) => diff --git a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart index acc7f87b8..9d1f48c3e 100644 --- a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart @@ -21,7 +21,8 @@ class ParabolicSARConfig extends IndicatorConfig { this.minAccelerationFactor = 0.02, this.maxAccelerationFactor = 0.2, this.scatterStyle = const ScatterStyle(), - }) : super(); + String? title, + }) : super(title: title ?? ParabolicSARConfig.name); /// Initializes from JSON. factory ParabolicSARConfig.fromJson(Map json) => diff --git a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart index 6773a01b0..62cde9dbd 100644 --- a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart @@ -6,17 +6,21 @@ part of 'parabolic_sar_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -ParabolicSARConfig _$ParabolicSARConfigFromJson(Map json) { - return ParabolicSARConfig( - minAccelerationFactor: (json['minAccelerationFactor'] as num).toDouble(), - maxAccelerationFactor: (json['maxAccelerationFactor'] as num).toDouble(), - scatterStyle: - ScatterStyle.fromJson(json['scatterStyle'] as Map), - ); -} +ParabolicSARConfig _$ParabolicSARConfigFromJson(Map json) => + ParabolicSARConfig( + minAccelerationFactor: + (json['minAccelerationFactor'] as num?)?.toDouble() ?? 0.02, + maxAccelerationFactor: + (json['maxAccelerationFactor'] as num?)?.toDouble() ?? 0.2, + scatterStyle: json['scatterStyle'] == null + ? const ScatterStyle() + : ScatterStyle.fromJson(json['scatterStyle'] as Map), + title: json['title'] as String?, + ); Map _$ParabolicSARConfigToJson(ParabolicSARConfig instance) => { + 'title': instance.title, 'minAccelerationFactor': instance.minAccelerationFactor, 'maxAccelerationFactor': instance.maxAccelerationFactor, 'scatterStyle': instance.scatterStyle, diff --git a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart index 12e218aa6..3a7ca59d9 100644 --- a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart @@ -3,7 +3,9 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rainbow_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/color_converter.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -16,18 +18,26 @@ part 'rainbow_indicator_config.g.dart'; /// Rainbow Indicator Config @JsonSerializable() +@ColorConverter() class RainbowIndicatorConfig extends MAIndicatorConfig { /// Initializes - RainbowIndicatorConfig({ + const RainbowIndicatorConfig({ int period = 50, MovingAverageType movingAverageType = MovingAverageType.simple, String fieldType = 'close', this.bandsCount = 10, - }) : rainbowColors = _getRainbowColors(bandsCount), + this.rainbowLineStyles, + bool showLastIndicator = false, + }) : assert( + rainbowLineStyles == null || rainbowLineStyles.length == bandsCount, + '''If you provide [rainbowLineStyles] it should have the same length as [bandsCount]. + since the bands count is $bandsCount. the same number of lineStyles should be provided.''', + ), super( period: period, movingAverageType: movingAverageType, fieldType: fieldType, + showLastIndicator: showLastIndicator, ); /// Initializes from JSON. @@ -57,18 +67,22 @@ class RainbowIndicatorConfig extends MAIndicatorConfig { /// Rainbow Moving Averages bands count final int bandsCount; - /// List of colors for the different bands in the [RainbowSeries]. - final List rainbowColors; + /// List of line styles for the different bands in the [RainbowSeries]. + final List? rainbowLineStyles; @override Series getSeries(IndicatorInput indicatorInput) => RainbowSeries.fromIndicator( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), - rainbowColors: rainbowColors, + rainbowLineStyles: rainbowLineStyles ?? + _getRainbowColors(bandsCount) + .map((Color color) => LineStyle(color: color)) + .toList(), rainbowOptions: RainbowOptions( period: period, movingAverageType: movingAverageType, bandsCount: bandsCount, + showLastIndicator: showLastIndicator, ), ); diff --git a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.g.dart index 778045ea1..0a4d53b69 100644 --- a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.g.dart @@ -7,52 +7,32 @@ part of 'rainbow_indicator_config.dart'; // ************************************************************************** RainbowIndicatorConfig _$RainbowIndicatorConfigFromJson( - Map json) { - return RainbowIndicatorConfig( - period: json['period'] as int, - movingAverageType: - _$enumDecode(_$MovingAverageTypeEnumMap, json['movingAverageType']), - fieldType: json['fieldType'] as String, - bandsCount: json['bandsCount'] as int, - ); -} + Map json) => + RainbowIndicatorConfig( + period: json['period'] as int? ?? 50, + movingAverageType: $enumDecodeNullable( + _$MovingAverageTypeEnumMap, json['movingAverageType']) ?? + MovingAverageType.simple, + fieldType: json['fieldType'] as String? ?? 'close', + bandsCount: json['bandsCount'] as int? ?? 10, + rainbowLineStyles: (json['rainbowLineStyles'] as List?) + ?.map((e) => LineStyle.fromJson(e as Map)) + .toList(), + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + ); Map _$RainbowIndicatorConfigToJson( RainbowIndicatorConfig instance) => { + 'showLastIndicator': instance.showLastIndicator, 'period': instance.period, 'movingAverageType': - _$MovingAverageTypeEnumMap[instance.movingAverageType], + _$MovingAverageTypeEnumMap[instance.movingAverageType]!, 'fieldType': instance.fieldType, 'bandsCount': instance.bandsCount, + 'rainbowLineStyles': instance.rainbowLineStyles, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - const _$MovingAverageTypeEnumMap = { MovingAverageType.simple: 'simple', MovingAverageType.exponential: 'exponential', diff --git a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_item.dart b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_item.dart index 565a0885c..50757852c 100644 --- a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/ma_indicator/ma_indicator_item.dart'; diff --git a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart index 347824783..1a144c3b2 100644 --- a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart @@ -6,6 +6,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/roc_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -18,7 +19,16 @@ class ROCIndicatorConfig extends IndicatorConfig { const ROCIndicatorConfig({ this.period = 14, this.fieldType = 'close', - }) : super(isOverlay: false); + this.lineStyle, + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? ROCIndicatorConfig.name, + ); /// Initializes from JSON. factory ROCIndicatorConfig.fromJson(Map json) => @@ -37,10 +47,18 @@ class ROCIndicatorConfig extends IndicatorConfig { /// Field type final String fieldType; + /// Line style. + final LineStyle? lineStyle; + @override Series getSeries(IndicatorInput indicatorInput) => ROCSeries.fromIndicator( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), - rocOptions: ROCOptions(period: period), + rocOptions: ROCOptions( + period: period, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + ), + lineStyle: lineStyle, ); @override diff --git a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart index 66d9d4aed..924c5dab7 100644 --- a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart @@ -6,15 +6,24 @@ part of 'roc_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -ROCIndicatorConfig _$ROCIndicatorConfigFromJson(Map json) { - return ROCIndicatorConfig( - period: json['period'] as int, - fieldType: json['fieldType'] as String, - ); -} +ROCIndicatorConfig _$ROCIndicatorConfigFromJson(Map json) => + ROCIndicatorConfig( + period: json['period'] as int? ?? 14, + fieldType: json['fieldType'] as String? ?? 'close', + lineStyle: json['lineStyle'] == null + ? null + : LineStyle.fromJson(json['lineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$ROCIndicatorConfigToJson(ROCIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'fieldType': instance.fieldType, + 'lineStyle': instance.lineStyle, }; diff --git a/lib/src/add_ons/indicators_ui/roc/roc_indicator_item.dart b/lib/src/add_ons/indicators_ui/roc/roc_indicator_item.dart index 4e52e4467..e57dc4c24 100644 --- a/lib/src/add_ons/indicators_ui/roc/roc_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/roc/roc_indicator_item.dart @@ -1,5 +1,4 @@ import 'package:deriv_chart/generated/l10n.dart'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/callbacks.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_item.dart'; diff --git a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart index 5874e2a82..604ff3add 100644 --- a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart @@ -1,13 +1,14 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rsi_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/rsi_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import '../callbacks.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'rsi_indicator_item.dart'; @@ -27,7 +28,15 @@ class RSIIndicatorConfig extends IndicatorConfig { this.lineStyle = const LineStyle(color: Colors.white), this.pinLabels = false, this.showZones = true, - }) : super(isOverlay: false); + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? RSIIndicatorConfig.name, + ); /// Initializes from JSON. factory RSIIndicatorConfig.fromJson(Map json) => @@ -63,7 +72,11 @@ class RSIIndicatorConfig extends IndicatorConfig { Series getSeries(IndicatorInput indicatorInput) => RSISeries.fromIndicator( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), this, - rsiOptions: RSIOptions(period: period), + rsiOptions: RSIOptions( + period: period, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + ), showZones: showZones, ); diff --git a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart index 7dd0f24fd..63b00bfe0 100644 --- a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart @@ -6,20 +6,29 @@ part of 'rsi_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -RSIIndicatorConfig _$RSIIndicatorConfigFromJson(Map json) { - return RSIIndicatorConfig( - period: json['period'] as int, - fieldType: json['fieldType'] as String, - oscillatorLinesConfig: OscillatorLinesConfig.fromJson( - json['oscillatorLinesConfig'] as Map), - lineStyle: LineStyle.fromJson(json['lineStyle'] as Map), - pinLabels: json['pinLabels'] as bool, - showZones: json['showZones'] as bool, - ); -} +RSIIndicatorConfig _$RSIIndicatorConfigFromJson(Map json) => + RSIIndicatorConfig( + period: json['period'] as int? ?? 14, + fieldType: json['fieldType'] as String? ?? 'close', + oscillatorLinesConfig: json['oscillatorLinesConfig'] == null + ? const OscillatorLinesConfig(overboughtValue: 80, oversoldValue: 20) + : OscillatorLinesConfig.fromJson( + json['oscillatorLinesConfig'] as Map), + lineStyle: json['lineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + pinLabels: json['pinLabels'] as bool? ?? false, + showZones: json['showZones'] as bool? ?? true, + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$RSIIndicatorConfigToJson(RSIIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'lineStyle': instance.lineStyle, 'fieldType': instance.fieldType, diff --git a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_item.dart b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_item.dart index d690ddf70..622914c9c 100644 --- a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_item.dart @@ -1,11 +1,12 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/oscillator_lines/oscillator_lines_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/oscillator_limit.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import '../callbacks.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'rsi_indicator_config.dart'; diff --git a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart index 12615b525..577454765 100644 --- a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart @@ -4,6 +4,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/smi_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -27,7 +28,17 @@ class SMIIndicatorConfig extends IndicatorConfig { this.signalPeriod = 10, this.maType = MovingAverageType.exponential, this.showZones = true, - }) : super(isOverlay: false); + this.lineStyle, + this.signalLineStyle, + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? SMIIndicatorConfig.name, + ); /// Initializes from JSON. factory SMIIndicatorConfig.fromJson(Map json) => @@ -64,6 +75,12 @@ class SMIIndicatorConfig extends IndicatorConfig { /// Whether to show zones (intersection between indicator and overbought/sold). final bool showZones; + /// Line style. + final LineStyle? lineStyle; + + /// Signal line style. + final LineStyle? signalLineStyle; + @override Series getSeries(IndicatorInput indicatorInput) => SMISeries( indicatorInput, @@ -72,6 +89,10 @@ class SMIIndicatorConfig extends IndicatorConfig { smoothingPeriod: smoothingPeriod, doubleSmoothingPeriod: doubleSmoothingPeriod, signalOptions: MAOptions(period: signalPeriod, type: maType), + lineStyle: lineStyle, + signalLineStyle: signalLineStyle, + showLastIndicator: showLastIndicator, + pipSize: pipSize, ), overboughtValue: overboughtValue, oversoldValue: oversoldValue, diff --git a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart index d53c18909..30b34f195 100644 --- a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart @@ -6,57 +6,45 @@ part of 'smi_indicator_config.dart'; // JsonSerializableGenerator // ************************************************************************** -SMIIndicatorConfig _$SMIIndicatorConfigFromJson(Map json) { - return SMIIndicatorConfig( - period: json['period'] as int, - smoothingPeriod: json['smoothingPeriod'] as int, - doubleSmoothingPeriod: json['doubleSmoothingPeriod'] as int, - overboughtValue: (json['overboughtValue'] as num).toDouble(), - oversoldValue: (json['oversoldValue'] as num).toDouble(), - signalPeriod: json['signalPeriod'] as int, - maType: _$enumDecode(_$MovingAverageTypeEnumMap, json['maType']), - showZones: json['showZones'] as bool, - ); -} +SMIIndicatorConfig _$SMIIndicatorConfigFromJson(Map json) => + SMIIndicatorConfig( + period: json['period'] as int? ?? 10, + smoothingPeriod: json['smoothingPeriod'] as int? ?? 3, + doubleSmoothingPeriod: json['doubleSmoothingPeriod'] as int? ?? 3, + overboughtValue: (json['overboughtValue'] as num?)?.toDouble() ?? 40, + oversoldValue: (json['oversoldValue'] as num?)?.toDouble() ?? -40, + signalPeriod: json['signalPeriod'] as int? ?? 10, + maType: $enumDecodeNullable(_$MovingAverageTypeEnumMap, json['maType']) ?? + MovingAverageType.exponential, + showZones: json['showZones'] as bool? ?? true, + lineStyle: json['lineStyle'] == null + ? null + : LineStyle.fromJson(json['lineStyle'] as Map), + signalLineStyle: json['signalLineStyle'] == null + ? null + : LineStyle.fromJson(json['signalLineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$SMIIndicatorConfigToJson(SMIIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'smoothingPeriod': instance.smoothingPeriod, 'doubleSmoothingPeriod': instance.doubleSmoothingPeriod, 'signalPeriod': instance.signalPeriod, - 'maType': _$MovingAverageTypeEnumMap[instance.maType], + 'maType': _$MovingAverageTypeEnumMap[instance.maType]!, 'overboughtValue': instance.overboughtValue, 'oversoldValue': instance.oversoldValue, 'showZones': instance.showZones, + 'lineStyle': instance.lineStyle, + 'signalLineStyle': instance.signalLineStyle, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - const _$MovingAverageTypeEnumMap = { MovingAverageType.simple: 'simple', MovingAverageType.exponential: 'exponential', diff --git a/lib/src/add_ons/indicators_ui/smi/smi_indicator_item.dart b/lib/src/add_ons/indicators_ui/smi/smi_indicator_item.dart index 370023eec..90e559e8a 100644 --- a/lib/src/add_ons/indicators_ui/smi/smi_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/smi/smi_indicator_item.dart @@ -1,12 +1,12 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/dropdown_menu.dart' as deriv_dropdown; import 'package:deriv_chart/src/add_ons/indicators_ui/widgets/field_widget.dart'; - -import 'package:flutter/material.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; +import 'package:flutter/material.dart' hide DropdownMenu; import '../callbacks.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'smi_indicator_config.dart'; diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart index 18bab078d..e13a60df8 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart @@ -1,4 +1,3 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/callbacks.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_item.dart'; @@ -6,8 +5,10 @@ import 'package:deriv_chart/src/add_ons/indicators_ui/oscillator_lines/oscillato import 'package:deriv_chart/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_item.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/stochastic_oscillator_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/stochastic_oscillator_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -29,7 +30,17 @@ class StochasticOscillatorIndicatorConfig extends IndicatorConfig { overboughtValue: 80, oversoldValue: 20, ), - }) : super(isOverlay: false); + this.fastLineStyle = const LineStyle(color: Colors.white), + this.slowLineStyle = const LineStyle(color: Colors.red), + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? StochasticOscillatorIndicatorConfig.name, + ); /// Initializes from JSON. factory StochasticOscillatorIndicatorConfig.fromJson( @@ -70,6 +81,12 @@ class StochasticOscillatorIndicatorConfig extends IndicatorConfig { /// default is true final bool showZones; + /// Fast line style. + final LineStyle fastLineStyle; + + /// Slow line style. + final LineStyle slowLineStyle; + @override Series getSeries(IndicatorInput indicatorInput) => StochasticOscillatorSeries( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), this, diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart index 66b3e96fa..7e28d60d4 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart @@ -7,23 +7,41 @@ part of 'stochastic_oscillator_indicator_config.dart'; // ************************************************************************** StochasticOscillatorIndicatorConfig - _$StochasticOscillatorIndicatorConfigFromJson(Map json) { - return StochasticOscillatorIndicatorConfig( - period: json['period'] as int, - fieldType: json['fieldType'] as String, - overBoughtPrice: (json['overBoughtPrice'] as num).toDouble(), - overSoldPrice: (json['overSoldPrice'] as num).toDouble(), - showZones: json['showZones'] as bool, - isSmooth: json['isSmooth'] as bool, - lineStyle: LineStyle.fromJson(json['lineStyle'] as Map), - oscillatorLinesConfig: OscillatorLinesConfig.fromJson( - json['oscillatorLinesConfig'] as Map), - ); -} + _$StochasticOscillatorIndicatorConfigFromJson(Map json) => + StochasticOscillatorIndicatorConfig( + period: json['period'] as int? ?? 14, + fieldType: json['fieldType'] as String? ?? 'close', + overBoughtPrice: (json['overBoughtPrice'] as num?)?.toDouble() ?? 80, + overSoldPrice: (json['overSoldPrice'] as num?)?.toDouble() ?? 20, + showZones: json['showZones'] as bool? ?? false, + isSmooth: json['isSmooth'] as bool? ?? true, + lineStyle: json['lineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + oscillatorLinesConfig: json['oscillatorLinesConfig'] == null + ? const OscillatorLinesConfig( + overboughtValue: 80, oversoldValue: 20) + : OscillatorLinesConfig.fromJson( + json['oscillatorLinesConfig'] as Map), + fastLineStyle: json['fastLineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson( + json['fastLineStyle'] as Map), + slowLineStyle: json['slowLineStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson( + json['slowLineStyle'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$StochasticOscillatorIndicatorConfigToJson( StochasticOscillatorIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'overBoughtPrice': instance.overBoughtPrice, 'overSoldPrice': instance.overSoldPrice, @@ -32,4 +50,6 @@ Map _$StochasticOscillatorIndicatorConfigToJson( 'fieldType': instance.fieldType, 'isSmooth': instance.isSmooth, 'showZones': instance.showZones, + 'fastLineStyle': instance.fastLineStyle, + 'slowLineStyle': instance.slowLineStyle, }; diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_item.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_item.dart index 6132773cc..4e4a8fe30 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_item.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/generated/l10n.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/callbacks.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_item.dart'; diff --git a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart index 5dda2f3c0..14d67512e 100644 --- a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart @@ -27,7 +27,15 @@ class WilliamsRIndicatorConfig extends IndicatorConfig { oversoldValue: -80, overboughtValue: -20, ), - }) : super(isOverlay: false); + int pipSize = 4, + bool showLastIndicator = false, + String? title, + }) : super( + isOverlay: false, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + title: title ?? WilliamsRIndicatorConfig.name, + ); /// Initializes from JSON. factory WilliamsRIndicatorConfig.fromJson(Map json) => @@ -58,10 +66,17 @@ class WilliamsRIndicatorConfig extends IndicatorConfig { @override Series getSeries(IndicatorInput indicatorInput) => WilliamsRSeries( indicatorInput, - WilliamsROptions(period), + WilliamsROptions( + period, + pipSize: pipSize, + showLastIndicator: showLastIndicator, + ), overboughtValue: oscillatorLimits.overboughtValue, oversoldValue: oscillatorLimits.oversoldValue, + overboughtLineStyle: oscillatorLimits.overboughtStyle, + oversoldLineStyle: oscillatorLimits.oversoldStyle, showZones: showZones, + lineStyle: lineStyle, ); @override diff --git a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart index 8fbc73632..aa99d1bdd 100644 --- a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart @@ -7,21 +7,33 @@ part of 'williams_r_indicator_config.dart'; // ************************************************************************** WilliamsRIndicatorConfig _$WilliamsRIndicatorConfigFromJson( - Map json) { - return WilliamsRIndicatorConfig( - period: json['period'] as int, - lineStyle: LineStyle.fromJson(json['lineStyle'] as Map), - zeroHorizontalLinesStyle: LineStyle.fromJson( - json['zeroHorizontalLinesStyle'] as Map), - showZones: json['showZones'] as bool, - oscillatorLimits: OscillatorLinesConfig.fromJson( - json['oscillatorLimits'] as Map), - ); -} + Map json) => + WilliamsRIndicatorConfig( + period: json['period'] as int? ?? 14, + lineStyle: json['lineStyle'] == null + ? const LineStyle(color: Colors.white) + : LineStyle.fromJson(json['lineStyle'] as Map), + zeroHorizontalLinesStyle: json['zeroHorizontalLinesStyle'] == null + ? const LineStyle(color: Colors.red) + : LineStyle.fromJson( + json['zeroHorizontalLinesStyle'] as Map), + showZones: json['showZones'] as bool? ?? true, + oscillatorLimits: json['oscillatorLimits'] == null + ? const OscillatorLinesConfig( + oversoldValue: -80, overboughtValue: -20) + : OscillatorLinesConfig.fromJson( + json['oscillatorLimits'] as Map), + pipSize: json['pipSize'] as int? ?? 4, + showLastIndicator: json['showLastIndicator'] as bool? ?? false, + title: json['title'] as String?, + ); Map _$WilliamsRIndicatorConfigToJson( WilliamsRIndicatorConfig instance) => { + 'title': instance.title, + 'showLastIndicator': instance.showLastIndicator, + 'pipSize': instance.pipSize, 'period': instance.period, 'lineStyle': instance.lineStyle, 'zeroHorizontalLinesStyle': instance.zeroHorizontalLinesStyle, diff --git a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_item.dart b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_item.dart index 1c4a2a00d..706c63126 100644 --- a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_item.dart +++ b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_item.dart @@ -1,5 +1,4 @@ import 'package:deriv_chart/generated/l10n.dart'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart index 8b123fc99..99592d0be 100644 --- a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart @@ -1,11 +1,12 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/zigzag_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import '../callbacks.dart'; -import '../indicator_config.dart'; import '../indicator_item.dart'; import 'zigzag_indicator_item.dart'; @@ -18,7 +19,8 @@ class ZigZagIndicatorConfig extends IndicatorConfig { const ZigZagIndicatorConfig({ this.distance = 10, this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.blue), - }) : super(); + String? title, + }) : super(title: title ?? ZigZagIndicatorConfig.name); /// Initializes from JSON. factory ZigZagIndicatorConfig.fromJson(Map json) => diff --git a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart index 21e81cd12..1ea550d6d 100644 --- a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart @@ -7,16 +7,19 @@ part of 'zigzag_indicator_config.dart'; // ************************************************************************** ZigZagIndicatorConfig _$ZigZagIndicatorConfigFromJson( - Map json) { - return ZigZagIndicatorConfig( - distance: (json['distance'] as num).toDouble(), - lineStyle: LineStyle.fromJson(json['lineStyle'] as Map), - ); -} + Map json) => + ZigZagIndicatorConfig( + distance: (json['distance'] as num?)?.toDouble() ?? 10, + lineStyle: json['lineStyle'] == null + ? const LineStyle(thickness: 0.9, color: Colors.blue) + : LineStyle.fromJson(json['lineStyle'] as Map), + title: json['title'] as String?, + ); Map _$ZigZagIndicatorConfigToJson( ZigZagIndicatorConfig instance) => { + 'title': instance.title, 'distance': instance.distance, 'lineStyle': instance.lineStyle, }; diff --git a/lib/src/add_ons/repository.dart b/lib/src/add_ons/repository.dart new file mode 100644 index 000000000..b34c1c9a5 --- /dev/null +++ b/lib/src/add_ons/repository.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +/// Holds indicators/drawing tools that were added to the Chart during runtime. +abstract class Repository extends ChangeNotifier { + /// Retrieves the list of items in a repository. + List get items; + + /// To adds a new indicator or drawing tool. + void add(T config); + + /// Edits an existing indicator or drawing tool at the specified [index]. + /// This method allows you to modify the settings and properties of the + /// indicator or tool + /// without changing its position in the list. + void editAt(int index); + + /// Updates an existing indicator or drawing tool at the + /// specified [index] with new configuration settings. + /// This method allows you to modify the indicator or tool's properties + /// while preserving its position in the list. + void updateAt(int index, T config); + + /// Removes indicator or drawing tool at [index]. + void removeAt(int index); + + /// Swaps two elements of a list. + void swap(int index1, int index2); + + /// Clears all indicator and drawing tools + void clear(); +} diff --git a/lib/src/deriv_chart/chart/basic_chart.dart b/lib/src/deriv_chart/chart/basic_chart.dart index 9977a786e..14e417f3a 100644 --- a/lib/src/deriv_chart/chart/basic_chart.dart +++ b/lib/src/deriv_chart/chart/basic_chart.dart @@ -1,19 +1,24 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/custom_painters/chart_data_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/gestures/gesture_manager.dart'; import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; import 'package:deriv_chart/src/deriv_chart/chart/y_axis/y_grid_label_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/y_axis/y_grid_line_painter.dart'; +import 'package:deriv_chart/src/models/chart_axis_config.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../../misc/callbacks.dart'; +import 'data_visualization/chart_series/series.dart'; import 'data_visualization/models/animation_info.dart'; import 'helpers/functions/conversion.dart'; import 'helpers/functions/helper_functions.dart'; import 'multiple_animated_builder.dart'; import 'y_axis/quote_grid.dart'; +const Duration _defaultDuration = Duration(milliseconds: 300); + /// The basic chart that other charts extend from. class BasicChart extends StatefulWidget { ///Initializes a basic chart. @@ -23,6 +28,9 @@ class BasicChart extends StatefulWidget { this.opacity = 1, ChartAxisConfig? chartAxisConfig, Key? key, + this.onQuoteAreaChanged, + this.currentTickAnimationDuration = _defaultDuration, + this.quoteBoundsAnimationDuration = _defaultDuration, }) : chartAxisConfig = chartAxisConfig ?? const ChartAxisConfig(), super(key: key); @@ -38,6 +46,15 @@ class BasicChart extends StatefulWidget { /// The axis configuration of the chart. final ChartAxisConfig chartAxisConfig; + /// Callback provided by library user. + final VisibleQuoteAreaChangedCallback? onQuoteAreaChanged; + + /// Duration of the current tick animated transition. + final Duration currentTickAnimationDuration; + + /// Duration of quote bounds animated transition. + final Duration quoteBoundsAnimationDuration; + @override BasicChartState createState() => BasicChartState(); } @@ -68,10 +85,6 @@ class BasicChartState extends State /// Padding should be at least half of barrier label height. static const double minPadding = 10; - /// Duration of quote bounds animated transition. - final Duration quoteBoundsAnimationDuration = - const Duration(milliseconds: 300); - /// Top quote bound target for animated transition. double topBoundQuoteTarget = 60; @@ -150,6 +163,16 @@ class BasicChartState extends State widget.mainSeries.didUpdate(oldChart.mainSeries); } _playNewTickAnimation(); + + if (widget.currentTickAnimationDuration.inMilliseconds != + oldChart.currentTickAnimationDuration.inMilliseconds) { + _setupCurrentTickAnimation(); + } + + if (widget.quoteBoundsAnimationDuration.inMilliseconds != + oldChart.quoteBoundsAnimationDuration.inMilliseconds) { + _setupBoundsAnimation(); + } } @override @@ -164,8 +187,22 @@ class BasicChartState extends State /// Call function to calculate the grid line quotes and put them inside /// [yAxisModel]. - List calculateGridLineQuotes(YAxisModel yAxisModel) => - gridLineQuotes = yAxisModel.gridQuotes(); + List calculateGridLineQuotes(YAxisModel yAxisModel) { + final List newGridLineQuotes = yAxisModel.gridQuotes(); + + if (newGridLineQuotes.isNotEmpty && + (gridLineQuotes == null || + gridLineQuotes!.isEmpty || + newGridLineQuotes.first != gridLineQuotes!.first || + newGridLineQuotes.last != gridLineQuotes!.last)) { + widget.onQuoteAreaChanged + ?.call(newGridLineQuotes.first, newGridLineQuotes.last); + } + + gridLineQuotes = newGridLineQuotes; + + return gridLineQuotes!; + } void _playNewTickAnimation() { _currentTickAnimationController @@ -192,7 +229,7 @@ class BasicChartState extends State void _setupCurrentTickAnimation() { _currentTickAnimationController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 300), + duration: widget.currentTickAnimationDuration, ); currentTickAnimation = CurvedAnimation( parent: _currentTickAnimationController, @@ -204,13 +241,25 @@ class BasicChartState extends State topBoundQuoteAnimationController = AnimationController.unbounded( value: topBoundQuoteTarget, vsync: this, - duration: quoteBoundsAnimationDuration, + duration: widget.quoteBoundsAnimationDuration, ); bottomBoundQuoteAnimationController = AnimationController.unbounded( value: bottomBoundQuoteTarget, vsync: this, - duration: quoteBoundsAnimationDuration, + duration: widget.quoteBoundsAnimationDuration, ); + + /// Builds the widget once the animation is finished + /// so that the y-axis is correctly filled. + topBoundQuoteAnimationController.addListener(_quoteAnimationListener); + bottomBoundQuoteAnimationController.addListener(_quoteAnimationListener); + } + + void _quoteAnimationListener() { + if (topBoundQuoteAnimationController.isCompleted && + bottomBoundQuoteAnimationController.isCompleted) { + setState(() {}); + } } void _clearGestures() { @@ -266,6 +315,16 @@ class BasicChartState extends State bottomPadding: _bottomPadding, ); + /// Returns quote based on the y-coordinate. + double chartQuoteFromCanvasY(double y) => quoteFromCanvasY( + y: y, + topBoundQuote: _topBoundQuote, + bottomBoundQuote: _bottomBoundQuote, + canvasHeight: canvasSize?.height ?? 200, + topPadding: _topPadding, + bottomPadding: _bottomPadding, + ); + @override Widget build(BuildContext context) => LayoutBuilder( key: _key, @@ -277,10 +336,11 @@ class BasicChartState extends State constraints.maxHeight, ); - final YAxisModel yAxisModel = _setupYAxisModel(canvasSize!); - updateVisibleData(); _updateQuoteBoundTargets(); + + final YAxisModel yAxisModel = _setupYAxisModel(canvasSize!); + final List gridLineQuotes = calculateGridLineQuotes(yAxisModel); return Stack( @@ -405,6 +465,16 @@ class BasicChartState extends State verticalPaddingFraction = ((verticalPadding + dy) / canvasSize!.height).clamp(0.05, 0.49); }); + _onScaleYAxis(); + } + + void _onScaleYAxis() { + if (gridLineQuotes != null && gridLineQuotes!.isNotEmpty) { + widget.onQuoteAreaChanged?.call( + gridLineQuotes!.first, + gridLineQuotes!.last, + ); + } } void _setupInitialBounds() { diff --git a/lib/src/deriv_chart/chart/bottom_chart.dart b/lib/src/deriv_chart/chart/bottom_chart.dart index f56578339..a5c02c3a8 100644 --- a/lib/src/deriv_chart/chart/bottom_chart.dart +++ b/lib/src/deriv_chart/chart/bottom_chart.dart @@ -1,62 +1,216 @@ -import 'package:deriv_chart/deriv_chart.dart'; -import 'package:deriv_chart/src/theme/chart_default_theme.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/widgets/bottom_indicator_title.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/crosshair/crosshair_area_web.dart'; +import 'package:provider/provider.dart'; import 'basic_chart.dart'; -import 'y_axis/quote_grid.dart'; + +/// Called when the indicator is moved up/down +/// +/// [offset] is the displacement between the swap positions. +typedef SwapCallback = Function(int offset); /// The chart to add the bottom indicators too. class BottomChart extends BasicChart { /// Initializes a bottom chart. const BottomChart({ required Series series, + required this.granularity, + required this.title, int pipSize = 4, Key? key, + this.onRemove, + this.onEdit, + this.onExpandToggle, + this.onCrosshairDisappeared, + this.onCrosshairHover, + this.onSwap, + this.isExpanded = false, + this.showCrosshair = true, + this.showExpandedIcon = false, + this.showMoveUpIcon = false, + this.showMoveDownIcon = false, + this.bottomChartTitleMargin, }) : super(key: key, mainSeries: series, pipSize: pipSize); + /// For candles: Duration of one candle in ms. + /// For ticks: Average ms difference between two consecutive ticks. + final int granularity; + + /// Called when an indicator is to be removed. + final VoidCallback? onRemove; + + /// Called when an indicator is to be edited. + final VoidCallback? onEdit; + + /// Called when an indicator is to be expanded. + final VoidCallback? onExpandToggle; + + /// Called when an indicator is to moved up/down. + final SwapCallback? onSwap; + + /// Called when the crosshair is dismissed. + final VoidCallback? onCrosshairDisappeared; + + /// Called when the crosshair cursor is hovered/moved. + final OnCrosshairHover? onCrosshairHover; + + /// Whether the indicator is expanded or not. + final bool isExpanded; + + /// Whether the crosshair should be shown or not. + final bool showCrosshair; + + /// The title of the bottom chart. + final String title; + + /// Whether the expanded icon should be shown or not. + final bool showExpandedIcon; + + /// Whether the move up icon should be shown or not. + final bool showMoveUpIcon; + + /// Whether the move down icon should be shown or not. + final bool showMoveDownIcon; + + /// Specifies the margin to prevent overlap. + final EdgeInsets? bottomChartTitleMargin; + @override _BottomChartState createState() => _BottomChartState(); } class _BottomChartState extends BasicChartState { - @override - List calculateGridLineQuotes(YAxisModel yAxisModel) => - gridLineQuotes = const []; + ChartTheme get theme => context.read(); - @override - Widget build(BuildContext context) { - final ChartDefaultTheme theme = - Theme.of(context).brightness == Brightness.dark - ? ChartDefaultDarkTheme() - : ChartDefaultLightTheme(); - return ClipRect( - child: Stack( - children: [ - Column( - children: [ - Divider( - height: 0.5, - thickness: 1, - color: theme.brandGreenishColor, - ), - Expanded(child: super.build(context)), - ], + Widget _buildBottomChartOptions(BuildContext context) { + Widget _buildIcon({ + required IconData iconData, + void Function()? onPressed, + }) => + Material( + type: MaterialType.circle, + color: Colors.transparent, + clipBehavior: Clip.antiAlias, + child: IconButton( + icon: Icon( + iconData, + size: 16, + color: theme.base01Color, + ), + onPressed: onPressed, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), ), - Positioned( - top: 15, - left: 10, - child: Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: theme.base01Color.withOpacity(0.1), - borderRadius: BorderRadius.circular(2), + ); + + Widget _buildIcons() => Row( + children: [ + if (widget.showMoveUpIcon) + _buildIcon( + iconData: Icons.arrow_upward, + onPressed: () { + widget.onSwap?.call(-1); + }, ), - child: Text( - widget.mainSeries.runtimeType.toString(), + if (widget.showMoveDownIcon) + _buildIcon( + iconData: Icons.arrow_downward, + onPressed: () { + widget.onSwap?.call(1); + }, ), + if (widget.showExpandedIcon) + _buildIcon( + iconData: widget.isExpanded + ? Icons.fullscreen_exit + : Icons.fullscreen, + onPressed: () { + widget.onExpandToggle?.call(); + }, + ), + _buildIcon( + iconData: Icons.settings, + onPressed: () { + widget.onEdit?.call(); + }, ), - ), - ], + _buildIcon( + iconData: Icons.delete, + onPressed: () { + widget.onRemove?.call(); + }, + ), + ], + ); + + return Positioned( + top: 15, + left: widget.bottomChartTitleMargin?.left ?? 10, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: theme.base07Color, + borderRadius: BorderRadius.circular(2), + ), + child: Row( + children: [ + BottomIndicatorTitle( + widget.title, + theme.textStyle( + color: theme.base01Color, + textStyle: + const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + _buildIcons(), + ], + ), + ), + ); + } + + Widget _buildCrosshairAreaWeb() => CrosshairAreaWeb( + mainSeries: widget.mainSeries, + epochFromCanvasX: xAxis.epochFromX, + quoteFromCanvasY: chartQuoteFromCanvasY, + epochToCanvasX: xAxis.xFromEpoch, + quoteToCanvasY: chartQuoteToCanvasY, + quoteLabelsTouchAreaWidth: quoteLabelsTouchAreaWidth, + showCrosshairCursor: widget.showCrosshair, + onCrosshairDisappeared: widget.onCrosshairDisappeared, + onCrosshairHover: widget.onCrosshairHover, + ); + + @override + Widget build(BuildContext context) { + final ChartConfig chartConfig = ChartConfig( + pipSize: widget.pipSize, + granularity: widget.granularity, + ); + + return Provider.value( + value: chartConfig, + child: ClipRect( + child: Stack( + children: [ + Column( + children: [ + Divider( + height: 0.5, + thickness: 1, + color: theme.base01Color, + ), + Expanded(child: super.build(context)), + ], + ), + if (kIsWeb) _buildCrosshairAreaWeb(), + _buildBottomChartOptions(context) + ], + ), ), ); } diff --git a/lib/src/deriv_chart/chart/chart.dart b/lib/src/deriv_chart/chart/chart.dart index 57f10da6c..60eb55bfe 100644 --- a/lib/src/deriv_chart/chart/chart.dart +++ b/lib/src/deriv_chart/chart/chart.dart @@ -1,35 +1,70 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:collection/collection.dart'; import 'package:deriv_chart/src/deriv_chart/chart/gestures/gesture_manager.dart'; import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis.dart'; +import 'package:deriv_chart/src/deriv_chart/drawing_tool_chart/drawing_tools.dart'; import 'package:deriv_chart/src/misc/callbacks.dart'; +import 'package:deriv_chart/src/models/chart_axis_config.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/chart_default_light_theme.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; - +import '../../add_ons/indicators_ui/indicator_config.dart'; +import '../../add_ons/repository.dart'; +import '../../misc/chart_controller.dart'; +import '../../models/tick.dart'; +import '../../theme/chart_default_dark_theme.dart'; +import '../../theme/chart_theme.dart'; import 'bottom_chart.dart'; +import 'data_visualization/annotations/chart_annotation.dart'; import 'data_visualization/chart_data.dart'; +import 'data_visualization/chart_series/data_series.dart'; +import 'data_visualization/chart_series/series.dart'; +import 'data_visualization/markers/marker_series.dart'; +import 'data_visualization/models/chart_object.dart'; import 'main_chart.dart'; +const Duration _defaultDuration = Duration(milliseconds: 300); + /// Interactive chart widget. class Chart extends StatefulWidget { /// Creates chart that expands to available space. const Chart({ required this.mainSeries, required this.granularity, + required this.drawingTools, this.pipSize = 4, this.controller, - this.overlaySeries, - this.bottomSeries, + this.overlayConfigs, + this.bottomConfigs, this.markerSeries, this.theme, this.onCrosshairAppeared, + this.onCrosshairDisappeared, + this.onCrosshairHover, this.onVisibleAreaChanged, + this.onQuoteAreaChanged, this.isLive = false, this.dataFitEnabled = false, this.opacity = 1.0, this.annotations, this.chartAxisConfig = const ChartAxisConfig(), + this.showCrosshair = false, + this.indicatorsRepo, + this.maxCurrentTickOffset, + this.msPerPx, + this.minIntervalWidth, + this.maxIntervalWidth, + this.minElapsedTimeToFollow = 0, + this.currentTickAnimationDuration, + this.quoteBoundsAnimationDuration, + this.showCurrentTickBlinkAnimation, + this.verticalPaddingFraction, + this.bottomChartTitleMargin, + this.showDataFitButton, + this.showScrollToLastTickButton, + this.loadingAnimationColor, Key? key, }) : super(key: key); @@ -37,15 +72,19 @@ class Chart extends StatefulWidget { final DataSeries mainSeries; /// List of overlay indicator series to add on chart beside the [mainSeries]. - final List? overlaySeries; + final List? overlayConfigs; /// List of bottom indicator series to add on chart separate from the /// [mainSeries]. - final List? bottomSeries; + final List? bottomConfigs; /// Open position marker series. final MarkerSeries? markerSeries; + /// Keep the reference to the drawing tools class for + /// sharing data between the DerivChart and the DrawingToolsDialog + final DrawingTools drawingTools; + /// Chart's controller final ChartController? controller; @@ -59,9 +98,18 @@ class Chart extends StatefulWidget { /// Called when crosshair details appear after long press. final VoidCallback? onCrosshairAppeared; + /// Called when the crosshair is dismissed. + final VoidCallback? onCrosshairDisappeared; + + /// Called when the crosshair cursor is hovered/moved. + final OnCrosshairHoverCallback? onCrosshairHover; + /// Called when chart is scrolled or zoomed. final VisibleAreaChangedCallback? onVisibleAreaChanged; + /// Callback provided by library user. + final VisibleQuoteAreaChangedCallback? onQuoteAreaChanged; + /// Chart's theme. final ChartTheme? theme; @@ -83,6 +131,56 @@ class Chart extends StatefulWidget { /// Configurations for chart's axes. final ChartAxisConfig chartAxisConfig; + /// Whether the crosshair should be shown or not. + final bool showCrosshair; + + /// Max distance between rightBoundEpoch and nowEpoch in pixels. + final double? maxCurrentTickOffset; + + /// Specifies the zoom level of the chart. + final double? msPerPx; + + /// Specifies the minimum interval width + /// that is used for calculating the maximum msPerPx. + final double? minIntervalWidth; + + /// Specifies the maximum interval width + /// that is used for calculating the maximum msPerPx. + final double? maxIntervalWidth; + + /// Specifies the minimum time in milliseconds before which it can update the + /// rightBoundEpoch when the chart is in follow mode. This is used to control + /// the number of frames painted each second. + final int minElapsedTimeToFollow; + + /// Duration of the current tick animated transition. + final Duration? currentTickAnimationDuration; + + /// Duration of quote bounds animated transition. + final Duration? quoteBoundsAnimationDuration; + + /// Whether to show current tick blink animation or not. + final bool? showCurrentTickBlinkAnimation; + + /// Fraction of the chart's height taken by top or bottom padding. + /// Quote scaling (drag on quote area) is controlled by this variable. + final double? verticalPaddingFraction; + + /// Specifies the margin to prevent overlap. + final EdgeInsets? bottomChartTitleMargin; + + /// Whether the data fit button is shown or not. + final bool? showDataFitButton; + + /// Whether to show the scroll to last tick button or not. + final bool? showScrollToLastTickButton; + + /// The color of the loading animation. + final Color? loadingAnimationColor; + + /// Chart's indicators + final Repository? indicatorsRepo; + @override State createState() => _ChartState(); } @@ -92,6 +190,9 @@ class _ChartState extends State with WidgetsBindingObserver { bool? _followCurrentTick; late ChartController _controller; late ChartTheme _chartTheme; + late List? bottomSeries; + late List? overlaySeries; + int? expandedIndex; @override void initState() { @@ -110,6 +211,21 @@ class _ChartState extends State with WidgetsBindingObserver { _controller = widget.controller ?? ChartController(); } + List? _getIndicatorSeries(List? configs) { + if (configs == null) { + return null; + } + + return configs + .map((IndicatorConfig indicatorConfig) => indicatorConfig.getSeries( + IndicatorInput( + widget.mainSeries.input, + widget.granularity, + ), + )) + .toList(); + } + void _initChartTheme() { _chartTheme = widget.theme ?? (Theme.of(context).brightness == Brightness.dark @@ -117,6 +233,25 @@ class _ChartState extends State with WidgetsBindingObserver { : ChartDefaultLightTheme()); } + void _onCrosshairHover( + Offset globalPosition, + Offset localPosition, + EpochToX epochToX, + QuoteToY quoteToY, + EpochFromX epochFromX, + QuoteFromY quoteFromY, + ) { + widget.onCrosshairHover?.call( + globalPosition, + localPosition, + epochToX, + quoteToY, + epochFromX, + quoteFromY, + null, + ); + } + @override Widget build(BuildContext context) { final ChartConfig chartConfig = ChartConfig( @@ -125,13 +260,31 @@ class _ChartState extends State with WidgetsBindingObserver { chartAxisConfig: widget.chartAxisConfig, ); + final List? overlaySeries = + _getIndicatorSeries(widget.overlayConfigs); + + final List? bottomSeries = + _getIndicatorSeries(widget.bottomConfigs); + final List chartDataList = [ widget.mainSeries, - if (widget.overlaySeries != null) ...widget.overlaySeries!, - if (widget.bottomSeries != null) ...widget.bottomSeries!, + if (overlaySeries != null) ...overlaySeries, + if (bottomSeries != null) ...bottomSeries, if (widget.annotations != null) ...widget.annotations!, ]; + _controller + ..getSeriesList = (() => [ + if (overlaySeries != null) ...overlaySeries, + if (bottomSeries != null) ...bottomSeries, + ]) + ..getConfigsList = (() => [ + if (widget.overlayConfigs != null) ...?widget.overlayConfigs, + if (widget.bottomConfigs != null) ...?widget.bottomConfigs, + ]); + + final bool isExpanded = expandedIndex != null; + return MultiProvider( providers: [ Provider.value(value: _chartTheme), @@ -148,34 +301,103 @@ class _ChartState extends State with WidgetsBindingObserver { onVisibleAreaChanged: _onVisibleAreaChanged, isLive: widget.isLive, startWithDataFitMode: widget.dataFitEnabled, + maxCurrentTickOffset: widget.maxCurrentTickOffset, + msPerPx: widget.msPerPx, + minIntervalWidth: widget.minIntervalWidth, + maxIntervalWidth: widget.maxIntervalWidth, + minElapsedTimeToFollow: widget.minElapsedTimeToFollow, child: Column( children: [ Expanded( flex: 3, child: MainChart( + drawingTools: widget.drawingTools, controller: _controller, mainSeries: widget.mainSeries, - overlaySeries: widget.overlaySeries, + overlaySeries: overlaySeries, annotations: widget.annotations, markerSeries: widget.markerSeries, pipSize: widget.pipSize, onCrosshairAppeared: widget.onCrosshairAppeared, + onQuoteAreaChanged: widget.onQuoteAreaChanged, isLive: widget.isLive, showLoadingAnimationForHistoricalData: !widget.dataFitEnabled, - showDataFitButton: widget.dataFitEnabled, + showDataFitButton: + widget.showDataFitButton ?? widget.dataFitEnabled, + showScrollToLastTickButton: + widget.showScrollToLastTickButton ?? true, opacity: widget.opacity, chartAxisConfig: widget.chartAxisConfig, + verticalPaddingFraction: widget.verticalPaddingFraction, + showCrosshair: widget.showCrosshair, + onCrosshairDisappeared: widget.onCrosshairDisappeared, + onCrosshairHover: _onCrosshairHover, + loadingAnimationColor: widget.loadingAnimationColor, + currentTickAnimationDuration: + widget.currentTickAnimationDuration ?? _defaultDuration, + quoteBoundsAnimationDuration: + widget.quoteBoundsAnimationDuration ?? _defaultDuration, + showCurrentTickBlinkAnimation: + widget.showCurrentTickBlinkAnimation ?? true, ), ), - if (widget.bottomSeries?.isNotEmpty ?? false) - ...widget.bottomSeries! - .map((Series series) => Expanded( - child: BottomChart( - series: series, - pipSize: widget.pipSize, - ))) - .toList() + if (bottomSeries?.isNotEmpty ?? false) + ...bottomSeries!.mapIndexed((int index, Series series) { + if (isExpanded && expandedIndex != index) { + return const SizedBox.shrink(); + } + + return Expanded( + flex: isExpanded ? bottomSeries.length : 1, + child: BottomChart( + series: series, + granularity: widget.granularity, + pipSize: widget.bottomConfigs?[index].pipSize ?? + widget.pipSize, + title: widget.bottomConfigs![index].title, + bottomChartTitleMargin: widget.bottomChartTitleMargin, + onRemove: () => _onRemove(widget.bottomConfigs![index]), + onEdit: () => _onEdit(widget.bottomConfigs![index]), + onExpandToggle: () { + setState(() { + expandedIndex = + expandedIndex != index ? index : null; + }); + }, + onSwap: (int offset) => _onSwap( + widget.bottomConfigs![index], + widget.bottomConfigs![index + offset]), + onCrosshairDisappeared: widget.onCrosshairDisappeared, + onCrosshairHover: ( + Offset globalPosition, + Offset localPosition, + EpochToX epochToX, + QuoteToY quoteToY, + EpochFromX epochFromX, + QuoteFromY quoteFromY, + ) => + widget.onCrosshairHover?.call( + globalPosition, + localPosition, + epochToX, + quoteToY, + epochFromX, + quoteFromY, + widget.bottomConfigs![index], + ), + isExpanded: isExpanded, + showCrosshair: widget.showCrosshair, + showExpandedIcon: bottomSeries.length > 1, + showMoveUpIcon: !isExpanded && + bottomSeries.length > 1 && + index != 0, + showMoveDownIcon: !isExpanded && + bottomSeries.length > 1 && + index != bottomSeries.length - 1, + ), + ); + }).toList() ], ), ), @@ -184,6 +406,30 @@ class _ChartState extends State with WidgetsBindingObserver { ); } + void _onEdit(IndicatorConfig config) { + if (widget.indicatorsRepo != null) { + final int index = widget.indicatorsRepo!.items.indexOf(config); + widget.indicatorsRepo!.editAt(index); + } + } + + void _onRemove(IndicatorConfig config) { + expandedIndex = null; + + if (widget.indicatorsRepo != null) { + final int index = widget.indicatorsRepo!.items.indexOf(config); + widget.indicatorsRepo!.removeAt(index); + } + } + + void _onSwap(IndicatorConfig config1, IndicatorConfig config2) { + if (widget.indicatorsRepo != null) { + final int index1 = widget.indicatorsRepo!.items.indexOf(config1); + final int index2 = widget.indicatorsRepo!.items.indexOf(config2); + widget.indicatorsRepo!.swap(index1, index2); + } + } + void _onVisibleAreaChanged(int leftBoundEpoch, int rightBoundEpoch) { widget.onVisibleAreaChanged?.call(leftBoundEpoch, rightBoundEpoch); @@ -239,5 +485,16 @@ class _ChartState extends State with WidgetsBindingObserver { _controller.onScrollToLastTick?.call(animate: false); } } + + // Check if the the expanded bottom indicator is moved/removed. + if (expandedIndex != null && + oldWidget.bottomConfigs?.length != widget.bottomConfigs?.length && + expandedIndex! < (oldWidget.bottomConfigs?.length ?? 0)) { + final int? newIndex = widget.bottomConfigs + ?.indexOf(oldWidget.bottomConfigs![expandedIndex!]); + if (newIndex != expandedIndex) { + expandedIndex = newIndex == -1 ? null : newIndex; + } + } } } diff --git a/lib/src/deriv_chart/chart/crosshair/crosshair_area.dart b/lib/src/deriv_chart/chart/crosshair/crosshair_area.dart index 3cde33e25..9bb36c87c 100644 --- a/lib/src/deriv_chart/chart/crosshair/crosshair_area.dart +++ b/lib/src/deriv_chart/chart/crosshair/crosshair_area.dart @@ -35,7 +35,7 @@ class CrosshairArea extends StatefulWidget { /// Called on longpress to show candle/point details. final VoidCallback? onCrosshairAppeared; - /// Called when candle or point is dismissed. + /// Called when the crosshair is dismissed. final VoidCallback? onCrosshairDisappeared; @override diff --git a/lib/src/deriv_chart/chart/crosshair/crosshair_area_web.dart b/lib/src/deriv_chart/chart/crosshair/crosshair_area_web.dart new file mode 100644 index 000000000..3f2c63825 --- /dev/null +++ b/lib/src/deriv_chart/chart/crosshair/crosshair_area_web.dart @@ -0,0 +1,102 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; +import 'package:deriv_chart/src/misc/callbacks.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; + +/// Place this area on top of the chart to display candle/point details on longpress. +class CrosshairAreaWeb extends StatefulWidget { + /// Initializes a widget to display candle/point details on longpress in a chart. + const CrosshairAreaWeb({ + required this.mainSeries, + required this.epochFromCanvasX, + required this.quoteFromCanvasY, + required this.epochToCanvasX, + required this.quoteToCanvasY, + this.quoteLabelsTouchAreaWidth = 70, + this.showCrosshairCursor = true, + this.pipSize = 4, + Key? key, + this.onCrosshairAppeared, + this.onCrosshairDisappeared, + this.onCrosshairHover, + }) : super(key: key); + + /// The main series of the chart. + final Series mainSeries; + + /// Number of decimal digits when showing prices. + final int pipSize; + + /// Width of the touch area for vertical zoom (on top of quote labels). + final double quoteLabelsTouchAreaWidth; + + /// Whether the crosshair cursor should be shown or not. + final bool showCrosshairCursor; + + /// Conversion function for converting chart's canvas' X position to epoch. + final EpochFromX epochFromCanvasX; + + /// Conversion function for converting chart's canvas' Y position to quote. + final QuoteFromY quoteFromCanvasY; + + /// Conversion function for converting epoch to chart's canvas' X position. + final EpochToX epochToCanvasX; + + /// Conversion function for converting quote to chart's canvas' Y position. + final QuoteToY quoteToCanvasY; + + /// Called on longpress to show candle/point details. + final VoidCallback? onCrosshairAppeared; + + /// Called when the crosshair is dismissed. + final VoidCallback? onCrosshairDisappeared; + + /// Called when the crosshair cursor is hovered/moved. + final OnCrosshairHover? onCrosshairHover; + + @override + _CrosshairAreaWebState createState() => _CrosshairAreaWebState(); +} + +class _CrosshairAreaWebState extends State { + XAxisModel get xAxis => context.read(); + + void _onCrosshairHover(Offset globalPosition, Offset localPosition) { + if (widget.onCrosshairHover == null) { + return; + } + + widget.onCrosshairHover?.call( + globalPosition, + localPosition, + widget.epochToCanvasX, + widget.quoteToCanvasY, + widget.epochFromCanvasX, + widget.quoteFromCanvasY, + ); + } + + @override + Widget build(BuildContext context) => Positioned.fill( + right: widget.quoteLabelsTouchAreaWidth, + child: Listener( + behavior: HitTestBehavior.translucent, + onPointerMove: (PointerMoveEvent ev) => + _onCrosshairHover(ev.position, ev.localPosition), + child: MouseRegion( + opaque: false, + cursor: widget.showCrosshairCursor + ? SystemMouseCursors.precise + : SystemMouseCursors.basic, + onExit: (PointerExitEvent ev) => + widget.onCrosshairDisappeared?.call(), + onHover: (PointerHoverEvent ev) => + _onCrosshairHover(ev.position, ev.localPosition), + child: const SizedBox.expand(), + ), + ), + ); +} diff --git a/lib/src/deriv_chart/chart/crosshair/find.dart b/lib/src/deriv_chart/chart/crosshair/find.dart index 3f02ce1c2..b5f84e4ce 100644 --- a/lib/src/deriv_chart/chart/crosshair/find.dart +++ b/lib/src/deriv_chart/chart/crosshair/find.dart @@ -39,6 +39,43 @@ int findClosestIndex(double index, List ticks) { } } +/// Binary search to find closest index to the [epoch]. +int findClosestIndexBinarySearch(int epoch, List? entries) { + if (entries == null || entries.isEmpty) { + return 0; + } + + int lo = 0; + int hi = entries.length - 1; + int localEpoch = epoch; + + if (localEpoch > entries[hi].epoch) { + localEpoch = entries[hi].epoch; + return hi; + } + + while (lo <= hi) { + final int mid = (hi + lo) ~/ 2; + // int getEpochOf(T t, int index) => t.epoch; + if (localEpoch < entries[mid].epoch) { + hi = mid - 1; + } else if (localEpoch > entries[mid].epoch) { + lo = mid + 1; + } else { + return mid; + } + } + + // Check if hi is less than 0 + if (hi < 0) { + return lo; // or another appropriate value to indicate the closest index + } + + return (entries[lo].epoch - localEpoch) < (localEpoch - entries[hi].epoch) + ? lo + : hi; +} + /// Returns index of the [epoch] location in [ticks]. /// /// E.g. `3` if [epoch] matches epoch of `ticks[3]`. diff --git a/lib/src/deriv_chart/chart/custom_painters/chart_data_painter.dart b/lib/src/deriv_chart/chart/custom_painters/chart_data_painter.dart index 261239528..0f275d38a 100644 --- a/lib/src/deriv_chart/chart/custom_painters/chart_data_painter.dart +++ b/lib/src/deriv_chart/chart/custom_painters/chart_data_painter.dart @@ -1,12 +1,15 @@ // ignore_for_file: unnecessary_null_comparison -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; -import 'package:deriv_chart/src/theme/painting_styles/chart_painting_style.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:flutter/material.dart'; +import '../data_visualization/chart_series/line_series/line_series.dart'; +import '../data_visualization/chart_series/ohlc_series/candle/candle_series.dart'; +import '../data_visualization/chart_series/series.dart'; + /// A `CustomPainter` which paints the chart data inside the chart. class ChartDataPainter extends BaseChartDataPainter { /// Initializes a `CustomPainter` which paints the chart data inside @@ -57,8 +60,7 @@ class ChartDataPainter extends BaseChartDataPainter { @override bool shouldRepaint(covariant ChartDataPainter oldDelegate) { bool styleChanged() => - (mainSeries is LineSeries && oldDelegate.mainSeries is CandleSeries) || - (mainSeries is CandleSeries && oldDelegate.mainSeries is LineSeries) || + (mainSeries.runtimeType != oldDelegate.mainSeries.runtimeType) || (mainSeries is LineSeries && theme.lineStyle != oldDelegate.theme.lineStyle) || (mainSeries is CandleSeries && @@ -68,8 +70,8 @@ class ChartDataPainter extends BaseChartDataPainter { mainSeries.shouldRepaint(oldDelegate.mainSeries); return super.shouldRepaint(oldDelegate) || - visibleAnimationChanged() || - styleChanged(); + styleChanged() || + visibleAnimationChanged(); } } @@ -149,15 +151,14 @@ class BaseChartDataPainter extends CustomPainter { return true; } - final Map oldStyles = - Map.fromIterable( + final Map oldSeries = Map.fromIterable( oldDelegate.series, key: (dynamic series) => series.id, - value: (dynamic series) => series.style, - ); - return series.any( - (Series series) => series.style != oldStyles[series.id], + value: (dynamic series) => series, ); + + return series + .any((Series series) => series.shouldRepaint(oldSeries[series.id])); } return rightBoundEpoch != oldDelegate.rightBoundEpoch || diff --git a/lib/src/deriv_chart/chart/custom_painters/loading_painter.dart b/lib/src/deriv_chart/chart/custom_painters/loading_painter.dart index 21ddb0ef9..bd25a7683 100644 --- a/lib/src/deriv_chart/chart/custom_painters/loading_painter.dart +++ b/lib/src/deriv_chart/chart/custom_painters/loading_painter.dart @@ -7,6 +7,7 @@ class LoadingPainter extends CustomPainter { LoadingPainter({ required this.loadingAnimationProgress, required this.loadingRightBoundX, + this.loadingAnimationColor, }); /// The progress shown in `double` for the loading. @@ -15,6 +16,9 @@ class LoadingPainter extends CustomPainter { /// The right bound of the loading area in X axis. final double loadingRightBoundX; + /// The color of the loading animation. + final Color? loadingAnimationColor; + @override void paint(Canvas canvas, Size size) { paintLoadingAnimation( @@ -22,6 +26,7 @@ class LoadingPainter extends CustomPainter { size: size, loadingAnimationProgress: loadingAnimationProgress, loadingRightBoundX: loadingRightBoundX, + color: loadingAnimationColor, ); } diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier.dart index 6d56dbbb9..3e0ed3879 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier.dart @@ -1,7 +1,10 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/barrier_objects.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import '../barrier.dart'; +import '../horizontal_barrier/horizontal_barrier.dart'; import 'accumulators_entry_spot_barrier_painter.dart'; /// [AccumulatorsEntrySpotBarrier] creates a dot and a dashed horizontal barrier diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier_painter.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier_painter.dart index 2e0a209e1..7aaca29a1 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier_painter.dart @@ -1,15 +1,18 @@ import 'dart:ui' as ui; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/barrier_objects.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_accumulators_entry_spot.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_line.dart'; import 'package:deriv_chart/src/theme/colors.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; import 'package:deriv_chart/src/theme/painting_styles/entry_spot_style.dart'; import 'package:flutter/material.dart'; +import 'accumulators_entry_spot_barrier.dart'; + /// A class for painting horizontal barrier with entry spot. class AccumulatorsEntrySpotBarrierPainter< T extends AccumulatorsEntrySpotBarrier> extends SeriesPainter { diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/candle_indicator_painter.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/candle_indicator_painter.dart index f4b5ce88c..27f2274ca 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/candle_indicator_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/candle_indicator_painter.dart @@ -1,15 +1,17 @@ import 'dart:math'; import 'dart:ui'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/barrier_objects.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/functions/helper_functions.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; import 'package:flutter/material.dart'; +import 'horizontal_barrier.dart'; import 'horizontal_barrier_painter.dart'; +import 'tick_indicator.dart'; /// A class for painting candle indicators. class CandleIndicatorPainter extends HorizontalBarrierPainter { diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier.dart index c76415992..832484037 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier.dart @@ -1,7 +1,9 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/barrier_objects.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import '../barrier.dart'; import 'horizontal_barrier_painter.dart'; /// Horizontal barrier class. diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier_painter.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier_painter.dart index a401cf800..901526286 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier_painter.dart @@ -1,15 +1,19 @@ import 'dart:ui' as ui; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/barrier_objects.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/create_shape_path.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_dot.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_line.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; import 'package:flutter/material.dart'; +import 'horizontal_barrier.dart'; +import 'tick_indicator.dart'; + /// A class for painting horizontal barriers. class HorizontalBarrierPainter extends SeriesPainter { @@ -54,6 +58,7 @@ class HorizontalBarrierPainter series.style as HorizontalBarrierStyle? ?? theme.horizontalBarrierStyle; _paint = Paint() + ..style = PaintingStyle.fill ..strokeWidth = 1 ..color = style.color; diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/tick_indicator.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/tick_indicator.dart index 520ed83bf..f0fabd09f 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/tick_indicator.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/tick_indicator.dart @@ -1,11 +1,15 @@ import 'dart:async'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; +import 'package:deriv_chart/src/models/candle.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; import 'package:flutter/material.dart'; import 'candle_indicator_painter.dart'; +import 'horizontal_barrier.dart'; import 'horizontal_barrier_painter.dart'; /// Tick indicator. diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_data.dart b/lib/src/deriv_chart/chart/data_visualization/chart_data.dart index 68ec6a319..e5459be7a 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_data.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_data.dart @@ -13,6 +13,12 @@ typedef EpochToX = double Function(int); /// Conversion function to convert value(quote) value to canvas Y. typedef QuoteToY = double Function(double); +/// Conversion function to convert canvas X to epoch value. +typedef EpochFromX = int Function(double); + +/// Conversion function to convert canvas Y to value(quote). +typedef QuoteFromY = double Function(double); + /// Any data that the chart takes and makes it paint its self on the chart's /// canvas including Line, CandleStick data, Markers, barriers etc.. abstract class ChartData { diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart index 5dc2a3bc2..cb7ff57d6 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart @@ -1,12 +1,16 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/functions/min_max_calculator.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; import 'package:deriv_chart/src/theme/painting_styles/data_series_style.dart'; import 'package:flutter/material.dart'; +import '../annotations/barriers/horizontal_barrier/horizontal_barrier.dart'; import '../chart_data.dart'; import 'indexed_entry.dart'; +import 'series.dart'; import 'visible_entries.dart'; /// Series with only a single list of data to paint. diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/adx_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/adx_series.dart index fdbc7439c..25ee5ebb9 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/adx_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/adx_series.dart @@ -1,15 +1,19 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/adx/adx_indicator_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_painters/bar_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; -import 'package:deriv_chart/src/theme/painting_styles/bar_style.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; import '../../chart_data.dart'; +import '../data_series.dart'; +import '../series.dart'; import '../series_painter.dart'; import 'models/adx_options.dart'; import 'single_indicator_series.dart'; @@ -24,12 +28,20 @@ class ADXSeries extends Series { String? id, }) : super(id ?? 'ADX$adxOptions'); - late SingleIndicatorSeries _adxSeries; - late SingleIndicatorSeries _positiveDISeries; - late SingleIndicatorSeries _negativeDISeries; - late SingleIndicatorSeries _adxHistogramSeries; + /// ADX series + late SingleIndicatorSeries adxSeries; - late List _adxSeriesList; + /// Positive DI series + late SingleIndicatorSeries positiveDISeries; + + /// Negative DI series + late SingleIndicatorSeries negativeDISeries; + + /// ADX histogram series + late SingleIndicatorSeries adxHistogramSeries; + + /// ADX Series List + late List adxSeriesList; /// List of [Tick]s to calculate ADX on. final IndicatorDataInput ticks; @@ -56,27 +68,35 @@ class ADXSeries extends Series { positiveDIIndicator, negativeDIIndicator, adxPeriod: adxOptions.smoothingPeriod, - ); + )..calculateValues(); - _positiveDISeries = SingleIndicatorSeries( + positiveDISeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => positiveDIIndicator, inputIndicator: positiveDIIndicator, options: adxOptions, - style: const LineStyle(color: Colors.green), + style: config.positiveLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.positiveLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _negativeDISeries = SingleIndicatorSeries( + negativeDISeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => negativeDIIndicator, inputIndicator: negativeDIIndicator, options: adxOptions, - style: const LineStyle(color: Colors.red), + style: config.negativeLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.negativeLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _adxHistogramSeries = SingleIndicatorSeries( + adxHistogramSeries = SingleIndicatorSeries( painterCreator: (Series series) => BarPainter( series as DataSeries, checkColorCallback: ({ @@ -88,10 +108,10 @@ class ADXSeries extends Series { indicatorCreator: () => adxHistogramIndicator, inputIndicator: adxHistogramIndicator, options: adxOptions, - style: const BarStyle(), + style: config.barStyle, ); - _adxSeries = SingleIndicatorSeries( + adxSeries = SingleIndicatorSeries( painterCreator: (Series series) => OscillatorLinePainter( series as DataSeries, secondaryHorizontalLines: [0], @@ -99,16 +119,34 @@ class ADXSeries extends Series { indicatorCreator: () => adxIndicator, inputIndicator: adxIndicator, options: adxOptions, - style: const LineStyle(color: Colors.white), + style: config.lineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.lineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _adxSeriesList = [ - _adxHistogramSeries, - _adxSeries, - _positiveDISeries, - _negativeDISeries, + adxSeriesList = [ + adxSeries, + positiveDISeries, + negativeDISeries, ]; + if (config.showHistogram) { + adxSeriesList.add(adxHistogramSeries); + } + + if (config.showShading) { + return ChannelFillPainter( + positiveDISeries, + negativeDISeries, + firstUpperChannelFillColor: + config.positiveLineStyle.color.withOpacity(0.2), + secondUpperChannelFillColor: + config.negativeLineStyle.color.withOpacity(0.2), + ); + } + return null; } @@ -117,12 +155,12 @@ class ADXSeries extends Series { final ADXSeries? series = oldData as ADXSeries?; final bool positiveDIUpdated = - _positiveDISeries.didUpdate(series?._positiveDISeries); + positiveDISeries.didUpdate(series?.positiveDISeries); final bool negativeDIUpdated = - _negativeDISeries.didUpdate(series?._negativeDISeries); - final bool adxUpdated = _adxSeries.didUpdate(series?._adxSeries); + negativeDISeries.didUpdate(series?.negativeDISeries); + final bool adxUpdated = adxSeries.didUpdate(series?.adxSeries); final bool histogramUpdated = - _adxHistogramSeries.didUpdate(series?._adxHistogramSeries); + adxHistogramSeries.didUpdate(series?.adxHistogramSeries); return positiveDIUpdated || negativeDIUpdated || @@ -132,14 +170,24 @@ class ADXSeries extends Series { @override void onUpdate(int leftEpoch, int rightEpoch) { - for (final SingleIndicatorSeries series in _adxSeriesList) { + for (final SingleIndicatorSeries series in adxSeriesList) { series.update(leftEpoch, rightEpoch); } } @override List recalculateMinMax() => - [_adxSeriesList.getMinValue(), _adxSeriesList.getMaxValue()]; + [adxSeriesList.getMinValue(), adxSeriesList.getMaxValue()]; + + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + final ADXSeries oldSeries = previous as ADXSeries; + return config.toJson().toString() != oldSeries.config.toJson().toString(); + } @override void paint( @@ -152,22 +200,27 @@ class ADXSeries extends Series { ChartTheme theme, ) { if (config.showSeries) { - _positiveDISeries.paint( + positiveDISeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _negativeDISeries.paint( + negativeDISeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _adxSeries.paint( + adxSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } if (config.showHistogram) { - _adxHistogramSeries.paint( + adxHistogramSeries.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + } + + if (config.showShading) { + super.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } } @override - int? getMaxEpoch() => _adxSeries.getMaxEpoch(); + int? getMaxEpoch() => adxSeries.getMaxEpoch(); @override - int? getMinEpoch() => _adxSeries.getMinEpoch(); + int? getMinEpoch() => adxSeries.getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/alligator_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/alligator_series.dart index bd51b0f39..4384affeb 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/alligator_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/alligator_series.dart @@ -1,5 +1,6 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; @@ -28,13 +29,9 @@ class AlligatorSeries extends Series { IndicatorInput indicatorInput, { required this.alligatorOptions, String? id, - this.jawOffset = 8, - this.teethOffset = 5, - this.lipsOffset = 3, }) : _fieldIndicator = HL2Indicator(indicatorInput), _indicatorInput = indicatorInput, - super(id ?? - 'Alligator$alligatorOptions$jawOffset$teethOffset$lipsOffset'); + super(id ?? 'Alligator$alligatorOptions'); final Indicator _fieldIndicator; final IndicatorInput _indicatorInput; @@ -42,37 +39,40 @@ class AlligatorSeries extends Series { /// Alligator options AlligatorOptions alligatorOptions; - SingleIndicatorSeries? _jawSeries; - SingleIndicatorSeries? _teethSeries; - SingleIndicatorSeries? _lipsSeries; + /// Jaw series + SingleIndicatorSeries? jawSeries; - SingleIndicatorSeries? _bullishSeries; - SingleIndicatorSeries? _bearishSeries; + /// Teeth series + SingleIndicatorSeries? teethSeries; - /// Shift to future in jaw series - final int jawOffset; + /// Lips series + SingleIndicatorSeries? lipsSeries; - /// Shift to future in teeth series - final int teethOffset; + /// Bullish Series + SingleIndicatorSeries? bullishSeries; - /// Shift to future in lips series - final int lipsOffset; + /// Bearish Series + SingleIndicatorSeries? bearishSeries; @override SeriesPainter? createPainter() { if (alligatorOptions.showLines) { - _jawSeries = SingleIndicatorSeries( + jawSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => MMAIndicator(_fieldIndicator, alligatorOptions.jawPeriod), inputIndicator: _fieldIndicator, options: alligatorOptions, - style: const LineStyle(color: Colors.blue), - offset: jawOffset, + style: alligatorOptions.jawLineStyle, + offset: alligatorOptions.jawOffset, + lastTickIndicatorStyle: getLastIndicatorStyle( + alligatorOptions.jawLineStyle.color, + showLastIndicator: alligatorOptions.showLastIndicator, + ), ); - _teethSeries = SingleIndicatorSeries( + teethSeries = SingleIndicatorSeries( painterCreator: ( Series series, ) => @@ -83,11 +83,15 @@ class AlligatorSeries extends Series { ), inputIndicator: _fieldIndicator, options: alligatorOptions, - style: const LineStyle(color: Colors.red), - offset: teethOffset, + style: alligatorOptions.teethLineStyle, + offset: alligatorOptions.teethOffset, + lastTickIndicatorStyle: getLastIndicatorStyle( + alligatorOptions.teethLineStyle.color, + showLastIndicator: alligatorOptions.showLastIndicator, + ), ); - _lipsSeries = SingleIndicatorSeries( + lipsSeries = SingleIndicatorSeries( painterCreator: ( Series series, ) => @@ -96,13 +100,17 @@ class AlligatorSeries extends Series { MMAIndicator(_fieldIndicator, alligatorOptions.lipsPeriod), inputIndicator: _fieldIndicator, options: alligatorOptions, - style: const LineStyle(color: Colors.green), - offset: lipsOffset, + style: alligatorOptions.lipsLineStyle, + offset: alligatorOptions.lipsOffset, + lastTickIndicatorStyle: getLastIndicatorStyle( + alligatorOptions.lipsLineStyle.color, + showLastIndicator: alligatorOptions.showLastIndicator, + ), ); } if (alligatorOptions.showFractal) { - _bearishSeries = SingleIndicatorSeries( + bearishSeries = SingleIndicatorSeries( painterCreator: ( Series series, ) => @@ -112,7 +120,7 @@ class AlligatorSeries extends Series { options: alligatorOptions, style: const LineStyle(color: Colors.redAccent), ); - _bullishSeries = SingleIndicatorSeries( + bullishSeries = SingleIndicatorSeries( painterCreator: ( Series series, ) => @@ -131,16 +139,16 @@ class AlligatorSeries extends Series { bool didUpdate(ChartData? oldData) { final AlligatorSeries? series = oldData as AlligatorSeries?; - final bool _jawUpdated = _jawSeries?.didUpdate(series?._jawSeries) ?? false; + final bool _jawUpdated = jawSeries?.didUpdate(series?.jawSeries) ?? false; final bool _teethUpdated = - _teethSeries?.didUpdate(series?._teethSeries) ?? false; + teethSeries?.didUpdate(series?.teethSeries) ?? false; final bool _lipsUpdated = - _lipsSeries?.didUpdate(series?._lipsSeries) ?? false; + lipsSeries?.didUpdate(series?.lipsSeries) ?? false; final bool _bearishUpdated = - _bearishSeries?.didUpdate(series?._bearishSeries) ?? false; + bearishSeries?.didUpdate(series?.bearishSeries) ?? false; final bool _bullishUpdated = - _bullishSeries?.didUpdate(series?._bullishSeries) ?? false; + bullishSeries?.didUpdate(series?.bullishSeries) ?? false; return _jawUpdated || _teethUpdated || @@ -151,27 +159,36 @@ class AlligatorSeries extends Series { @override void onUpdate(int leftEpoch, int rightEpoch) { - _jawSeries?.update(leftEpoch, rightEpoch); - _teethSeries?.update(leftEpoch, rightEpoch); - _lipsSeries?.update(leftEpoch, rightEpoch); - _bullishSeries?.update(leftEpoch, rightEpoch); - _bearishSeries?.update(leftEpoch, rightEpoch); + jawSeries?.update(leftEpoch, rightEpoch); + teethSeries?.update(leftEpoch, rightEpoch); + lipsSeries?.update(leftEpoch, rightEpoch); + bullishSeries?.update(leftEpoch, rightEpoch); + bearishSeries?.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ [ - _jawSeries, - _teethSeries, - _lipsSeries, + jawSeries, + teethSeries, + lipsSeries, ].getMinValue(), [ - _jawSeries, - _teethSeries, - _lipsSeries, + jawSeries, + teethSeries, + lipsSeries, ].getMaxValue() ]; + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + return alligatorOptions != (previous as AlligatorSeries).alligatorOptions; + } + @override void paint( Canvas canvas, @@ -182,23 +199,23 @@ class AlligatorSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _jawSeries?.paint( + jawSeries?.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _teethSeries?.paint( + teethSeries?.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _lipsSeries?.paint( + lipsSeries?.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _bearishSeries?.paint( + bearishSeries?.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _bullishSeries?.paint( + bullishSeries?.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } @override int? getMaxEpoch() => - [_jawSeries, _teethSeries, _lipsSeries].getMaxEpoch(); + [jawSeries, teethSeries, lipsSeries].getMaxEpoch(); @override int? getMinEpoch() => - [_jawSeries, _teethSeries, _lipsSeries].getMinEpoch(); + [jawSeries, teethSeries, lipsSeries].getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/aroon_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/aroon_series.dart index 60c3187c2..8d4d2005f 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/aroon_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/aroon_series.dart @@ -4,11 +4,11 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/single_indicator_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/chart_theme.dart'; -import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; @@ -32,25 +32,32 @@ class AroonSeries extends Series { /// Configs for `ArronIndicator` final AroonIndicatorConfig indicatorConfig; - late SingleIndicatorSeries _aroonUpSeries; - late SingleIndicatorSeries _aroonDownSeries; + /// Arron up series + late SingleIndicatorSeries aroonUpSeries; + + /// Arron down series + late SingleIndicatorSeries aroonDownSeries; /// options AroonOptions aroonOption; @override SeriesPainter? createPainter() { - _aroonUpSeries = SingleIndicatorSeries( + aroonUpSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => AroonUpIndicator.fromIndicator( HighValueIndicator(indicatorInput), period: indicatorConfig.period), inputIndicator: CloseValueIndicator(indicatorInput), - style: const LineStyle(color: Colors.green), + style: indicatorConfig.upLineStyle, options: aroonOption, + lastTickIndicatorStyle: getLastIndicatorStyle( + indicatorConfig.upLineStyle.color, + showLastIndicator: indicatorConfig.showLastIndicator, + ), ); - _aroonDownSeries = SingleIndicatorSeries( + aroonDownSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => AroonDownIndicator.fromIndicator( @@ -58,7 +65,11 @@ class AroonSeries extends Series { period: indicatorConfig.period), inputIndicator: CloseValueIndicator(indicatorInput), options: aroonOption, - style: const LineStyle(color: Colors.red), + style: indicatorConfig.downLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + indicatorConfig.downLineStyle.color, + showLastIndicator: indicatorConfig.showLastIndicator, + ), ); return null; @@ -67,28 +78,27 @@ class AroonSeries extends Series { @override bool didUpdate(ChartData? oldData) { final AroonSeries? series = oldData as AroonSeries?; - final bool _aroonUpUpdated = - _aroonUpSeries.didUpdate(series?._aroonUpSeries); + final bool _aroonUpUpdated = aroonUpSeries.didUpdate(series?.aroonUpSeries); final bool _aroonDownUpdated = - _aroonDownSeries.didUpdate(series?._aroonDownSeries); + aroonDownSeries.didUpdate(series?.aroonDownSeries); return _aroonUpUpdated || _aroonDownUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _aroonUpSeries.update(leftEpoch, rightEpoch); - _aroonDownSeries.update(leftEpoch, rightEpoch); + aroonUpSeries.update(leftEpoch, rightEpoch); + aroonDownSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ [ - _aroonUpSeries, - _aroonDownSeries, + aroonUpSeries, + aroonDownSeries, ].getMinValue(), [ - _aroonUpSeries, - _aroonDownSeries, + aroonUpSeries, + aroonDownSeries, ].getMaxValue() ]; @@ -102,21 +112,21 @@ class AroonSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _aroonDownSeries.paint( + aroonDownSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _aroonUpSeries.paint( + aroonUpSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } @override int? getMaxEpoch() => [ - _aroonDownSeries, - _aroonUpSeries, + aroonDownSeries, + aroonUpSeries, ].getMaxEpoch(); @override int? getMinEpoch() => [ - _aroonDownSeries, - _aroonUpSeries, + aroonDownSeries, + aroonUpSeries, ].getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/bollinger_bands_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/bollinger_bands_series.dart index eb8ecd488..d0ac3c3ea 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/bollinger_bands_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/bollinger_bands_series.dart @@ -1,6 +1,13 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/functions/helper_functions.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; @@ -8,11 +15,6 @@ import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; -import '../../chart_data.dart'; -import '../data_series.dart'; -import '../series.dart'; -import '../series_painter.dart'; -import 'ma_series.dart'; import 'models/bollinger_bands_options.dart'; import 'single_indicator_series.dart'; @@ -39,95 +41,132 @@ class BollingerBandSeries extends Series { }) : _fieldIndicator = indicator, super(id ?? 'Bollinger$bbOptions'); - late SingleIndicatorSeries _lowerSeries; - late SingleIndicatorSeries _middleSeries; - late SingleIndicatorSeries _upperSeries; + /// Lower series + late SingleIndicatorSeries lowerSeries; + + /// Middle series + late SingleIndicatorSeries middleSeries; + + /// Upper series + late SingleIndicatorSeries upperSeries; + + /// Inner Series + final List innerSeries = []; /// Bollinger bands options final BollingerBandsOptions bbOptions; + /// Field Indicator final Indicator _fieldIndicator; - final List _innerSeries = []; - @override SeriesPainter? createPainter() { final StandardDeviationIndicator standardDeviation = StandardDeviationIndicator(_fieldIndicator, bbOptions.period); final CachedIndicator bbmSMA = - MASeries.getMAIndicator(_fieldIndicator, bbOptions); + MASeries.getMAIndicator(_fieldIndicator, bbOptions)..calculateValues(); - _middleSeries = SingleIndicatorSeries( + middleSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => bbmSMA, inputIndicator: _fieldIndicator, options: bbOptions, + style: bbOptions.middleLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + bbOptions.middleLineStyle.color, + showLastIndicator: bbOptions.showLastIndicator, + ), + ); + + lowerSeries = SingleIndicatorSeries( + painterCreator: (Series series) => + LinePainter(series as DataSeries), + indicatorCreator: () => BollingerBandsLowerIndicator( + bbmSMA, + standardDeviation, + k: bbOptions.standardDeviationFactor, + ), + inputIndicator: _fieldIndicator, + options: bbOptions, + style: bbOptions.lowerLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + bbOptions.lowerLineStyle.color, + showLastIndicator: bbOptions.showLastIndicator, + ), + ); + + upperSeries = SingleIndicatorSeries( + painterCreator: (Series series) => + LinePainter(series as DataSeries), + indicatorCreator: () => BollingerBandsUpperIndicator( + bbmSMA, + standardDeviation, + k: bbOptions.standardDeviationFactor, + ), + inputIndicator: _fieldIndicator, + options: bbOptions, + style: bbOptions.upperLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + bbOptions.upperLineStyle.color, + showLastIndicator: bbOptions.showLastIndicator, + ), ); - _lowerSeries = SingleIndicatorSeries( - painterCreator: (Series series) => - LinePainter(series as DataSeries), - indicatorCreator: () => BollingerBandsLowerIndicator( - bbmSMA, - standardDeviation, - k: bbOptions.standardDeviationFactor, - ), - inputIndicator: _fieldIndicator, - options: bbOptions); - - _upperSeries = SingleIndicatorSeries( - painterCreator: (Series series) => - LinePainter(series as DataSeries), - indicatorCreator: () => BollingerBandsUpperIndicator( - bbmSMA, - standardDeviation, - k: bbOptions.standardDeviationFactor, - ), - inputIndicator: _fieldIndicator, - options: bbOptions); - - _innerSeries - ..add(_lowerSeries) - ..add(_middleSeries) - ..add(_upperSeries); - - // TODO(ramin): return the painter that paints Channel Fill between bands - return null; + innerSeries + ..add(lowerSeries) + ..add(middleSeries) + ..add(upperSeries); + + return ChannelFillPainter( + upperSeries, + lowerSeries, + firstUpperChannelFillColor: bbOptions.fillColor.withOpacity(0.2), + secondUpperChannelFillColor: bbOptions.fillColor.withOpacity(0.2), + ); } @override bool didUpdate(ChartData? oldData) { final BollingerBandSeries? series = oldData as BollingerBandSeries?; - final bool lowerUpdated = _lowerSeries.didUpdate(series?._lowerSeries); - final bool middleUpdated = _middleSeries.didUpdate(series?._middleSeries); - final bool upperUpdated = _upperSeries.didUpdate(series?._upperSeries); + final bool lowerUpdated = lowerSeries.didUpdate(series?.lowerSeries); + final bool middleUpdated = middleSeries.didUpdate(series?.middleSeries); + final bool upperUpdated = upperSeries.didUpdate(series?.upperSeries); return lowerUpdated || middleUpdated || upperUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _lowerSeries.update(leftEpoch, rightEpoch); - _middleSeries.update(leftEpoch, rightEpoch); - _upperSeries.update(leftEpoch, rightEpoch); + lowerSeries.update(leftEpoch, rightEpoch); + middleSeries.update(leftEpoch, rightEpoch); + upperSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => - // Can just use _lowerSeries minValue for min and _upperSeries maxValue + // Can just use lowerSeries minValue for min and upperSeries maxValue // for max. But to be safe we calculate min and max. from all three series [ - _innerSeries + innerSeries .map((Series series) => series.minValue) .reduce((double a, double b) => safeMin(a, b)), - _innerSeries + innerSeries .map((Series series) => series.maxValue) .reduce((double a, double b) => safeMax(a, b)), ]; + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + return bbOptions != (previous as BollingerBandSeries).bbOptions; + } + @override void paint( Canvas canvas, @@ -138,19 +177,24 @@ class BollingerBandSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _lowerSeries.paint( + lowerSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _middleSeries.paint( + middleSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _upperSeries.paint( + upperSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - // TODO(ramin): call super.paint to paint the Channels fill. + if (bbOptions.showChannelFill && + upperSeries.visibleEntries.isNotEmpty && + lowerSeries.visibleEntries.isNotEmpty) { + super.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + } } @override - int? getMinEpoch() => _innerSeries.getMinEpoch(); + int? getMinEpoch() => innerSeries.getMinEpoch(); @override - int? getMaxEpoch() => _innerSeries.getMaxEpoch(); + int? getMaxEpoch() => innerSeries.getMaxEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/cci_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/cci_series.dart index ffabdca86..2308a90c5 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/cci_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/cci_series.dart @@ -1,5 +1,6 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; @@ -33,6 +34,7 @@ class CCISeries extends AbstractSingleIndicatorSeries { const LineStyle(color: Colors.white, thickness: 0.5), LineStyle cciLineStyle = const LineStyle(), this.showZones = true, + this.showLastIndicator = false, String? id, }) : _options = options, super( @@ -40,6 +42,10 @@ class CCISeries extends AbstractSingleIndicatorSeries { id ?? 'CCISeries', options: options, style: cciLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + cciLineStyle.color, + showLastIndicator: showLastIndicator, + ), ); final IndicatorInput _indicatorInput; @@ -64,6 +70,9 @@ class CCISeries extends AbstractSingleIndicatorSeries { /// Whether to fill overbought/sold zones. final bool showZones; + /// Whether to show last indicator or not. + final bool showLastIndicator; + @override SeriesPainter createPainter() => showZones ? OscillatorLinePainter( diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/donchian_channels_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/donchian_channels_series.dart index b32747bf1..b21b4e06e 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/donchian_channels_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/donchian_channels_series.dart @@ -1,16 +1,18 @@ -import 'dart:ui' as ui; - -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; -import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_fill.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; import '../../chart_data.dart'; +import '../line_series/line_series.dart'; +import '../series.dart'; import '../series_painter.dart'; /// Donchian Channels series @@ -37,9 +39,14 @@ class DonchianChannelsSeries extends Series { // TODO(Ramin): define DonchianChannelOptions class super(id ?? 'Donchian$config'); - late LineSeries _upperChannelSeries; - late LineSeries _middleChannelSeries; - late LineSeries _lowerChannelSeries; + /// Upper channel series. + late LineSeries upperChannelSeries; + + /// Middle channel series. + late LineSeries middleChannelSeries; + + /// Lower channel series. + late LineSeries lowerChannelSeries; final HighValueIndicator _highIndicator; final LowValueIndicator _lowIndicator; @@ -67,50 +74,81 @@ class DonchianChannelsSeries extends Series { lowerChannelIndicator, )..calculateValues(); - _upperChannelSeries = LineSeries( + upperChannelSeries = LineSeries( upperChannelIndicator.results, style: config.upperLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.upperLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _lowerChannelSeries = LineSeries( + lowerChannelSeries = LineSeries( lowerChannelIndicator.results, style: config.lowerLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.lowerLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _middleChannelSeries = LineSeries( + middleChannelSeries = LineSeries( middleChannelIndicator.results, style: config.middleLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.middleLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - // TODO(ramin): return the painter that paints Channel Fill between bands + + if (config.showChannelFill) { + return ChannelFillPainter( + upperChannelSeries, + lowerChannelSeries, + firstUpperChannelFillColor: config.fillColor.withOpacity(0.2), + secondUpperChannelFillColor: config.fillColor.withOpacity(0.2), + ); + } + return null; } + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + final DonchianChannelsSeries oldSeries = previous as DonchianChannelsSeries; + return config.toJson().toString() != oldSeries.config.toJson().toString(); + } + @override bool didUpdate(ChartData? oldData) { final DonchianChannelsSeries? oldSeries = oldData as DonchianChannelsSeries?; final bool upperUpdated = - _upperChannelSeries.didUpdate(oldSeries?._upperChannelSeries); + upperChannelSeries.didUpdate(oldSeries?.upperChannelSeries); final bool middleUpdated = - _middleChannelSeries.didUpdate(oldSeries?._middleChannelSeries); + middleChannelSeries.didUpdate(oldSeries?.middleChannelSeries); final bool lowerUpdated = - _lowerChannelSeries.didUpdate(oldSeries?._lowerChannelSeries); + lowerChannelSeries.didUpdate(oldSeries?.lowerChannelSeries); return upperUpdated || middleUpdated || lowerUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _upperChannelSeries.update(leftEpoch, rightEpoch); - _middleChannelSeries.update(leftEpoch, rightEpoch); - _lowerChannelSeries.update(leftEpoch, rightEpoch); + upperChannelSeries.update(leftEpoch, rightEpoch); + middleChannelSeries.update(leftEpoch, rightEpoch); + lowerChannelSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ - _lowerChannelSeries.minValue, - _upperChannelSeries.maxValue, + lowerChannelSeries.minValue, + upperChannelSeries.maxValue, ]; @override @@ -123,119 +161,25 @@ class DonchianChannelsSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _upperChannelSeries.paint( + upperChannelSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _middleChannelSeries.paint( + middleChannelSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _lowerChannelSeries.paint( + lowerChannelSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - if (_lowerChannelSeries.entries == null || - _upperChannelSeries.entries == null) { - return; - } - if (config.showChannelFill && - _upperChannelSeries.visibleEntries.isNotEmpty && - _lowerChannelSeries.visibleEntries.isNotEmpty) { - final Path fillPath = Path() - ..moveTo( - epochToX(_upperChannelSeries.getEpochOf( - _upperChannelSeries.visibleEntries.first, - _upperChannelSeries.visibleEntries.startIndex, - )), - quoteToY(_upperChannelSeries.visibleEntries.first.quote), - ); - - for (int i = _upperChannelSeries.visibleEntries.startIndex + 1; - i < _upperChannelSeries.visibleEntries.endIndex - 1; - i++) { - final Tick tick = _upperChannelSeries.entries![i]; - fillPath.lineTo( - epochToX(_upperChannelSeries.getEpochOf(tick, i)), - quoteToY(tick.quote), - ); - } - - // Check for animated upper tick. - final Tick lastUpperTick = _upperChannelSeries.entries!.last; - final Tick lastUpperVisibleTick = _upperChannelSeries.visibleEntries.last; - double? lastVisibleTickX; - - if (lastUpperTick == lastUpperVisibleTick && - _upperChannelSeries.prevLastEntry != null) { - lastVisibleTickX = ui.lerpDouble( - epochToX(_upperChannelSeries.getEpochOf( - _upperChannelSeries.prevLastEntry!.entry, - _upperChannelSeries.prevLastEntry!.index, - )), - epochToX(lastUpperTick.epoch), - animationInfo.currentTickPercent, - ); - - final double tickY = quoteToY(ui.lerpDouble( - _upperChannelSeries.prevLastEntry!.entry.quote, - lastUpperTick.quote, - animationInfo.currentTickPercent, - )!); - - fillPath.lineTo(lastVisibleTickX!, tickY); - } else { - lastVisibleTickX = epochToX(lastUpperVisibleTick.epoch); - fillPath.lineTo(lastVisibleTickX, quoteToY(lastUpperVisibleTick.quote)); - } - - // Check for animated lower tick. - final Tick lastLowerTick = _lowerChannelSeries.entries!.last; - final Tick lastLowerVisibleTick = _lowerChannelSeries.visibleEntries.last; - - if (lastLowerTick == lastLowerVisibleTick && - _lowerChannelSeries.prevLastEntry != null) { - lastVisibleTickX = ui.lerpDouble( - epochToX(_lowerChannelSeries.getEpochOf( - _lowerChannelSeries.prevLastEntry!.entry, - _lowerChannelSeries.prevLastEntry!.index, - )), - epochToX(lastLowerTick.epoch), - animationInfo.currentTickPercent, - ); - - final double tickY = quoteToY(ui.lerpDouble( - _lowerChannelSeries.prevLastEntry!.entry.quote, - lastLowerTick.quote, - animationInfo.currentTickPercent, - )!); - - fillPath.lineTo(lastVisibleTickX!, tickY); - } else { - lastVisibleTickX = epochToX(lastLowerVisibleTick.epoch); - fillPath.lineTo(lastVisibleTickX, quoteToY(lastLowerVisibleTick.quote)); - } - - for (int i = _lowerChannelSeries.visibleEntries.endIndex - 1; - i >= _lowerChannelSeries.visibleEntries.startIndex; - i--) { - final Tick tick = _lowerChannelSeries.entries![i]; - fillPath.lineTo( - epochToX(_lowerChannelSeries.getEpochOf(tick, i)), - quoteToY(tick.quote), - ); - } - - fillPath.close(); - - paintFill( - canvas, - fillPath, - config.fillColor, - ); + upperChannelSeries.visibleEntries.isNotEmpty && + lowerChannelSeries.visibleEntries.isNotEmpty) { + super.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } } // min/max epoch for all 3 channels are equal, using only `_loweChannelSeries` min/max. @override - int? getMaxEpoch() => _lowerChannelSeries.getMaxEpoch(); + int? getMaxEpoch() => lowerChannelSeries.getMaxEpoch(); @override - int? getMinEpoch() => _lowerChannelSeries.getMinEpoch(); + int? getMinEpoch() => lowerChannelSeries.getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/dpo_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/dpo_series.dart index 20cce38c5..002a5758c 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/dpo_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/dpo_series.dart @@ -1,6 +1,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; @@ -38,7 +39,8 @@ class DPOSeries extends Series { }) : _fieldIndicator = indicator, super(id ?? 'Ichimoku$dpoOptions'); - late SingleIndicatorSeries _dpoSeries; + /// DPO Series + late SingleIndicatorSeries dpoSeries; /// Detrended Price Oscillator options final DPOOptions dpoOptions; @@ -55,7 +57,7 @@ class DPOSeries extends Series { isCentered: dpoOptions.isCentered, ); - _dpoSeries = SingleIndicatorSeries( + dpoSeries = SingleIndicatorSeries( painterCreator: (Series series) => OscillatorLinePainter( series as DataSeries, secondaryHorizontalLines: [0], @@ -64,6 +66,13 @@ class DPOSeries extends Series { inputIndicator: _fieldIndicator, options: dpoOptions, offset: dpoOptions.isCentered ? -dpoIndicator.timeShift : 0, + style: dpoOptions.lineStyle, + lastTickIndicatorStyle: dpoOptions.lineStyle != null + ? getLastIndicatorStyle( + dpoOptions.lineStyle!.color, + showLastIndicator: dpoOptions.showLastIndicator, + ) + : null, ); return null; @@ -73,20 +82,20 @@ class DPOSeries extends Series { bool didUpdate(ChartData? oldData) { final DPOSeries? series = oldData as DPOSeries; - final bool dpoUpdated = _dpoSeries.didUpdate(series?._dpoSeries); + final bool dpoUpdated = dpoSeries.didUpdate(series?.dpoSeries); return dpoUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _dpoSeries.update(leftEpoch, rightEpoch); + dpoSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ - _dpoSeries.maxValue, - _dpoSeries.minValue, + dpoSeries.minValue, + dpoSeries.maxValue, ]; @override @@ -99,13 +108,13 @@ class DPOSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _dpoSeries.paint( + dpoSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } @override - int? getMinEpoch() => _dpoSeries.getMinEpoch(); + int? getMinEpoch() => dpoSeries.getMinEpoch(); @override - int? getMaxEpoch() => _dpoSeries.getMaxEpoch(); + int? getMaxEpoch() => dpoSeries.getMaxEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fcb_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fcb_series.dart index ed802b1b1..b5ecbceed 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fcb_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fcb_series.dart @@ -1,10 +1,11 @@ +import 'package:deriv_chart/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/chart_theme.dart'; -import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; @@ -20,64 +21,84 @@ class FractalChaosBandSeries extends Series { /// Initializes FractalChaosBandSeries( this.indicatorInput, { + required this.config, String? id, - // ignore: avoid_unused_constructor_parameters - bool channelFill = false, }) : super(id ?? 'FCB'); ///input data final IndicatorInput indicatorInput; - late SingleIndicatorSeries _fcbHighSeries; - late SingleIndicatorSeries _fcbLowSeries; + /// FCB high series + late SingleIndicatorSeries fcbHighSeries; + + /// FCB low series + late SingleIndicatorSeries fcbLowSeries; + + /// Configuration of FCB Indicator. + final FractalChaosBandIndicatorConfig config; @override SeriesPainter? createPainter() { - _fcbHighSeries = SingleIndicatorSeries( + fcbHighSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), - // Using SMA temporarily until TA's migration branch gets updated. - indicatorCreator: () => - SMAIndicator(CloseValueIndicator(indicatorInput), 10), + indicatorCreator: () => FCBHighIndicator(indicatorInput), inputIndicator: CloseValueIndicator(indicatorInput), - style: const LineStyle(color: Colors.blue), + style: config.highLineStyle, ); - _fcbLowSeries = SingleIndicatorSeries( + fcbLowSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), - indicatorCreator: () => - SMAIndicator(CloseValueIndicator(indicatorInput), 10), + indicatorCreator: () => FCBLowIndicator(indicatorInput), inputIndicator: CloseValueIndicator(indicatorInput), - style: const LineStyle(color: Colors.blue), + style: config.lowLineStyle, ); + if (config.showChannelFill) { + return ChannelFillPainter( + fcbHighSeries, + fcbLowSeries, + firstUpperChannelFillColor: config.fillColor.withOpacity(0.2), + secondUpperChannelFillColor: config.fillColor.withOpacity(0.2), + ); + } + return null; } + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + final FractalChaosBandSeries oldSeries = previous as FractalChaosBandSeries; + return config.toJson().toString() != oldSeries.config.toJson().toString(); + } + @override bool didUpdate(ChartData? oldData) { final FractalChaosBandSeries? series = oldData as FractalChaosBandSeries?; - final bool _fcbHighUpdated = - _fcbHighSeries.didUpdate(series?._fcbHighSeries); - final bool _fcbLowUpdated = _fcbLowSeries.didUpdate(series?._fcbLowSeries); + final bool _fcbHighUpdated = fcbHighSeries.didUpdate(series?.fcbHighSeries); + final bool _fcbLowUpdated = fcbLowSeries.didUpdate(series?.fcbLowSeries); return _fcbHighUpdated || _fcbLowUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _fcbHighSeries.update(leftEpoch, rightEpoch); - _fcbLowSeries.update(leftEpoch, rightEpoch); + fcbHighSeries.update(leftEpoch, rightEpoch); + fcbLowSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ [ - _fcbHighSeries, - _fcbLowSeries, + fcbHighSeries, + fcbLowSeries, ].getMinValue(), [ - _fcbHighSeries, - _fcbLowSeries, + fcbHighSeries, + fcbLowSeries, ].getMaxValue() ]; @@ -91,21 +112,28 @@ class FractalChaosBandSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _fcbLowSeries.paint( + fcbLowSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _fcbHighSeries.paint( + fcbHighSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + + if (config.showChannelFill && + fcbHighSeries.visibleEntries.isNotEmpty && + fcbLowSeries.visibleEntries.isNotEmpty) { + super.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + } } @override int? getMaxEpoch() => [ - _fcbLowSeries, - _fcbHighSeries, + fcbLowSeries, + fcbHighSeries, ].getMaxEpoch(); @override int? getMinEpoch() => [ - _fcbLowSeries, - _fcbHighSeries, + fcbLowSeries, + fcbHighSeries, ].getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fractals_series/arrow_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fractals_series/arrow_painter.dart index d80cf5467..732c56cd5 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fractals_series/arrow_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/fractals_series/arrow_painter.dart @@ -1,9 +1,11 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/create_shape_path.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; -import '../../../chart_data.dart'; import '../../data_painter.dart'; /// A [DataPainter] for painting arrow data. diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/gator_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/gator_series.dart index 2c33c3a42..b7b0e5e88 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/gator_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/gator_series.dart @@ -28,6 +28,7 @@ class GatorSeries extends Series { IndicatorInput indicatorInput, { required this.gatorOptions, required this.gatorConfig, + this.barStyle = const BarStyle(), String? id, }) : _fieldIndicator = HL2Indicator(indicatorInput), super(id ?? 'Gator$AlligatorOptions'); @@ -37,15 +38,21 @@ class GatorSeries extends Series { /// Gator options AlligatorOptions gatorOptions; - late SingleIndicatorSeries _gatorTopSeries; - late SingleIndicatorSeries _gatorBottomSeries; + /// Gator top series + late SingleIndicatorSeries gatorTopSeries; + + /// Gator bottom series + late SingleIndicatorSeries gatorBottomSeries; /// Gator config final GatorIndicatorConfig gatorConfig; + /// Histogram bar style + final BarStyle barStyle; + @override SeriesPainter? createPainter() { - _gatorTopSeries = SingleIndicatorSeries( + gatorTopSeries = SingleIndicatorSeries( painterCreator: (Series series) => BarPainter( series as DataSeries, checkColorCallback: ({ @@ -59,14 +66,15 @@ class GatorSeries extends Series { jawPeriod: gatorOptions.jawPeriod, jawOffset: gatorConfig.jawOffset, teethPeriod: gatorOptions.teethPeriod, - teethOffset: gatorConfig.teethOffset), + teethOffset: gatorConfig.teethOffset) + ..calculateValues(), inputIndicator: _fieldIndicator, options: gatorOptions, - style: const BarStyle(), + style: barStyle, offset: min(gatorConfig.jawOffset, gatorConfig.teethOffset), ); - _gatorBottomSeries = SingleIndicatorSeries( + gatorBottomSeries = SingleIndicatorSeries( painterCreator: ( Series series, ) => @@ -84,10 +92,10 @@ class GatorSeries extends Series { teethOffset: gatorConfig.teethOffset, lipsOffset: gatorConfig.lipsOffset, lipsPeriod: gatorOptions.lipsPeriod, - ), + )..calculateValues(), inputIndicator: _fieldIndicator, options: gatorOptions, - style: const BarStyle(), + style: barStyle, offset: min(gatorConfig.teethOffset, gatorConfig.lipsOffset), ); @@ -98,23 +106,23 @@ class GatorSeries extends Series { bool didUpdate(ChartData? oldData) { final GatorSeries? series = oldData as GatorSeries?; final bool _gatorBottomUpdated = - _gatorBottomSeries.didUpdate(series?._gatorBottomSeries); + gatorBottomSeries.didUpdate(series?.gatorBottomSeries); final bool _gatorTopUpdated = - _gatorTopSeries.didUpdate(series?._gatorTopSeries); + gatorTopSeries.didUpdate(series?.gatorTopSeries); return _gatorTopUpdated || _gatorBottomUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _gatorTopSeries.update(leftEpoch, rightEpoch); - _gatorBottomSeries.update(leftEpoch, rightEpoch); + gatorTopSeries.update(leftEpoch, rightEpoch); + gatorBottomSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ - [_gatorBottomSeries, _gatorTopSeries].getMinValue(), - [_gatorBottomSeries, _gatorTopSeries].getMaxValue() + [gatorBottomSeries, gatorTopSeries].getMinValue(), + [gatorBottomSeries, gatorTopSeries].getMaxValue() ]; @override @@ -127,17 +135,17 @@ class GatorSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _gatorBottomSeries.paint( + gatorBottomSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _gatorTopSeries.paint( + gatorTopSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } @override int? getMaxEpoch() => - [_gatorBottomSeries, _gatorTopSeries].getMaxEpoch(); + [gatorBottomSeries, gatorTopSeries].getMaxEpoch(); @override int? getMinEpoch() => - [_gatorBottomSeries, _gatorTopSeries].getMinEpoch(); + [gatorBottomSeries, gatorTopSeries].getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ichimoku_cloud_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ichimoku_cloud_series.dart index 3bfee5033..6043d4c92 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ichimoku_cloud_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ichimoku_cloud_series.dart @@ -1,14 +1,18 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/functions/helper_functions.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; import '../../chart_data.dart'; +import '../data_series.dart'; +import '../series.dart'; import '../series_painter.dart'; import 'models/ichimoku_clouds_options.dart'; import 'single_indicator_series.dart'; @@ -23,12 +27,24 @@ class IchimokuCloudSeries extends Series { String? id, }) : super(id ?? 'Ichimoku$ichimokuCloudOptions'); - late SingleIndicatorSeries _conversionLineSeries; - late SingleIndicatorSeries _baseLineSeries; - late SingleIndicatorSeries _laggingSpanSeries; - late SingleIndicatorSeries _spanASeries; - late SingleIndicatorSeries _spanBSeries; - final List _ichimokuSeries = []; + /// Conversion line series. + late SingleIndicatorSeries conversionLineSeries; + + /// Base line series. + late SingleIndicatorSeries baseLineSeries; + + /// Lagging line series. + late SingleIndicatorSeries laggingSpanSeries; + + /// SpanA line series. + late SingleIndicatorSeries spanASeries; + + /// SpanB line series. + late SingleIndicatorSeries spanBSeries; + + + /// Ichimoku series. + final List ichimokuSeries = []; /// List of [Tick]s to calculate IchimokuCloud on. final IndicatorDataInput ticks; @@ -72,74 +88,84 @@ class IchimokuCloudSeries extends Series { period: ichimokuCloudOptions.leadingSpanBPeriod, ); - _conversionLineSeries = SingleIndicatorSeries( + conversionLineSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => conversionLineIndicator, inputIndicator: closeValueIndicator, options: ichimokuCloudOptions, - style: const LineStyle( - color: Colors.indigo, + style: config.conversionLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.conversionLineStyle.color, + showLastIndicator: config.showLastIndicator, ), ); - _baseLineSeries = SingleIndicatorSeries( + baseLineSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => baseLineIndicator, inputIndicator: closeValueIndicator, options: ichimokuCloudOptions, - style: const LineStyle( - color: Colors.redAccent, + style: config.baseLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.baseLineStyle.color, + showLastIndicator: config.showLastIndicator, ), ); - _laggingSpanSeries = SingleIndicatorSeries( + laggingSpanSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => laggingSpanIndicator, inputIndicator: closeValueIndicator, options: ichimokuCloudOptions, offset: config.laggingSpanOffset, - style: const LineStyle( - color: Colors.lime, + style: config.laggingLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.laggingLineStyle.color, + showLastIndicator: config.showLastIndicator, ), ); - _spanASeries = SingleIndicatorSeries( + spanASeries = SingleIndicatorSeries( painterCreator: (Series series) => null, indicatorCreator: () => spanAIndicator, inputIndicator: closeValueIndicator, options: ichimokuCloudOptions, offset: ichimokuCloudOptions.baseLinePeriod, - style: const LineStyle( - color: Colors.green, + style: config.spanALineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.spanALineStyle.color, + showLastIndicator: config.showLastIndicator, ), ); - _spanBSeries = SingleIndicatorSeries( + spanBSeries = SingleIndicatorSeries( painterCreator: (Series series) => null, indicatorCreator: () => spanBIndicator, inputIndicator: closeValueIndicator, options: ichimokuCloudOptions, offset: ichimokuCloudOptions.baseLinePeriod, - style: const LineStyle( - color: Colors.red, + style: config.spanBLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.spanBLineStyle.color, + showLastIndicator: config.showLastIndicator, ), ); - _ichimokuSeries - ..add(_conversionLineSeries) - ..add(_baseLineSeries) - ..add(_laggingSpanSeries) - ..add(_spanASeries) - ..add(_spanBSeries); + ichimokuSeries + ..add(conversionLineSeries) + ..add(baseLineSeries) + ..add(laggingSpanSeries) + ..add(spanASeries) + ..add(spanBSeries); return ChannelFillPainter( - _spanASeries, - _spanBSeries, - firstUpperChannelFillColor: Colors.green.withOpacity(0.2), - secondUpperChannelFillColor: Colors.red.withOpacity(0.2), + spanASeries, + spanBSeries, + firstUpperChannelFillColor: config.spanALineStyle.color.withOpacity(0.2), + secondUpperChannelFillColor: config.spanBLineStyle.color.withOpacity(0.2), ); } @@ -148,13 +174,13 @@ class IchimokuCloudSeries extends Series { final IchimokuCloudSeries? series = oldData as IchimokuCloudSeries?; final bool conversionLineUpdated = - _conversionLineSeries.didUpdate(series?._conversionLineSeries); + conversionLineSeries.didUpdate(series?.conversionLineSeries); final bool baseLineUpdated = - _baseLineSeries.didUpdate(series?._baseLineSeries); + baseLineSeries.didUpdate(series?.baseLineSeries); final bool laggingSpanUpdated = - _laggingSpanSeries.didUpdate(series?._laggingSpanSeries); - final bool spanAUpdated = _spanASeries.didUpdate(series?._spanASeries); - final bool spanBUpdated = _spanBSeries.didUpdate(series?._spanBSeries); + laggingSpanSeries.didUpdate(series?.laggingSpanSeries); + final bool spanAUpdated = spanASeries.didUpdate(series?.spanASeries); + final bool spanBUpdated = spanBSeries.didUpdate(series?.spanBSeries); return conversionLineUpdated || baseLineUpdated || @@ -165,26 +191,36 @@ class IchimokuCloudSeries extends Series { @override void onUpdate(int leftEpoch, int rightEpoch) { - _conversionLineSeries.update(leftEpoch, rightEpoch); - _baseLineSeries.update(leftEpoch, rightEpoch); - _laggingSpanSeries.update(leftEpoch, rightEpoch); - _spanASeries.update(leftEpoch, rightEpoch); - _spanBSeries.update(leftEpoch, rightEpoch); + conversionLineSeries.update(leftEpoch, rightEpoch); + baseLineSeries.update(leftEpoch, rightEpoch); + laggingSpanSeries.update(leftEpoch, rightEpoch); + spanASeries.update(leftEpoch, rightEpoch); + spanBSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() { - final double minValue = _ichimokuSeries + final double minValue = ichimokuSeries .map((SingleIndicatorSeries series) => series.minValue) .reduce(safeMin); - final double maxValue = _ichimokuSeries + final double maxValue = ichimokuSeries .map((SingleIndicatorSeries series) => series.maxValue) .reduce(safeMax); return [minValue, maxValue]; } + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + final IchimokuCloudSeries oldSeries = previous as IchimokuCloudSeries; + return config.toJson().toString() != oldSeries.config.toJson().toString(); + } + @override void paint( Canvas canvas, @@ -195,24 +231,28 @@ class IchimokuCloudSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _conversionLineSeries.paint( + conversionLineSeries.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + baseLineSeries.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + laggingSpanSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _baseLineSeries.paint( + spanASeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _laggingSpanSeries.paint( + spanBSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); super.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); if (animationInfo.currentTickPercent == 1) { - _spanASeries.resetLastEntryAnimation(); - _spanBSeries.resetLastEntryAnimation(); + spanASeries.resetLastEntryAnimation(); + spanBSeries.resetLastEntryAnimation(); } } @override - int? getMaxEpoch() => _ichimokuSeries.getMaxEpoch(); + int? getMaxEpoch() => ichimokuSeries.getMaxEpoch(); @override - int? getMinEpoch() => _ichimokuSeries.getMinEpoch(); + int? getMinEpoch() => ichimokuSeries.getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_env_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_env_series.dart index a1c2b48bb..23373ab52 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_env_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_env_series.dart @@ -1,11 +1,12 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/functions/helper_functions.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/chart_theme.dart'; -import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; import '../../chart_data.dart'; @@ -45,18 +46,25 @@ class MAEnvSeries extends Series { /// Moving Average Envelope options MAEnvOptions? maEnvOptions; - late SingleIndicatorSeries _lowerSeries; - late SingleIndicatorSeries _middleSeries; - late SingleIndicatorSeries _upperSeries; + /// Lower series + late SingleIndicatorSeries lowerSeries; - final List _innerSeries = []; + /// Middle series + late SingleIndicatorSeries middleSeries; + + /// Upper series + late SingleIndicatorSeries upperSeries; + + /// Inner Series + final List innerSeries = []; @override SeriesPainter? createPainter() { final CachedIndicator smaIndicator = - MASeries.getMAIndicator(_fieldIndicator, maEnvOptions!); + MASeries.getMAIndicator(_fieldIndicator, maEnvOptions!) + ..calculateValues(); - _lowerSeries = SingleIndicatorSeries( + lowerSeries = SingleIndicatorSeries( painterCreator: ( Series series, ) => @@ -68,19 +76,27 @@ class MAEnvSeries extends Series { ), inputIndicator: _fieldIndicator, options: maEnvOptions, - style: const LineStyle(color: Colors.red), + style: maEnvOptions!.lowerLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + maEnvOptions!.lowerLineStyle.color, + showLastIndicator: maEnvOptions!.showLastIndicator, + ), ); - _middleSeries = SingleIndicatorSeries( + middleSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => smaIndicator, inputIndicator: _fieldIndicator, options: maEnvOptions, - style: const LineStyle(color: Colors.blue), + style: maEnvOptions!.middleLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + maEnvOptions!.middleLineStyle.color, + showLastIndicator: maEnvOptions!.showLastIndicator, + ), ); - _upperSeries = SingleIndicatorSeries( + upperSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => MAEnvUpperIndicator( @@ -90,49 +106,67 @@ class MAEnvSeries extends Series { ), inputIndicator: _fieldIndicator, options: maEnvOptions, - style: const LineStyle(color: Colors.green), + style: maEnvOptions!.upperLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + maEnvOptions!.upperLineStyle.color, + showLastIndicator: maEnvOptions!.showLastIndicator, + ), ); - _innerSeries - ..add(_lowerSeries) - ..add(_middleSeries) - ..add(_upperSeries); + innerSeries + ..add(lowerSeries) + ..add(middleSeries) + ..add(upperSeries); - return null; + return ChannelFillPainter( + upperSeries, + lowerSeries, + firstUpperChannelFillColor: maEnvOptions?.fillColor.withOpacity(0.2), + secondUpperChannelFillColor: maEnvOptions?.fillColor.withOpacity(0.2), + ); } @override bool didUpdate(ChartData? oldData) { final MAEnvSeries? series = oldData as MAEnvSeries?; - final bool _lowerUpdated = _lowerSeries.didUpdate(series?._lowerSeries); - final bool _middleUpdated = _middleSeries.didUpdate(series?._middleSeries); - final bool _upperUpdated = _upperSeries.didUpdate(series?._upperSeries); + final bool _lowerUpdated = lowerSeries.didUpdate(series?.lowerSeries); + final bool _middleUpdated = middleSeries.didUpdate(series?.middleSeries); + final bool _upperUpdated = upperSeries.didUpdate(series?.upperSeries); return _lowerUpdated || _middleUpdated || _upperUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _lowerSeries.update(leftEpoch, rightEpoch); - _middleSeries.update(leftEpoch, rightEpoch); - _upperSeries.update(leftEpoch, rightEpoch); + lowerSeries.update(leftEpoch, rightEpoch); + middleSeries.update(leftEpoch, rightEpoch); + upperSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => - // Can just use _lowerSeries minValue for min and _upperSeries maxValue + // Can just use lowerSeries minValue for min and upperSeries maxValue // for max. But to be safe we calculate min and max. from all three series // TODO(Ramin): Maybe later we can have these code and getMin/MaxEpochs in a parent class for Indicators like MAEnv, Ichimoku, Bollinger, etc [ - _innerSeries + innerSeries .map((Series series) => series.minValue) .reduce((double a, double b) => safeMin(a, b)), - _innerSeries + innerSeries .map((Series series) => series.maxValue) .reduce((double a, double b) => safeMax(a, b)), ]; + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + return maEnvOptions != (previous as MAEnvSeries).maEnvOptions; + } + @override void paint( Canvas canvas, @@ -143,17 +177,25 @@ class MAEnvSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _lowerSeries.paint( + lowerSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _middleSeries.paint( + middleSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _upperSeries.paint( + upperSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + + if (maEnvOptions != null && + maEnvOptions!.showChannelFill && + upperSeries.visibleEntries.isNotEmpty && + lowerSeries.visibleEntries.isNotEmpty) { + super.paint( + canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); + } } @override - int? getMaxEpoch() => _innerSeries.getMaxEpoch(); + int? getMaxEpoch() => innerSeries.getMaxEpoch(); @override - int? getMinEpoch() => _innerSeries.getMinEpoch(); + int? getMinEpoch() => innerSeries.getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_rainbow_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_rainbow_series.dart index c286ea1db..693833fdf 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_rainbow_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_rainbow_series.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; @@ -27,11 +28,11 @@ class RainbowSeries extends Series { RainbowSeries( IndicatorInput indicatorInput, { required RainbowOptions rainbowOptions, - List? rainbowColors, + List? rainbowLineStyles, String? id, }) : this.fromIndicator( CloseValueIndicator(indicatorInput), - rainbowColors: rainbowColors ?? const [], + rainbowLineStyles: rainbowLineStyles ?? const [], id: id, rainbowOptions: rainbowOptions, ); @@ -40,7 +41,7 @@ class RainbowSeries extends Series { RainbowSeries.fromIndicator( Indicator indicator, { required this.rainbowOptions, - this.rainbowColors = const [], + this.rainbowLineStyles = const [], String? id, }) : _fieldIndicator = indicator, super(id ?? 'MARainbow$rainbowOptions'); @@ -50,38 +51,50 @@ class RainbowSeries extends Series { /// Rainbow options RainbowOptions rainbowOptions; - final List _rainbowSeries = []; + /// Rainbow series + final List rainbowSeries = []; - /// colors of rainbow bands - final List rainbowColors; + /// Line styles of rainbow bands + final List rainbowLineStyles; @override SeriesPainter? createPainter() { /// check if we have color for every band - final bool useColors = rainbowColors.length == rainbowOptions.bandsCount; + final bool useColors = + rainbowLineStyles.length == rainbowOptions.bandsCount; final List> indicators = >[]; for (int i = 0; i < rainbowOptions.bandsCount; i++) { + final LineStyle style = + useColors ? rainbowLineStyles[i] : const LineStyle(color: Colors.red); if (i == 0) { indicators .add(MASeries.getMAIndicator(_fieldIndicator, rainbowOptions)); - _rainbowSeries.add(SingleIndicatorSeries( + rainbowSeries.add(SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => indicators[0] as CachedIndicator, inputIndicator: _fieldIndicator, options: rainbowOptions, - style: LineStyle(color: useColors ? rainbowColors[i] : Colors.red), + style: style, + lastTickIndicatorStyle: getLastIndicatorStyle( + style.color, + showLastIndicator: rainbowOptions.showLastIndicator, + ), )); } else { indicators .add(MASeries.getMAIndicator(indicators[i - 1], rainbowOptions)); - _rainbowSeries.add(SingleIndicatorSeries( + rainbowSeries.add(SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => indicators[i] as CachedIndicator, inputIndicator: _fieldIndicator, options: rainbowOptions, - style: LineStyle(color: useColors ? rainbowColors[i] : Colors.red), + style: style, + lastTickIndicatorStyle: getLastIndicatorStyle( + style.color, + showLastIndicator: rainbowOptions.showLastIndicator, + ), )); } } @@ -93,14 +106,13 @@ class RainbowSeries extends Series { final RainbowSeries? oldRainbowSeries = oldData as RainbowSeries?; if (oldRainbowSeries == null) { return false; - } else if (oldRainbowSeries._rainbowSeries.length != - _rainbowSeries.length) { + } else if (oldRainbowSeries.rainbowSeries.length != rainbowSeries.length) { return true; } bool needUpdate = false; - for (int i = 0; i < _rainbowSeries.length; i++) { - if (_rainbowSeries[i].didUpdate(oldRainbowSeries._rainbowSeries[i])) { + for (int i = 0; i < rainbowSeries.length; i++) { + if (rainbowSeries[i].didUpdate(oldRainbowSeries.rainbowSeries[i])) { needUpdate = true; } } @@ -109,7 +121,7 @@ class RainbowSeries extends Series { @override void onUpdate(int leftEpoch, int rightEpoch) { - for (final SingleIndicatorSeries series in _rainbowSeries) { + for (final SingleIndicatorSeries series in rainbowSeries) { series.update(leftEpoch, rightEpoch); } } @@ -125,7 +137,7 @@ class RainbowSeries extends Series { /// Returns minimum value of all series double _getMinValue() { final List minValues = []; - for (final SingleIndicatorSeries series in _rainbowSeries) { + for (final SingleIndicatorSeries series in rainbowSeries) { minValues.add(series.minValue); } return minValues.reduce(min); @@ -134,12 +146,23 @@ class RainbowSeries extends Series { /// Returns maximum value of all series double _getMaxValue() { final List maxValues = []; - for (final SingleIndicatorSeries series in _rainbowSeries) { + for (final SingleIndicatorSeries series in rainbowSeries) { maxValues.add(series.maxValue); } return maxValues.reduce(max); } + @override + bool shouldRepaint(ChartData? previous) { + if (previous == null) { + return true; + } + + final RainbowSeries oldSeries = previous as RainbowSeries; + return rainbowOptions != oldSeries.rainbowOptions || + rainbowLineStyles != oldSeries.rainbowLineStyles; + } + @override void paint( Canvas canvas, @@ -150,7 +173,7 @@ class RainbowSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - for (final SingleIndicatorSeries series in _rainbowSeries) { + for (final SingleIndicatorSeries series in rainbowSeries) { series.paint( canvas, size, @@ -165,9 +188,9 @@ class RainbowSeries extends Series { @override int? getMaxEpoch() => - _rainbowSeries.isNotEmpty ? _rainbowSeries[0].getMaxEpoch() : null; + rainbowSeries.isNotEmpty ? rainbowSeries[0].getMaxEpoch() : null; @override int? getMinEpoch() => - _rainbowSeries.isNotEmpty ? _rainbowSeries[0].getMinEpoch() : null; + rainbowSeries.isNotEmpty ? rainbowSeries[0].getMinEpoch() : null; } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart index 573f8fd26..83d633475 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart @@ -1,4 +1,6 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; @@ -42,11 +44,27 @@ class MASeries extends AbstractSingleIndicatorSeries { options: options, style: style ?? const LineStyle(thickness: 0.5), offset: offset, + lastTickIndicatorStyle: style != null + ? getLastIndicatorStyle( + style.color, + showLastIndicator: options.showLastIndicator, + ) + : null, ); @override SeriesPainter createPainter() => LinePainter(this); + @override + bool shouldRepaint(ChartData? oldDelegate) { + if (oldDelegate == null) { + return true; + } + + final MASeries oldSeries = oldDelegate as MASeries; + return options != oldSeries.options || style != oldSeries.style; + } + @override CachedIndicator initializeIndicator() => MASeries.getMAIndicator(inputIndicator, options as MAOptions); diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/macd_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/macd_series.dart index 6d3ae1807..c856855a7 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/macd_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/macd_series.dart @@ -1,4 +1,3 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_painters/bar_painter.dart'; @@ -6,12 +5,16 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; -import 'package:deriv_chart/src/theme/painting_styles/bar_style.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; +import '../data_series.dart'; +import '../series.dart'; import '../series_painter.dart'; import 'models/macd_options.dart'; @@ -22,15 +25,20 @@ class MACDSeries extends Series { this.indicatorInput, { required this.options, required this.config, - String id = '', - }) : super(id); + String? id = '', + }) : super(id ?? 'MACD$options'); ///input data final IndicatorInput indicatorInput; - late SingleIndicatorSeries _macdSeries; - late SingleIndicatorSeries _signalMACDSeries; - late SingleIndicatorSeries _macdHistogramSeries; + /// MACD Series + late SingleIndicatorSeries macdSeries; + + /// Signal MACD Series + late SingleIndicatorSeries signalMACDSeries; + + /// MACD Histogram Series + late SingleIndicatorSeries macdHistogramSeries; /// MACD Configuration. MACDIndicatorConfig config; @@ -55,26 +63,34 @@ class MACDSeries extends Series { macdIndicator, signalMACDIndicator) ..calculateValues(); - _macdSeries = SingleIndicatorSeries( + macdSeries = SingleIndicatorSeries( painterCreator: (Series series) => OscillatorLinePainter( series as DataSeries, secondaryHorizontalLines: [0]), indicatorCreator: () => macdIndicator, inputIndicator: CloseValueIndicator(indicatorInput), - style: const LineStyle(color: Colors.white), + style: options.lineStyle, options: options, + lastTickIndicatorStyle: getLastIndicatorStyle( + options.lineStyle.color, + showLastIndicator: options.showLastIndicator, + ), ); - _signalMACDSeries = SingleIndicatorSeries( + signalMACDSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => signalMACDIndicator, inputIndicator: CloseValueIndicator(indicatorInput), - style: const LineStyle(color: Colors.redAccent), + style: options.signalLineStyle, options: options, + lastTickIndicatorStyle: getLastIndicatorStyle( + options.signalLineStyle.color, + showLastIndicator: options.showLastIndicator, + ), ); - _macdHistogramSeries = SingleIndicatorSeries( + macdHistogramSeries = SingleIndicatorSeries( painterCreator: (Series series) => BarPainter( series as DataSeries, checkColorCallback: ({ @@ -85,7 +101,7 @@ class MACDSeries extends Series { ), indicatorCreator: () => macdHistogramIndicator, inputIndicator: CloseValueIndicator(indicatorInput), - style: const BarStyle(), + style: options.barStyle, options: options, ); @@ -96,29 +112,29 @@ class MACDSeries extends Series { bool didUpdate(ChartData? oldData) { final MACDSeries? series = oldData as MACDSeries; - final bool macdUpdated = _macdSeries.didUpdate(series?._macdSeries); + final bool macdUpdated = macdSeries.didUpdate(series?.macdSeries); final bool signalMACDUpdated = - _signalMACDSeries.didUpdate(series?._signalMACDSeries); + signalMACDSeries.didUpdate(series?.signalMACDSeries); final bool macdHistogramUpdated = - _macdHistogramSeries.didUpdate(series?._macdHistogramSeries); + macdHistogramSeries.didUpdate(series?.macdHistogramSeries); return macdUpdated || signalMACDUpdated || macdHistogramUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _macdSeries.update(leftEpoch, rightEpoch); - _signalMACDSeries.update(leftEpoch, rightEpoch); - _macdHistogramSeries.update(leftEpoch, rightEpoch); + macdSeries.update(leftEpoch, rightEpoch); + signalMACDSeries.update(leftEpoch, rightEpoch); + macdHistogramSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ - [_macdSeries, _signalMACDSeries, _macdHistogramSeries] + [macdSeries, signalMACDSeries, macdHistogramSeries] .getMinValue(), - [_macdSeries, _signalMACDSeries, _macdHistogramSeries] + [macdSeries, signalMACDSeries, macdHistogramSeries] .getMaxValue() ]; @@ -132,25 +148,25 @@ class MACDSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _macdHistogramSeries.paint( + macdHistogramSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _macdSeries.paint( + macdSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _signalMACDSeries.paint( + signalMACDSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } @override int? getMaxEpoch() => [ - _macdSeries, - _signalMACDSeries, - _macdHistogramSeries, + macdSeries, + signalMACDSeries, + macdHistogramSeries, ].getMaxEpoch(); @override int? getMinEpoch() => [ - _macdSeries, - _signalMACDSeries, - _macdHistogramSeries, + macdSeries, + signalMACDSeries, + macdHistogramSeries, ].getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/alligator_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/alligator_options.dart index 7354af9c5..533491a82 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/alligator_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/alligator_options.dart @@ -1,3 +1,6 @@ +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; + import 'indicator_options.dart'; /// Alligator indicator options. @@ -9,7 +12,14 @@ class AlligatorOptions extends IndicatorOptions { this.lipsPeriod = 5, this.showLines = true, this.showFractal = false, - }) : super(); + this.jawOffset = 8, + this.teethOffset = 5, + this.lipsOffset = 3, + this.jawLineStyle = const LineStyle(color: Colors.blue), + this.teethLineStyle = const LineStyle(color: Colors.red), + this.lipsLineStyle = const LineStyle(color: Colors.green), + bool showLastIndicator = false, + }) : super(showLastIndicator: showLastIndicator); /// Smoothing period for jaw series final int jawPeriod; @@ -26,6 +36,24 @@ class AlligatorOptions extends IndicatorOptions { /// show fractal indicator or not final bool showFractal; + /// Shift to future in jaw series + final int jawOffset; + + /// Shift to future in teeth series + final int teethOffset; + + /// Shift to future in lips series + final int lipsOffset; + + /// Jaw line style. + final LineStyle jawLineStyle; + + /// Teeth line style. + final LineStyle teethLineStyle; + + /// Lips line style. + final LineStyle lipsLineStyle; + @override List get props => [ jawPeriod, @@ -33,5 +61,11 @@ class AlligatorOptions extends IndicatorOptions { lipsPeriod, showLines, showFractal, + jawOffset, + teethOffset, + lipsOffset, + jawLineStyle, + teethLineStyle, + lipsLineStyle, ]; } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/bollinger_bands_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/bollinger_bands_options.dart index 0fca379e1..926c006ee 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/bollinger_bands_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/bollinger_bands_options.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; + import '../ma_series.dart'; import 'indicator_options.dart'; @@ -8,11 +11,44 @@ class BollingerBandsOptions extends MAOptions { this.standardDeviationFactor = 2, int period = 20, MovingAverageType movingAverageType = MovingAverageType.simple, - }) : super(period: period, type: movingAverageType); + this.upperLineStyle = const LineStyle(color: Colors.white), + this.middleLineStyle = const LineStyle(color: Colors.white), + this.lowerLineStyle = const LineStyle(color: Colors.white), + this.fillColor = Colors.white12, + this.showChannelFill = true, + bool showLastIndicator = false, + }) : super( + period: period, + type: movingAverageType, + showLastIndicator: showLastIndicator, + ); /// Standard Deviation value final double standardDeviationFactor; + /// Upper line style. + final LineStyle upperLineStyle; + + /// Middle line style. + final LineStyle middleLineStyle; + + /// Lower line style. + final LineStyle lowerLineStyle; + + /// Fill color. + final Color fillColor; + + /// Whether the area between upper and lower channel is filled. + final bool showChannelFill; + @override - List get props => super.props..add(standardDeviationFactor); + List get props => super.props + ..addAll([ + standardDeviationFactor, + upperLineStyle, + middleLineStyle, + lowerLineStyle, + fillColor, + showChannelFill + ]); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/dpo_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/dpo_options.dart index 7707dc832..27de865e0 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/dpo_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/dpo_options.dart @@ -1,3 +1,5 @@ +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; + import '../ma_series.dart'; import 'indicator_options.dart'; @@ -8,7 +10,18 @@ class DPOOptions extends MAOptions { int period = 14, MovingAverageType movingAverageType = MovingAverageType.simple, this.isCentered = true, - }) : super(period: period, type: movingAverageType); + this.lineStyle, + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + period: period, + type: movingAverageType, + showLastIndicator: showLastIndicator, + pipSize: pipSize, + ); + + /// Line style. + final LineStyle? lineStyle; /// Wether the indicator should be calculated `Centered` or not. final bool isCentered; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ichimoku_clouds_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ichimoku_clouds_options.dart index 8901a04c2..e13e5f20a 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ichimoku_clouds_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ichimoku_clouds_options.dart @@ -7,7 +7,8 @@ class IchimokuCloudOptions extends IndicatorOptions { this.conversionLinePeriod = 9, this.baseLinePeriod = 26, this.leadingSpanBPeriod = 52, - }); + bool showLastIndicator = false, + }) : super(showLastIndicator: showLastIndicator); /// The `period` for the `IchimokuConversionLine`. Default is set to `9`. final int conversionLinePeriod; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/indicator_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/indicator_options.dart index 13115ac6c..ded6e7fc8 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/indicator_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/indicator_options.dart @@ -12,13 +12,27 @@ abstract class IndicatorOptions extends Equatable { /// Initializes /// /// Provides const constructor for sub-classes - const IndicatorOptions(); + const IndicatorOptions({ + this.showLastIndicator = false, + this.pipSize = 4, + }); + + /// Whether to show last indicator or not. + final bool showLastIndicator; + + /// Number of digits after decimal point in price. + final int pipSize; } /// Moving Average indicator options class MAOptions extends IndicatorOptions { /// Initializes - const MAOptions({this.period = 20, this.type = MovingAverageType.simple}); + const MAOptions({ + this.period = 20, + this.type = MovingAverageType.simple, + bool showLastIndicator = false, + int pipSize = 4, + }) : super(showLastIndicator: showLastIndicator, pipSize: pipSize); /// The average of this number of past data which will be calculated as /// MA value. diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ma_env_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ma_env_options.dart index 182c8d3f2..6c6a89c03 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ma_env_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/ma_env_options.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import '../ma_series.dart'; @@ -11,7 +13,17 @@ class MAEnvOptions extends MAOptions { this.shiftType = ShiftType.percent, int period = 50, MovingAverageType movingAverageType = MovingAverageType.simple, - }) : super(period: period, type: movingAverageType); + this.upperLineStyle = const LineStyle(color: Colors.green), + this.middleLineStyle = const LineStyle(color: Colors.blue), + this.lowerLineStyle = const LineStyle(color: Colors.red), + this.fillColor = Colors.white12, + this.showChannelFill = true, + bool showLastIndicator = false, + }) : super( + period: period, + type: movingAverageType, + showLastIndicator: showLastIndicator, + ); /// Shift value final double shift; @@ -19,8 +31,30 @@ class MAEnvOptions extends MAOptions { /// Shift type could be Percent or Point final ShiftType shiftType; + /// Upper line style. + final LineStyle upperLineStyle; + + /// Middle line style. + final LineStyle middleLineStyle; + + /// Lower line style. + final LineStyle lowerLineStyle; + + /// Fill color. + final Color fillColor; + + /// Whether the area between upper and lower channel is filled. + final bool showChannelFill; + @override List get props => super.props - ..add(shift) - ..add(shiftType); + ..addAll([ + shift, + shiftType, + upperLineStyle, + middleLineStyle, + lowerLineStyle, + fillColor, + showChannelFill + ]); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/macd_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/macd_options.dart index 5f86edf49..17fcd4fc8 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/macd_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/macd_options.dart @@ -1,3 +1,7 @@ +import 'package:deriv_chart/src/theme/painting_styles/bar_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; + import 'indicator_options.dart'; /// MACD Options. @@ -7,7 +11,15 @@ class MACDOptions extends IndicatorOptions { this.fastMAPeriod = 12, this.slowMAPeriod = 26, this.signalPeriod = 9, - }); + this.barStyle = const BarStyle(), + this.lineStyle = const LineStyle(color: Colors.white), + this.signalLineStyle = const LineStyle(color: Colors.redAccent), + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + showLastIndicator: showLastIndicator, + pipSize: pipSize, + ); /// The `period` for the `MACDFastMA`. Default is set to `12`. final int fastMAPeriod; @@ -18,6 +30,15 @@ class MACDOptions extends IndicatorOptions { /// The `period` for the `MACDSignal`. Default is set to `9`. final int signalPeriod; + /// Histogram bar style + final BarStyle barStyle; + + /// Line style. + final LineStyle lineStyle; + + /// Signal line style. + final LineStyle signalLineStyle; + @override List get props => [fastMAPeriod, slowMAPeriod, signalPeriod]; } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rainbow_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rainbow_options.dart index d5e65d627..ab7103063 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rainbow_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rainbow_options.dart @@ -8,7 +8,12 @@ class RainbowOptions extends MAOptions { this.bandsCount = 10, int period = 50, MovingAverageType movingAverageType = MovingAverageType.simple, - }) : super(period: period, type: movingAverageType); + bool showLastIndicator = false, + }) : super( + period: period, + type: movingAverageType, + showLastIndicator: showLastIndicator, + ); /// number of rainbow bands final int bandsCount; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/roc_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/roc_options.dart index 0eb22c8a3..2b68b410f 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/roc_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/roc_options.dart @@ -3,7 +3,14 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie /// ROC indicator options. class ROCOptions extends IndicatorOptions { /// Initializes - const ROCOptions({this.period = 14}); + const ROCOptions({ + this.period = 14, + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + pipSize: pipSize, + showLastIndicator: showLastIndicator, + ); /// The Period final int period; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rsi_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rsi_options.dart index b5cef0357..2a0cbb9e7 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rsi_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/rsi_options.dart @@ -3,7 +3,14 @@ import 'indicator_options.dart'; /// RSI indicator options. class RSIOptions extends IndicatorOptions { /// Initializes an RSI indicator options. - const RSIOptions({this.period = 14}); + const RSIOptions({ + this.period = 14, + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + pipSize: pipSize, + showLastIndicator: showLastIndicator, + ); /// The period to calculate rsi Indicator on. final int period; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/smi_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/smi_options.dart index 8b434ecc5..72214fdfc 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/smi_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/smi_options.dart @@ -1,5 +1,6 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/ma_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/indicator_options.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; /// SMI Options class SMIOptions extends IndicatorOptions { @@ -12,7 +13,14 @@ class SMIOptions extends IndicatorOptions { period: 10, type: MovingAverageType.exponential, ), - }); + this.lineStyle, + this.signalLineStyle, + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + showLastIndicator: showLastIndicator, + pipSize: pipSize, + ); /// Period final int period; @@ -26,6 +34,12 @@ class SMIOptions extends IndicatorOptions { /// SMI signal options. final MAOptions signalOptions; + /// Line style. + final LineStyle? lineStyle; + + /// Signal line style. + final LineStyle? signalLineStyle; + @override List get props => [ period, diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/stochastic_oscillator_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/stochastic_oscillator_options.dart index 66343708a..d92fe0b82 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/stochastic_oscillator_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/stochastic_oscillator_options.dart @@ -3,7 +3,15 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie /// StochasticOscillator indicator options. class StochasticOscillatorOptions extends IndicatorOptions { /// Initializes an StochasticOscillator indicator options. - const StochasticOscillatorOptions({this.period = 14, this.isSmooth = true}); + const StochasticOscillatorOptions({ + this.period = 14, + this.isSmooth = true, + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + showLastIndicator: showLastIndicator, + pipSize: pipSize, + ); /// The period to calculate Stochastic Oscillator Indicator on. final int period; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/williams_r_options.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/williams_r_options.dart index a0b0ce247..c437e0454 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/williams_r_options.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/williams_r_options.dart @@ -3,7 +3,14 @@ import 'indicator_options.dart'; /// PSAR options class WilliamsROptions extends IndicatorOptions { /// Initializes - const WilliamsROptions(this.period); + const WilliamsROptions( + this.period, { + bool showLastIndicator = false, + int pipSize = 4, + }) : super( + pipSize: pipSize, + showLastIndicator: showLastIndicator, + ); /// Period final int period; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/parabolic_sar/parabolic_sar_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/parabolic_sar/parabolic_sar_series.dart index 611b08f72..a7a0a1044 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/parabolic_sar/parabolic_sar_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/parabolic_sar/parabolic_sar_series.dart @@ -1,3 +1,4 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_painters/scatter_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/parabolic_sar_options.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; @@ -33,6 +34,16 @@ class ParabolicSARSeries extends AbstractSingleIndicatorSeries { @override SeriesPainter createPainter() => ScatterPainter(this); + @override + bool shouldRepaint(ChartData? oldDelegate) { + if (oldDelegate == null) { + return true; + } + + final ParabolicSARSeries oldSeries = oldDelegate as ParabolicSARSeries; + return options != oldSeries.options || style != oldSeries.style; + } + @override CachedIndicator initializeIndicator() => CustomParabolicSarIndicator( _indicatorInput, diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/roc_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/roc_series.dart index 0f0e3c54d..4b0a5acba 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/roc_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/roc_series.dart @@ -1,22 +1,28 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/abstract_single_indicator_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/roc_options.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; -import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; +import '../series.dart'; +import '../series_painter.dart'; + /// ROC series. class ROCSeries extends AbstractSingleIndicatorSeries { /// Initializes an ROC Indicator. ROCSeries( IndicatorInput indicatorInput, { required ROCOptions rocOptions, + LineStyle? lineStyle, String? id, }) : this.fromIndicator( CloseValueIndicator(indicatorInput), rocOptions: rocOptions, + lineStyle: lineStyle, id: id ?? 'ROCIndicator', ); @@ -24,12 +30,20 @@ class ROCSeries extends AbstractSingleIndicatorSeries { ROCSeries.fromIndicator( Indicator inputIndicator, { required this.rocOptions, + LineStyle? lineStyle, String? id, }) : _inputIndicator = inputIndicator, super( inputIndicator, id ?? 'ROCIndicator', options: rocOptions, + style: lineStyle, + lastTickIndicatorStyle: lineStyle != null + ? getLastIndicatorStyle( + lineStyle.color, + showLastIndicator: rocOptions.showLastIndicator, + ) + : null, ); final Indicator _inputIndicator; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/rsi_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/rsi_series.dart index cb8b13ba0..38cd0ede3 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/rsi_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/rsi_series.dart @@ -1,11 +1,13 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/functions/helper_functions.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; +import '../series.dart'; import '../series_painter.dart'; import 'abstract_single_indicator_series.dart'; import 'models/rsi_options.dart'; @@ -39,6 +41,10 @@ class RSISeries extends AbstractSingleIndicatorSeries { id ?? 'RSIIndicator', options: rsiOptions, style: config.lineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.lineStyle.color, + showLastIndicator: rsiOptions.showLastIndicator, + ), ); final Indicator _inputIndicator; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/sample_multi_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/sample_multi_series.dart index 418494ada..59a789a5a 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/sample_multi_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/sample_multi_series.dart @@ -1,13 +1,15 @@ import 'dart:math'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:flutter/material.dart'; import '../../chart_data.dart'; +import '../series.dart'; import '../series_painter.dart'; +import 'ma_series.dart'; import 'models/indicator_options.dart'; import 'sample_multi_painter.dart'; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/smi_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/smi_series.dart index 7d7a6a23f..874e7ab12 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/smi_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/smi_series.dart @@ -1,4 +1,3 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/single_indicator_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; @@ -6,10 +5,17 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; +import '../data_series.dart'; +import '../series.dart'; +import 'ma_series.dart'; import 'models/smi_options.dart'; /// Stochastic Momentum Index series class. @@ -23,8 +29,11 @@ class SMISeries extends Series { String? id, }) : super(id ?? 'SMI'); - late SingleIndicatorSeries _smiSeries; - late SingleIndicatorSeries _smiSignalSeries; + /// SMI series. + late SingleIndicatorSeries smiSeries; + + /// SMI signal series. + late SingleIndicatorSeries smiSignalSeries; late List _innerSeries; @@ -47,9 +56,9 @@ class SMISeries extends Series { period: smiOptions.period, smoothingPeriod: smiOptions.smoothingPeriod, doubleSmoothingPeriod: smiOptions.doubleSmoothingPeriod, - ); + )..calculateValues(); - _smiSeries = SingleIndicatorSeries( + smiSeries = SingleIndicatorSeries( painterCreator: (Series series) => OscillatorLinePainter( series as DataSeries, topHorizontalLine: overboughtValue, @@ -57,21 +66,34 @@ class SMISeries extends Series { secondaryHorizontalLinesStyle: const LineStyle(), ), indicatorCreator: () => smiIndicator, + style: smiOptions.lineStyle, options: smiOptions, inputIndicator: CloseValueIndicator(input), + lastTickIndicatorStyle: smiOptions.lineStyle != null + ? getLastIndicatorStyle( + smiOptions.lineStyle!.color, + showLastIndicator: smiOptions.showLastIndicator, + ) + : null, ); - _smiSignalSeries = SingleIndicatorSeries( + smiSignalSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => MASeries.getMAIndicator(smiIndicator, smiOptions.signalOptions), inputIndicator: smiIndicator, - style: const LineStyle(color: Colors.red), + style: smiOptions.signalLineStyle ?? const LineStyle(color: Colors.red), options: smiOptions, + lastTickIndicatorStyle: smiOptions.signalLineStyle != null + ? getLastIndicatorStyle( + smiOptions.signalLineStyle!.color, + showLastIndicator: smiOptions.showLastIndicator, + ) + : null, ); - _innerSeries = [_smiSeries, _smiSignalSeries]; + _innerSeries = [smiSeries, smiSignalSeries]; return null; } @@ -80,23 +102,23 @@ class SMISeries extends Series { bool didUpdate(ChartData? oldData) { final SMISeries? oldSeries = oldData as SMISeries?; - final bool smiUpdated = _smiSeries.didUpdate(oldSeries?._smiSeries); + final bool smiUpdated = smiSeries.didUpdate(oldSeries?.smiSeries); final bool smiSignalUpdated = - _smiSignalSeries.didUpdate(oldSeries?._smiSignalSeries); + smiSignalSeries.didUpdate(oldSeries?.smiSignalSeries); return smiUpdated || smiSignalUpdated; } @override - int? getMaxEpoch() => _smiSeries.getMaxEpoch(); + int? getMaxEpoch() => smiSeries.getMaxEpoch(); @override - int? getMinEpoch() => _smiSeries.getMinEpoch(); + int? getMinEpoch() => smiSeries.getMinEpoch(); @override void onUpdate(int leftEpoch, int rightEpoch) { - _smiSeries.update(leftEpoch, rightEpoch); - _smiSignalSeries.update(leftEpoch, rightEpoch); + smiSeries.update(leftEpoch, rightEpoch); + smiSignalSeries.update(leftEpoch, rightEpoch); } @override @@ -115,9 +137,9 @@ class SMISeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _smiSeries.paint( + smiSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _smiSignalSeries.paint( + smiSignalSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/stochastic_oscillator_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/stochastic_oscillator_series.dart index cbeb92be4..0e4c6eb63 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/stochastic_oscillator_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/stochastic_oscillator_series.dart @@ -8,10 +8,10 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_serie import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/chart_theme.dart'; -import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; import 'package:flutter/material.dart'; @@ -27,8 +27,11 @@ class StochasticOscillatorSeries extends Series { String? id, }) : super(id ?? 'StochasticOscillator'); - late SingleIndicatorSeries _fastPercentStochasticIndicatorSeries; - late SingleIndicatorSeries _slowStochasticIndicatorSeries; + /// Fast percent StochasticOscillator indicator series. + late SingleIndicatorSeries fastPercentStochasticIndicatorSeries; + + /// Slow percent StochasticOscillator indicator series. + late SingleIndicatorSeries slowStochasticIndicatorSeries; ///input data final Indicator inputIndicator; @@ -54,7 +57,7 @@ class StochasticOscillatorSeries extends Series { SmoothedFastStochasticIndicator(fastStochasticIndicator, period: stochasticOscillatorOptions.period); - _fastPercentStochasticIndicatorSeries = SingleIndicatorSeries( + fastPercentStochasticIndicatorSeries = SingleIndicatorSeries( painterCreator: (Series series) => config.showZones ? OscillatorLinePainter( series as DataSeries, @@ -69,20 +72,28 @@ class StochasticOscillatorSeries extends Series { indicatorCreator: () => smoothedFastStochasticIndicator, inputIndicator: inputIndicator, options: stochasticOscillatorOptions, - style: const LineStyle(color: Colors.white), + style: config.fastLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.fastLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _slowStochasticIndicatorSeries = SingleIndicatorSeries( + slowStochasticIndicatorSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => SmoothedSlowStochasticIndicator(slowStochasticIndicator), inputIndicator: inputIndicator, options: stochasticOscillatorOptions, - style: const LineStyle(color: Colors.red), + style: config.slowLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.slowLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); } else { - _fastPercentStochasticIndicatorSeries = SingleIndicatorSeries( + fastPercentStochasticIndicatorSeries = SingleIndicatorSeries( painterCreator: (Series series) => config.showZones ? OscillatorLinePainter( series as DataSeries, @@ -97,16 +108,24 @@ class StochasticOscillatorSeries extends Series { indicatorCreator: () => fastStochasticIndicator, options: stochasticOscillatorOptions, inputIndicator: inputIndicator, - style: const LineStyle(color: Colors.white), + style: config.fastLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.fastLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); - _slowStochasticIndicatorSeries = SingleIndicatorSeries( + slowStochasticIndicatorSeries = SingleIndicatorSeries( painterCreator: (Series series) => LinePainter(series as DataSeries), indicatorCreator: () => slowStochasticIndicator, inputIndicator: inputIndicator, options: stochasticOscillatorOptions, - style: const LineStyle(color: Colors.red), + style: config.slowLineStyle, + lastTickIndicatorStyle: getLastIndicatorStyle( + config.slowLineStyle.color, + showLastIndicator: config.showLastIndicator, + ), ); } return null; @@ -116,29 +135,29 @@ class StochasticOscillatorSeries extends Series { bool didUpdate(ChartData? oldData) { final StochasticOscillatorSeries? series = oldData as StochasticOscillatorSeries?; - final bool _fastUpdated = _fastPercentStochasticIndicatorSeries - .didUpdate(series?._fastPercentStochasticIndicatorSeries); - final bool _slowUpdated = _slowStochasticIndicatorSeries - .didUpdate(series?._slowStochasticIndicatorSeries); + final bool _fastUpdated = fastPercentStochasticIndicatorSeries + .didUpdate(series?.fastPercentStochasticIndicatorSeries); + final bool _slowUpdated = slowStochasticIndicatorSeries + .didUpdate(series?.slowStochasticIndicatorSeries); return _fastUpdated || _slowUpdated; } @override void onUpdate(int leftEpoch, int rightEpoch) { - _fastPercentStochasticIndicatorSeries.update(leftEpoch, rightEpoch); - _slowStochasticIndicatorSeries.update(leftEpoch, rightEpoch); + fastPercentStochasticIndicatorSeries.update(leftEpoch, rightEpoch); + slowStochasticIndicatorSeries.update(leftEpoch, rightEpoch); } @override List recalculateMinMax() => [ [ - _fastPercentStochasticIndicatorSeries, - _slowStochasticIndicatorSeries, + fastPercentStochasticIndicatorSeries, + slowStochasticIndicatorSeries, ].getMinValue(), [ - _fastPercentStochasticIndicatorSeries, - _slowStochasticIndicatorSeries, + fastPercentStochasticIndicatorSeries, + slowStochasticIndicatorSeries, ].getMaxValue() ]; @@ -152,21 +171,21 @@ class StochasticOscillatorSeries extends Series { ChartConfig chartConfig, ChartTheme theme, ) { - _fastPercentStochasticIndicatorSeries.paint( + fastPercentStochasticIndicatorSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); - _slowStochasticIndicatorSeries.paint( + slowStochasticIndicatorSeries.paint( canvas, size, epochToX, quoteToY, animationInfo, chartConfig, theme); } @override int? getMaxEpoch() => [ - _fastPercentStochasticIndicatorSeries, - _slowStochasticIndicatorSeries, + fastPercentStochasticIndicatorSeries, + slowStochasticIndicatorSeries, ].getMaxEpoch(); @override int? getMinEpoch() => [ - _fastPercentStochasticIndicatorSeries, - _slowStochasticIndicatorSeries, + fastPercentStochasticIndicatorSeries, + slowStochasticIndicatorSeries, ].getMinEpoch(); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/williams_r_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/williams_r_series.dart index 9af82f064..d7aa3ab93 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/williams_r_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/williams_r_series.dart @@ -1,5 +1,6 @@ import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/indicator.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; @@ -19,13 +20,23 @@ class WilliamsRSeries extends AbstractSingleIndicatorSeries { this.overboughtValue = -20, this.oversoldValue = -80, this.showZones = true, - this.overboughtSoldLineStyles = + this.overboughtLineStyle = const LineStyle(color: Colors.white, thickness: 0.5), + this.oversoldLineStyle = + const LineStyle(color: Colors.white, thickness: 0.5), + LineStyle? lineStyle, String? id, }) : super( CloseValueIndicator(_indicatorDataInput), id ?? 'WilliamsR', options: _options, + style: lineStyle, + lastTickIndicatorStyle: lineStyle != null + ? getLastIndicatorStyle( + lineStyle.color, + showLastIndicator: _options.showLastIndicator, + ) + : null, ); final IndicatorDataInput _indicatorDataInput; @@ -38,8 +49,11 @@ class WilliamsRSeries extends AbstractSingleIndicatorSeries { /// Oversold value final double oversoldValue; - /// The line style for overbought/sold horizontal lines. - final LineStyle overboughtSoldLineStyles; + /// LineStyle of overbought line + final LineStyle overboughtLineStyle; + + /// LineStyle of oversold line + final LineStyle oversoldLineStyle; /// Whether to show overbought/sold lines and zones channel fill. final bool showZones; @@ -50,10 +64,11 @@ class WilliamsRSeries extends AbstractSingleIndicatorSeries { this, topHorizontalLine: overboughtValue, bottomHorizontalLine: oversoldValue, - secondaryHorizontalLinesStyle: overboughtSoldLineStyles, + secondaryHorizontalLinesStyle: overboughtLineStyle, // TODO(NA): Zero line style will be removed from // OscillatorLinePainter - topHorizontalLinesStyle: overboughtSoldLineStyles, + topHorizontalLinesStyle: overboughtLineStyle, + bottomHorizontalLinesStyle: oversoldLineStyle, ) : LinePainter(this); diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/zigzag_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/zigzag_series.dart index 89513c5a9..bbc400ac6 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/zigzag_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/zigzag_series.dart @@ -1,3 +1,4 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; @@ -29,11 +30,20 @@ class ZigZagSeries extends LineSeries { @override SeriesPainter> createPainter() => LinePainter(this); + @override + bool shouldRepaint(ChartData? oldDelegate) { + if (oldDelegate == null) { + return true; + } + + return style != (oldDelegate as ZigZagSeries).style; + } + @override VisibleEntries getVisibleEntries(int startIndex, int endIndex) { int firstIndex = startIndex; int lastIndex = endIndex; - if (entries == null || startIndex < 0 || endIndex >= entries!.length) { + if (entries == null || startIndex < 0 || endIndex - 1 > entries!.length) { return VisibleEntries.empty(); } if (entries![startIndex].quote.isNaN) { diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart index ae0da50dc..daa137656 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/channel_fill_painter.dart @@ -1,12 +1,16 @@ import 'dart:ui' as ui; -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/helpers/combine_paths.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../../chart_data.dart'; import '../data_painter.dart'; +import '../data_series.dart'; /// A [DataPainter] for painting two line data and the channel fill inside /// of them. @@ -91,10 +95,24 @@ class ChannelFillPainter extends DataPainter> { addAreaPath(canvas, size, firstLineAreaPath, firstDataPathInfo.startPosition.dx, firstDataPathInfo.endPosition.dx); - final Path firstUpperChannelFill = Path.combine( - PathOperation.intersect, channelFillPath, firstLineAreaPath); - final Path secondUpperChannelFill = Path.combine( - PathOperation.difference, channelFillPath, firstLineAreaPath); + Path firstUpperChannelFill, secondUpperChannelFill; + + if (kIsWeb) { + final (Path, Path) paths = combinePaths( + firstSeries, + firstSeries.entries ?? [], + secondSeries.entries ?? [], + epochToX, + quoteToY); + + firstUpperChannelFill = paths.$1; + secondUpperChannelFill = paths.$2; + } else { + firstUpperChannelFill = Path.combine( + PathOperation.intersect, channelFillPath, firstLineAreaPath); + secondUpperChannelFill = Path.combine( + PathOperation.difference, channelFillPath, firstLineAreaPath); + } canvas ..drawPath(firstDataPathInfo.path, firstLinePaint) diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart index 35fa4ad54..3d9e54af3 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_painter.dart @@ -1,11 +1,13 @@ import 'dart:ui' as ui; -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; -import '../../chart_data.dart'; import '../data_painter.dart'; +import '../data_series.dart'; /// A [DataPainter] for painting line data. class LinePainter extends DataPainter> { diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_series.dart index 8318e55a7..ea55f85d0 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/line_series.dart @@ -1,6 +1,10 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; import 'package:flutter/material.dart'; +import '../data_series.dart'; import '../series_painter.dart'; import 'line_painter.dart'; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart index 9b2c057e3..fbd6b085f 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_painter.dart @@ -1,10 +1,15 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/helpers/combine_paths.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/functions/helper_functions.dart'; import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../chart_data.dart'; +import '../data_series.dart'; import 'line_painter.dart'; /// A [LinePainter] for painting line with two main top and bottom @@ -75,40 +80,74 @@ class OscillatorLinePainter extends LinePainter { canvas.drawPath(linePath.path, _linePaint!); if (_topHorizontalLine != null) { - final Path bottomAreaPath = Path.from(linePath.path) - ..lineTo(linePath.endPosition.dx, size.height) - ..lineTo(linePath.startPosition.dx, size.height); - final Path topRect = Path() - ..addRect( - Rect.fromLTRB( - linePath.startPosition.dx, - 0, - linePath.endPosition.dx, - quoteToY(_topHorizontalLine!), - ), - ); - - final Path topIntersections = - Path.combine(PathOperation.intersect, bottomAreaPath, topRect); + Path topIntersections; + + if (kIsWeb) { + final List horizontalLineEntries = series.entries! + .map((Tick entry) => + Tick(epoch: entry.epoch, quote: _topHorizontalLine!)) + .toList(); + topIntersections = combinePaths( + series, + series.entries ?? [], + horizontalLineEntries, + epochToX, + quoteToY, + ).$1; + } else { + final Path bottomAreaPath = Path.from(linePath.path) + ..lineTo(linePath.endPosition.dx, size.height) + ..lineTo(linePath.startPosition.dx, size.height); + final Path topRect = Path() + ..addRect( + Rect.fromLTRB( + linePath.startPosition.dx, + 0, + linePath.endPosition.dx, + quoteToY(_topHorizontalLine!), + ), + ); + + topIntersections = + Path.combine(PathOperation.intersect, bottomAreaPath, topRect); + } + canvas.drawPath(topIntersections, _topZonesPaint); } if (_bottomHorizontalLine != null) { - final Path topAreaPath = Path.from(linePath.path) - ..lineTo(linePath.endPosition.dx, 0) - ..lineTo(linePath.startPosition.dx, 0); - final Path bottomRect = Path() - ..addRect( - Rect.fromLTRB( - linePath.startPosition.dx, - quoteToY(_bottomHorizontalLine!), - linePath.endPosition.dx, - size.height, - ), - ); - - final Path bottomIntersection = - Path.combine(PathOperation.intersect, topAreaPath, bottomRect); + Path bottomIntersection; + + if (kIsWeb) { + final List horizontalLineEntries = series.entries! + .map((Tick entry) => + Tick(epoch: entry.epoch, quote: _bottomHorizontalLine!)) + .toList(); + + bottomIntersection = combinePaths( + series, + series.entries ?? [], + horizontalLineEntries, + epochToX, + quoteToY, + ).$2; + } else { + final Path topAreaPath = Path.from(linePath.path) + ..lineTo(linePath.endPosition.dx, 0) + ..lineTo(linePath.startPosition.dx, 0); + final Path bottomRect = Path() + ..addRect( + Rect.fromLTRB( + linePath.startPosition.dx, + quoteToY(_bottomHorizontalLine!), + linePath.endPosition.dx, + size.height, + ), + ); + bottomIntersection = + Path.combine(PathOperation.intersect, topAreaPath, bottomRect); + } + canvas.drawPath(bottomIntersection, _bottomZonesPaint); } diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_series.dart index 1c6e87f3b..972701099 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/line_series/oscillator_line_series.dart @@ -1,6 +1,9 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import '../data_series.dart'; import '../series_painter.dart'; +import 'line_series.dart'; import 'oscillator_line_painter.dart'; /// Oscillator Line Series. @@ -30,6 +33,7 @@ class OscillatorLineSeries extends LineSeries { final double _topHorizontalLine; final double _bottomHorizontalLine; final LineStyle? _secondaryHorizontalLinesStyle; + // ignore: unused_field final LineStyle? _mainHorizontalLinesStyle; diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painter.dart index dc1634d8b..dd7e4375d 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painter.dart @@ -1,18 +1,14 @@ -import 'dart:ui' as ui; - -import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/candle.dart'; import 'package:deriv_chart/src/theme/painting_styles/candle_style.dart'; import 'package:flutter/material.dart'; -import '../../../chart_data.dart'; import '../../data_painter.dart'; import '../../data_series.dart'; -import '../../indexed_entry.dart'; -import 'candle_painting.dart'; +import '../ohlc_painting.dart'; +import '../ohlc_painter.dart'; /// A [DataPainter] for painting CandleStick data. -class CandlePainter extends DataPainter> { +class CandlePainter extends OhlcPainter { /// Initializes CandlePainter(DataSeries series) : super(series); @@ -21,132 +17,51 @@ class CandlePainter extends DataPainter> { late Paint _negativeCandlePaint; @override - void onPaintData( + void onPaintCandle( Canvas canvas, - Size size, - EpochToX epochToX, - QuoteToY quoteToY, - AnimationInfo animationInfo, + OhlcPainting currentPainting, + OhlcPainting prevPainting, ) { - if (series.entries == null || series.visibleEntries.length < 2) { - return; - } - final CandleStyle style = series.style as CandleStyle? ?? theme.candleStyle; _linePaint = Paint() - ..color = style.lineColor + ..color = style.neutralColor ..strokeWidth = 1.2; _positiveCandlePaint = Paint()..color = style.positiveColor; _negativeCandlePaint = Paint()..color = style.negativeColor; - final double intervalWidth = - epochToX(chartConfig.granularity) - epochToX(0); - - final double candleWidth = intervalWidth * 0.6; - - // Painting visible candles except the last one that might be animated. - for (int i = series.visibleEntries.startIndex; - i < series.visibleEntries.endIndex - 1; - i++) { - final Candle candle = series.entries![i]; - - _paintCandle( - canvas, - CandlePainting( - width: candleWidth, - xCenter: epochToX(getEpochOf(candle, i)), - yHigh: quoteToY(candle.high), - yLow: quoteToY(candle.low), - yOpen: quoteToY(candle.open), - yClose: quoteToY(candle.close), - ), - ); - } - - // Painting last visible candle - final Candle lastCandle = series.entries!.last; - final Candle lastVisibleCandle = series.visibleEntries.last; - - CandlePainting lastCandlePainting; - - if (lastCandle == lastVisibleCandle && series.prevLastEntry != null) { - final IndexedEntry prevLastCandle = series.prevLastEntry!; - - final double animatedYClose = quoteToY(ui.lerpDouble( - prevLastCandle.entry.close, - lastCandle.close, - animationInfo.currentTickPercent, - )!); - - final double xCenter = ui.lerpDouble( - epochToX(getEpochOf(prevLastCandle.entry, prevLastCandle.index)), - epochToX(getEpochOf(lastCandle, series.entries!.length - 1)), - animationInfo.currentTickPercent, - )!; - - lastCandlePainting = CandlePainting( - xCenter: xCenter, - yHigh: lastCandle.high > prevLastCandle.entry.high - // In this case we don't update high-low line to avoid instant - // change of its height (ahead of animation). Candle close value - // animation will cover the line. - ? quoteToY(prevLastCandle.entry.high) - : quoteToY(lastCandle.high), - yLow: lastCandle.low < prevLastCandle.entry.low - // Same as comment above. - ? quoteToY(prevLastCandle.entry.low) - : quoteToY(lastCandle.low), - yOpen: quoteToY(lastCandle.open), - yClose: animatedYClose, - width: candleWidth, - ); - } else { - lastCandlePainting = CandlePainting( - xCenter: epochToX( - getEpochOf(lastVisibleCandle, series.visibleEntries.endIndex - 1)), - yHigh: quoteToY(lastVisibleCandle.high), - yLow: quoteToY(lastVisibleCandle.low), - yOpen: quoteToY(lastVisibleCandle.open), - yClose: quoteToY(lastVisibleCandle.close), - width: candleWidth, - ); - } - - _paintCandle(canvas, lastCandlePainting); - } - - void _paintCandle(Canvas canvas, CandlePainting cp) { canvas.drawLine( - Offset(cp.xCenter, cp.yHigh), - Offset(cp.xCenter, cp.yLow), + Offset(currentPainting.xCenter, currentPainting.yHigh), + Offset(currentPainting.xCenter, currentPainting.yLow), _linePaint, ); - if (cp.yOpen == cp.yClose) { + if (currentPainting.yOpen == currentPainting.yClose) { canvas.drawLine( - Offset(cp.xCenter - cp.width / 2, cp.yOpen), - Offset(cp.xCenter + cp.width / 2, cp.yOpen), + Offset(currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yOpen), + Offset(currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yOpen), _linePaint, ); - } else if (cp.yOpen > cp.yClose) { + } else if (currentPainting.yOpen > currentPainting.yClose) { canvas.drawRect( Rect.fromLTRB( - cp.xCenter - cp.width / 2, - cp.yClose, - cp.xCenter + cp.width / 2, - cp.yOpen, + currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yClose, + currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yOpen, ), _positiveCandlePaint, ); } else { canvas.drawRect( Rect.fromLTRB( - cp.xCenter - cp.width / 2, - cp.yOpen, - cp.xCenter + cp.width / 2, - cp.yClose, + currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yOpen, + currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yClose, ), _negativeCandlePaint, ); diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painting.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painting.dart deleted file mode 100644 index 97aeb3f1c..000000000 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/candle/candle_painting.dart +++ /dev/null @@ -1,30 +0,0 @@ -/// The required painting properties of a candle. -class CandlePainting { - /// Initialzes the required painting properties of a candle. - const CandlePainting({ - required this.xCenter, - required this.yHigh, - required this.yLow, - required this.yOpen, - required this.yClose, - required this.width, - }); - - /// The center X position of the candle. - final double xCenter; - - /// Y position of the candle's high value. - final double yHigh; - - /// Y position of the candle's low value. - final double yLow; - - /// Y position of the candle's open value. - final double yOpen; - - /// Y position of the candle's close value. - final double yClose; - - /// The width of the candle. - final double width; -} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_painter.dart new file mode 100644 index 000000000..8f84ceb3d --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_painter.dart @@ -0,0 +1,106 @@ +import 'package:deriv_chart/src/models/candle.dart'; +import 'package:deriv_chart/src/theme/painting_styles/candle_style.dart'; +import 'package:flutter/material.dart'; + +import '../../data_painter.dart'; +import '../../data_series.dart'; +import '../ohlc_painting.dart'; +import '../ohlc_painter.dart'; + +/// A [DataPainter] for painting Hollow CandleStick data. +class HollowCandlePainter extends OhlcPainter { + /// Initializes + HollowCandlePainter(DataSeries series) : super(series); + + late Color _positiveColor; + late Color _negativeColor; + late Color _neutralColor; + + @override + void onPaintCandle( + Canvas canvas, + OhlcPainting currentPainting, + OhlcPainting prevPainting, + ) { + final CandleStyle style = series.style as CandleStyle? ?? theme.candleStyle; + + _positiveColor = style.positiveColor; + _negativeColor = style.negativeColor; + _neutralColor = style.neutralColor; + + final Color _candleColor = currentPainting.yClose > prevPainting.yClose + ? _negativeColor + : currentPainting.yClose < prevPainting.yClose + ? _positiveColor + : _neutralColor; + + _drawWick(canvas, _candleColor, currentPainting); + + if (currentPainting.yOpen == currentPainting.yClose) { + _drawLine(canvas, _candleColor, currentPainting); + } else if (currentPainting.yOpen < currentPainting.yClose) { + _drawFilledRect(canvas, _candleColor, currentPainting); + } else { + _drawHollowRect(canvas, _candleColor, currentPainting); + } + } + + void _drawWick(Canvas canvas, Color color, OhlcPainting currentPainting) { + canvas + ..drawLine( + Offset(currentPainting.xCenter, currentPainting.yHigh), + Offset(currentPainting.xCenter, currentPainting.yClose), + Paint() + ..color = color + ..strokeWidth = 1.2, + ) + ..drawLine( + Offset(currentPainting.xCenter, currentPainting.yLow), + Offset(currentPainting.xCenter, currentPainting.yOpen), + Paint() + ..color = color + ..strokeWidth = 1.2, + ); + } + + void _drawFilledRect( + Canvas canvas, Color color, OhlcPainting currentPainting) { + canvas.drawRect( + Rect.fromLTRB( + currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yClose, + currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yOpen, + ), + Paint()..color = color, + ); + } + + void _drawHollowRect( + Canvas canvas, Color color, OhlcPainting currentPainting) { + canvas.drawRect( + Rect.fromLTRB( + currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yOpen, + currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yClose, + ), + Paint() + ..color = color + ..strokeWidth = 1 + ..style = PaintingStyle.stroke, + ); + } + + void _drawLine(Canvas canvas, Color color, OhlcPainting currentPainting) { + canvas.drawLine( + Offset(currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yOpen), + Offset(currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yOpen), + Paint() + ..color = color + ..strokeWidth = 1.2, + ); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_series.dart new file mode 100644 index 000000000..4872aa561 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/hollow_candle/hollow_candle_series.dart @@ -0,0 +1,28 @@ +import 'package:deriv_chart/src/models/candle.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/candle_style.dart'; + +import '../../data_series.dart'; +import '../../series_painter.dart'; +import '../ohlc_type_series.dart'; +import 'hollow_candle_painter.dart'; + +/// Hollow CandleStick series +class HollowCandleSeries extends OHLCTypeSeries { + /// Initializes + HollowCandleSeries( + List entries, { + String? id, + CandleStyle? style, + HorizontalBarrierStyle? lastTickIndicatorStyle, + }) : super( + entries, + id ?? 'HollowCandleSeries', + style: style, + lastTickIndicatorStyle: lastTickIndicatorStyle, + ); + + @override + SeriesPainter> createPainter() => + HollowCandlePainter(this); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_painter.dart new file mode 100644 index 000000000..e85181493 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_painter.dart @@ -0,0 +1,74 @@ +import 'package:deriv_chart/src/models/candle.dart'; +import 'package:deriv_chart/src/theme/painting_styles/candle_style.dart'; +import 'package:flutter/material.dart'; + +import '../../data_painter.dart'; +import '../../data_series.dart'; +import '../ohlc_painting.dart'; +import '../ohlc_painter.dart'; + +/// A [DataPainter] for painting Ohlc CandleStick data. +class OhlcCandlePainter extends OhlcPainter { + /// Initializes + OhlcCandlePainter(DataSeries series) : super(series); + + late Color _positiveColor; + late Color _negativeColor; + late Color _neutralColor; + + @override + void onPaintCandle( + Canvas canvas, + OhlcPainting currentPainting, + OhlcPainting prevPainting, + ) { + final CandleStyle style = series.style as CandleStyle? ?? theme.candleStyle; + + _positiveColor = style.positiveColor; + _negativeColor = style.negativeColor; + _neutralColor = style.neutralColor; + + final Color _candleColor = currentPainting.yClose > prevPainting.yClose + ? _negativeColor + : currentPainting.yClose < prevPainting.yClose + ? _positiveColor + : _neutralColor; + + _drawWick(canvas, _candleColor, currentPainting); + _drawOpenCloseLines(canvas, _candleColor, currentPainting); + } + + void _drawWick(Canvas canvas, Color color, OhlcPainting currentPainting) { + canvas.drawLine( + Offset(currentPainting.xCenter, currentPainting.yHigh), + Offset(currentPainting.xCenter, currentPainting.yLow), + Paint() + ..color = color + ..strokeWidth = 1.2, + ); + } + + void _drawOpenCloseLines( + Canvas canvas, Color color, OhlcPainting currentPainting) { + // Paint openning + canvas + ..drawLine( + Offset(currentPainting.xCenter - currentPainting.width / 2, + currentPainting.yOpen), + Offset(currentPainting.xCenter, currentPainting.yOpen), + Paint() + ..color = color + ..strokeWidth = 1.2, + ) + + // Paint closing + ..drawLine( + Offset(currentPainting.xCenter + currentPainting.width / 2, + currentPainting.yClose), + Offset(currentPainting.xCenter, currentPainting.yClose), + Paint() + ..color = color + ..strokeWidth = 1.2, + ); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_series.dart new file mode 100644 index 000000000..333b87a56 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_candle/ohlc_candle_series.dart @@ -0,0 +1,27 @@ +import 'package:deriv_chart/src/models/candle.dart'; +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import 'package:deriv_chart/src/theme/painting_styles/candle_style.dart'; + +import '../../data_series.dart'; +import '../../series_painter.dart'; +import '../ohlc_type_series.dart'; +import 'ohlc_candle_painter.dart'; + +/// Ohlc CandleStick series +class OhlcCandleSeries extends OHLCTypeSeries { + /// Initializes + OhlcCandleSeries( + List entries, { + String? id, + CandleStyle? style, + HorizontalBarrierStyle? lastTickIndicatorStyle, + }) : super( + entries, + id ?? 'OhlcCandleSeries', + style: style, + lastTickIndicatorStyle: lastTickIndicatorStyle, + ); + + @override + SeriesPainter> createPainter() => OhlcCandlePainter(this); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_painter.dart new file mode 100644 index 000000000..f26b5f966 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_painter.dart @@ -0,0 +1,136 @@ +import 'dart:ui' as ui; + +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/models/candle.dart'; +import 'package:flutter/material.dart'; + +import '../../chart_data.dart'; +import '../data_painter.dart'; +import '../data_series.dart'; +import '../indexed_entry.dart'; +import './ohlc_painting.dart'; + +/// A [DataPainter] for painting CandleStick data which handles the logic for +/// calculation of candle elements for all OHLC type charts +abstract class OhlcPainter extends DataPainter> { + /// Initializes + OhlcPainter(DataSeries series) : super(series); + + @override + void onPaintData( + Canvas canvas, + Size size, + EpochToX epochToX, + QuoteToY quoteToY, + AnimationInfo animationInfo, + ) { + if (series.entries == null || series.visibleEntries.length < 2) { + return; + } + + final double intervalWidth = + epochToX(chartConfig.granularity) - epochToX(0); + + final double candleWidth = intervalWidth * 0.6; + + // Painting visible candles except the last one that might be animated. + for (int i = series.visibleEntries.startIndex; + i < series.visibleEntries.endIndex - 1; + i++) { + final Candle candle = series.entries![i]; + final Candle prevCandle = + i != 0 ? series.entries![i - 1] : series.entries![0]; + + onPaintCandle( + canvas, + OhlcPainting( + width: candleWidth, + xCenter: epochToX(getEpochOf(candle, i)), + yHigh: quoteToY(candle.high), + yLow: quoteToY(candle.low), + yOpen: quoteToY(candle.open), + yClose: quoteToY(candle.close), + ), + OhlcPainting( + width: candleWidth, + xCenter: epochToX(getEpochOf(prevCandle, i - 1)), + yHigh: quoteToY(prevCandle.high), + yLow: quoteToY(prevCandle.low), + yOpen: quoteToY(prevCandle.open), + yClose: quoteToY(prevCandle.close), + )); + } + + // Painting last visible candle + final Candle lastCandle = series.entries!.last; + final Candle lastVisibleCandle = series.visibleEntries.last; + final Candle prevLastCandle = series.entries![series.entries!.length - 2]; + + late OhlcPainting lastCandlePainting; + late OhlcPainting prevLastCandlePainting; + + if (lastCandle == lastVisibleCandle && series.prevLastEntry != null) { + final IndexedEntry prevLastCandle = series.prevLastEntry!; + + final double animatedYClose = quoteToY(ui.lerpDouble( + prevLastCandle.entry.close, + lastCandle.close, + animationInfo.currentTickPercent, + )!); + + final double xCenter = ui.lerpDouble( + epochToX(getEpochOf(prevLastCandle.entry, prevLastCandle.index)), + epochToX(getEpochOf(lastCandle, series.entries!.length - 1)), + animationInfo.currentTickPercent, + )!; + + lastCandlePainting = OhlcPainting( + xCenter: xCenter, + yHigh: lastCandle.high > prevLastCandle.entry.high + // In this case we don't update high-low line to avoid instant + // change of its height (ahead of animation). Candle close value + // animation will cover the line. + ? quoteToY(prevLastCandle.entry.high) + : quoteToY(lastCandle.high), + yLow: lastCandle.low < prevLastCandle.entry.low + // Same as comment above. + ? quoteToY(prevLastCandle.entry.low) + : quoteToY(lastCandle.low), + yOpen: quoteToY(lastCandle.open), + yClose: animatedYClose, + width: candleWidth, + ); + } else { + lastCandlePainting = OhlcPainting( + xCenter: epochToX( + getEpochOf(lastVisibleCandle, series.visibleEntries.endIndex - 1)), + yHigh: quoteToY(lastVisibleCandle.high), + yLow: quoteToY(lastVisibleCandle.low), + yOpen: quoteToY(lastVisibleCandle.open), + yClose: quoteToY(lastVisibleCandle.close), + width: candleWidth, + ); + } + + prevLastCandlePainting = OhlcPainting( + xCenter: epochToX( + getEpochOf(prevLastCandle, series.visibleEntries.endIndex - 1)), + yHigh: quoteToY(prevLastCandle.high), + yLow: quoteToY(prevLastCandle.low), + yOpen: quoteToY(prevLastCandle.open), + yClose: quoteToY(prevLastCandle.close), + width: candleWidth, + ); + + onPaintCandle(canvas, lastCandlePainting, prevLastCandlePainting); + } + + /// Paints [DataSeries.visibleEntries]. + /// This method is for handling diffrent ways of painting candles for each + /// chart type + void onPaintCandle( + Canvas canvas, + OhlcPainting currentPainting, + OhlcPainting prevPainting, + ); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_painting.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_painting.dart new file mode 100644 index 000000000..2abfa616e --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/ohlc_series/ohlc_painting.dart @@ -0,0 +1,30 @@ +/// The required painting properties of a ohlc. +class OhlcPainting { + /// Initialzes the required painting properties of a ohlc. + const OhlcPainting({ + required this.xCenter, + required this.yHigh, + required this.yLow, + required this.yOpen, + required this.yClose, + required this.width, + }); + + /// The center X position of the ohlc. + final double xCenter; + + /// Y position of the ohlc's high value. + final double yHigh; + + /// Y position of the ohlc's low value. + final double yLow; + + /// Y position of the ohlc's open value. + final double yOpen; + + /// Y position of the ohlc's close value. + final double yClose; + + /// The width of the ohlc candle. + final double width; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/series.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/series.dart index 2ae58ee6e..b5c2c796f 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/series.dart @@ -1,12 +1,11 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_chart/src/theme/painting_styles/chart_painting_style.dart'; import 'package:flutter/material.dart'; -import '../chart_data.dart'; -import 'series_painter.dart'; - /// Base class of all chart series. abstract class Series implements ChartData { /// Initializes a base class of all chart series. diff --git a/lib/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart b/lib/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart index ea50a0d99..78003954e 100644 --- a/lib/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart +++ b/lib/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart @@ -1,9 +1,11 @@ -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:flutter/material.dart'; +import 'series.dart'; + /// A class responsible to paint its [series] data. abstract class SeriesPainter { /// Initializes series for sub-class. diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.dart new file mode 100644 index 000000000..ae911ed87 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.dart @@ -0,0 +1,336 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/channel/channel_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line_vector_drawing_mixin.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'channel_drawing.g.dart'; + +/// Channel drawing tool. A channel is 2 parallel lines that +/// created with 3 points. +@JsonSerializable() +class ChannelDrawing extends Drawing with LineVectorDrawingMixin { + /// Initializes + ChannelDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.middleEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + this.isDrawingFinished = false, + }); + + /// Initializes from JSON. + factory ChannelDrawing.fromJson(Map json) => + _$ChannelDrawingFromJson(json); + + @override + Map toJson() => _$ChannelDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'ChannelDrawing'; + + /// Part of a drawing: 'marker' or 'line' + final DrawingParts drawingPart; + + /// Starting point of drawing which is used as the start point of initial + /// vector + final EdgePoint startEdgePoint; + + /// Second point of drawing which is used to draw the end point of initial + /// vector + final EdgePoint middleEdgePoint; + + /// Ending point of drawing which is used to draw the second(final) vector + final EdgePoint endEdgePoint; + + /// Marker radius. + final double markerRadius = 10; + + /// Flag to show if drawing is finished. + final bool isDrawingFinished; + + Vector _initialVector = const Vector.zero(); + Vector _finalVector = const Vector.zero(); + + /// Keeps the latest position of the start and end point of drawing + Point? _startPoint, _middlePoint, _endPoint; + + /// Returns the Parallelogram path + Path getParallelogramPath( + Vector startVector, + Vector endVector, + ) => + Path() + ..moveTo(startVector.x0, startVector.y0) + ..lineTo(startVector.x1, startVector.y1) + ..lineTo(endVector.x1, endVector.y1) + ..lineTo(endVector.x0, endVector.y0) + ..close(); + + /// Draw the shaded area between two vectors + void drawParallelogram( + Canvas canvas, + ChannelDrawingToolConfig config, + DrawingPaintStyle paint, + Vector startVector, + Vector endVector, + ) { + final LineStyle fillStyle = config.fillStyle; + + /// The path for the shaded area between two lines + final Path path = getParallelogramPath(startVector, endVector); + + canvas.drawPath( + path, paint.fillPaintStyle(fillStyle.color, fillStyle.thickness)); + } + + /// Paint the line + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + final DrawingPaintStyle paint = DrawingPaintStyle(); + + /// Get the latest config of any drawing tool which is used to draw the line + config as ChannelDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final DrawingPatterns pattern = config.pattern; + final List edgePoints = config.edgePoints; + + /// Since we want to draw a marker on the screen base on the user click, + /// we need to call the onPaint function each time any record added to + /// edgePoints list, so we need to check the edgePoints list lenght to + /// know which marker should be drawn + _startPoint = updatePositionCallback(edgePoints.first, draggableStartPoint); + if (edgePoints.length > 1) { + _middlePoint = + updatePositionCallback(edgePoints[1], draggableMiddlePoint!); + } else { + _middlePoint = + updatePositionCallback(middleEdgePoint, draggableMiddlePoint!); + } + if (edgePoints.length > 2) { + _endPoint = updatePositionCallback(edgePoints.last, draggableEndPoint!); + } else { + _endPoint = updatePositionCallback(endEdgePoint, draggableEndPoint!); + } + + final double startXCoord = _startPoint!.x; + final double startQuoteToY = _startPoint!.y; + + final double middleXCoord = _middlePoint!.x; + final double middleQuoteToY = _middlePoint!.y; + + final double height = middleQuoteToY - _endPoint!.y; + + final double endXCoord = middleXCoord; + final double endQuoteToY = middleQuoteToY - height; + + _initialVector = getLineVector( + startXCoord, + startQuoteToY, + middleXCoord, + middleQuoteToY, + ); + + _finalVector = getLineVector( + endXCoord, + endQuoteToY, + startXCoord, + startQuoteToY - height, + ); + + if (drawingPart == DrawingParts.marker) { + if (endEdgePoint.epoch != 0 && endQuoteToY != 0) { + /// Draw final point + canvas.drawCircle( + Offset(middleXCoord, middleQuoteToY - height), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } else if (startEdgePoint.epoch != 0 && startQuoteToY != 0) { + /// Draw first point + canvas.drawCircle( + Offset(startXCoord, startQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } else if (middleEdgePoint.epoch != 0 && middleQuoteToY != 0) { + /// Draw second point + canvas.drawCircle( + Offset(middleXCoord, middleQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } + } else if (drawingPart == DrawingParts.line) { + if (endEdgePoint.epoch != 0 && endQuoteToY != 0) { + /// Draw second line + drawParallelogram( + canvas, + config, + paint, + _initialVector, + _finalVector, + ); + if (pattern == DrawingPatterns.solid) { + /// Drawing the markers in the final step again to hide the overlap + /// of fill color and the markers + canvas + ..drawCircle( + Offset(startXCoord, startQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()) + ..drawCircle( + Offset(middleXCoord, middleQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()) + ..drawCircle( + Offset(middleXCoord, middleQuoteToY - height), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()) + + /// Draw first line again to hide the overlap of fill color and line + ..drawLine( + Offset(_initialVector.x0, _initialVector.y0), + Offset(_initialVector.x1, _initialVector.y1), + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle( + lineStyle.color, lineStyle.thickness) + : paint.linePaintStyle(lineStyle.color, lineStyle.thickness), + ) + ..drawLine( + Offset(_finalVector.x0, _finalVector.y0), + Offset(_finalVector.x1, _finalVector.y1), + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle( + lineStyle.color, lineStyle.thickness) + : paint.linePaintStyle(lineStyle.color, lineStyle.thickness), + ); + } + } else if (startEdgePoint.epoch != 0 && startQuoteToY != 0) { + /// Draw first line + if (pattern == DrawingPatterns.solid) { + canvas.drawLine( + Offset(_initialVector.x0, _initialVector.y0), + Offset(_initialVector.x1, _initialVector.y1), + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle( + lineStyle.color, lineStyle.thickness) + : paint.linePaintStyle(lineStyle.color, lineStyle.thickness), + ); + } + } + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen, for any of the edge points + /// it will call "setIsEdgeDragged" callback function to determine which + /// point is clicked + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + final double middleXCoord = _middlePoint!.x; + final double middleQuoteToY = _middlePoint!.y; + + final double height = middleQuoteToY - _endPoint!.y; + + final double endXCoord = middleXCoord; + final double endQuoteToY = middleQuoteToY - height; + + /// Check if start point clicked + if (_startPoint!.isClicked(position, markerRadius)) { + setIsOverStartPoint(isOverPoint: true); + } else { + setIsOverStartPoint(isOverPoint: false); + } + + /// Check if middle point clicked + if (_middlePoint!.isClicked(position, markerRadius)) { + setIsOverMiddlePoint!(isOverPoint: true); + } else { + setIsOverMiddlePoint!(isOverPoint: false); + } + + /// Check if end point clicked, since the endPoint position is dependendat + /// to middle point position, we need to check it differently + final Point endPoint = Point(x: endXCoord, y: endQuoteToY); + + /// Check if end point clicked + if (endPoint.isClicked(position, markerRadius)) { + setIsOverEndPoint!(isOverPoint: true); + } else { + setIsOverEndPoint!(isOverPoint: false); + } + + /// Detect the area between 2 parallel lines + final Path path = getParallelogramPath(_initialVector, _finalVector); + + return (isDrawingFinished && path.contains(position)) || + (_startPoint!.isClicked(position, markerRadius) || + _middlePoint!.isClicked(position, markerRadius) || + endPoint.isClicked(position, markerRadius)); + } + + // TODO(NA): return true if the channel drawing is in epoch range. + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + true; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.g.dart new file mode 100644 index 000000000..0eb3acdd3 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'channel_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ChannelDrawing _$ChannelDrawingFromJson(Map json) => + ChannelDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + middleEdgePoint: json['middleEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['middleEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + isDrawingFinished: json['isDrawingFinished'] as bool? ?? false, + ); + +Map _$ChannelDrawingToJson(ChannelDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startEdgePoint': instance.startEdgePoint, + 'middleEdgePoint': instance.middleEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + 'isDrawingFinished': instance.isDrawingFinished, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing_creator.dart new file mode 100644 index 000000000..dd942f7f7 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing_creator.dart @@ -0,0 +1,125 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:flutter/material.dart'; +import '../data_model/drawing_parts.dart'; + +/// Creates a Channel drawing piece by piece collected on every gesture +/// exists in a widget tree starting from selecting the channel drawing tool +/// and until drawing should be finished. +class ChannelDrawingCreator extends DrawingCreator { + /// Initializes the channel drawing creator. + const ChannelDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + required this.shouldStopDrawing, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove specific drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + /// A flag to show when to stop drawing only for drawings which don't have + /// fixed number of points like continuous drawing + final bool shouldStopDrawing; + + @override + DrawingCreatorState createState() => + _ChannelDrawingCreatorState(); +} + +class _ChannelDrawingCreatorState extends DrawingCreatorState { + @override + void onTap(TapUpDetails details) { + super.onTap(details); + final ChannelDrawingCreator _widget = widget as ChannelDrawingCreator; + + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + tapCount++; + + if (edgePoints.isEmpty) { + /// Draw the initial point of the line. + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + drawingParts.add(ChannelDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + )); + } else if (edgePoints.length == 1) { + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + /// Checks if the initial point and the final point are the same. + if (edgePoints[1] == edgePoints.first) { + /// If the initial point and the 2nd point are the same, + /// remove the drawing and clean the drawing tool selection. + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } else { + /// If the initial point and the final point are not the same, + /// draw the final point and the whole line. + drawingParts.addAll([ + ChannelDrawing( + drawingPart: DrawingParts.marker, + middleEdgePoint: edgePoints[1], + ), + ChannelDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: edgePoints[0], + middleEdgePoint: edgePoints[1], + ) + ]); + } + } else if (edgePoints.length == 2) { + /// Draw final point and the whole line. + isDrawingFinished = true; + edgePoints.add(EdgePoint( + epoch: edgePoints[1].epoch, + quote: widget.quoteFromCanvasY(position!.dy), + )); + + /// If the initial point and the final point are not the same, + /// draw the final point and the whole line. + drawingParts.addAll([ + ChannelDrawing( + drawingPart: DrawingParts.marker, + middleEdgePoint: edgePoints[1], + endEdgePoint: edgePoints[2], + ), + ChannelDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: edgePoints[0], + middleEdgePoint: edgePoints[1], + endEdgePoint: edgePoints[2], + isDrawingFinished: isDrawingFinished, + ) + ]); + } + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_drawing_creator.dart new file mode 100644 index 000000000..844b778fc --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_drawing_creator.dart @@ -0,0 +1,120 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:flutter/material.dart'; +import '../data_model/drawing_parts.dart'; + +/// Creates a Continuous drawing piece by piece collected on every gesture +/// exists in a widget tree starting from selecting a continuous drawing tool +/// and until drawing should be finished. +class ContinuousDrawingCreator extends DrawingCreator { + /// Initializes the continuous drawing creator. + const ContinuousDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + required this.shouldStopDrawing, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove unfinished drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + /// A flag to show when to stop drawing only for drawings which don't have + /// fixed number of points like continuous drawing + final bool shouldStopDrawing; + + @override + DrawingCreatorState createState() => + _ContinuousDrawingCreatorState(); +} + +class _ContinuousDrawingCreatorState + extends DrawingCreatorState { + @override + void onTap(TapUpDetails details) { + super.onTap(details); + final ContinuousDrawingCreator _widget = widget as ContinuousDrawingCreator; + + if (_widget.shouldStopDrawing) { + return; + } else { + isDrawingFinished = false; + } + setState(() { + position = details.localPosition; + tapCount++; + final int currentTap = tapCount - 1; + final int previousTap = tapCount - 2; + + if (edgePoints.isEmpty) { + /// Draw the initial point of the continuous. + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + drawingParts.add(ContinuousLineDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + )); + } else if (!isDrawingFinished) { + /// Draw other points and the whole continuous drawing. + + isDrawingFinished = true; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + /// Checks if the initial point and the 2nd points are the same. + if (edgePoints[1] == edgePoints.first) { + /// If the initial point and the 2nd point are the same, + /// remove the drawing and clean the drawing tool selection. + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } else { + /// If the initial point and the final point are not the same, + /// draw the final point and the whole drawing. + if (tapCount > 2) { + drawingParts = []; + + drawingParts.add(ContinuousLineDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints[previousTap], + )); + } + drawingParts.addAll([ + ContinuousLineDrawing( + drawingPart: DrawingParts.marker, + endEdgePoint: edgePoints[currentTap], + ), + ContinuousLineDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: edgePoints[previousTap], + endEdgePoint: edgePoints[currentTap], + ) + ]); + } + } + + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + isInfiniteDrawing: true, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.dart new file mode 100644 index 000000000..864ae327d --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.dart @@ -0,0 +1,209 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/continuous/continuous_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'continuous_line_drawing.g.dart'; + +/// Line drawing tool. A line is a vector defined by two points that is +/// infinite in both directions. +@JsonSerializable() +class ContinuousLineDrawing extends Drawing { + /// Initializes + ContinuousLineDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + this.exceedStart = false, + this.exceedEnd = false, + }) : _lineDrawing = LineDrawing( + drawingPart: drawingPart, + startEdgePoint: startEdgePoint, + endEdgePoint: endEdgePoint, + exceedStart: exceedStart, + exceedEnd: exceedEnd, + ); + + /// Initializes from JSON. + factory ContinuousLineDrawing.fromJson(Map json) => + _$ContinuousLineDrawingFromJson(json); + + @override + Map toJson() => _$ContinuousLineDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Drawing part. + final DrawingParts drawingPart; + + /// Start edge point. + final EdgePoint startEdgePoint; + + /// End edge point. + final EdgePoint endEdgePoint; + + /// Whether the start point is exceeded. + final bool exceedStart; + + /// Whether the end point is exceeded. + final bool exceedEnd; + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'ContinuousLineDrawing'; + + final LineDrawing _lineDrawing; + + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + _lineDrawing.needsRepaint( + leftEpoch, + rightEpoch, + draggableStartPoint, + draggableEndPoint: draggableEndPoint, + ); + + /// Paint the line + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + config as ContinuousDrawingToolConfig; + + final DrawingData lineDrawingData = DrawingData( + id: drawingData.id, + drawingParts: drawingData.drawingParts, + isDrawingFinished: drawingData.isDrawingFinished, + isSelected: drawingData.isSelected, + isHovered: drawingData.isHovered, + ); + + /// Draw first line of the continuous drawing which need 2 taps to draw + if (config.edgePoints.length <= 2) { + _lineDrawing.onPaint( + canvas, + size, + theme, + epochFromX, + quoteFromY, + epochToX, + quoteToY, + LineDrawingToolConfig( + configId: config.configId, + drawingData: config.drawingData, + lineStyle: config.lineStyle, + pattern: config.pattern, + edgePoints: config.edgePoints, + ), + lineDrawingData, + series, + updatePositionCallback, + draggableStartPoint, + draggableEndPoint: draggableEndPoint, + ); + } + + /// Draw other lines of continuous which need more than 1 tap to draw + if (config.edgePoints.length > 2) { + // TODO(NA): Refactor this code to avoid using indexOf + config.edgePoints + .where((EdgePoint element) => + config.edgePoints.indexOf(element) > 1 && + config.edgePoints.indexOf(element) == + config.edgePoints.length - 1) + .forEach((EdgePoint edgePoint) { + final int index = config.edgePoints.indexOf(edgePoint); + _lineDrawing.onPaint( + canvas, + size, + theme, + epochFromX, + quoteFromY, + epochToX, + quoteToY, + LineDrawingToolConfig( + configId: config.configId, + drawingData: config.drawingData, + lineStyle: config.lineStyle, + pattern: config.pattern, + + /// Limit the edge points to only 2 points, since line drawing + /// needs only 2 points + edgePoints: [config.edgePoints[index - 1], edgePoint], + ), + lineDrawingData, + series, + updatePositionCallback, + draggableStartPoint, + draggableEndPoint: draggableEndPoint, + ); + }); + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen, for any of the edge points + /// it will call "setIsEdgeDragged" callback function to determine which + /// point is clicked + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + config as ContinuousDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final DrawingPatterns pattern = config.pattern; + + return _lineDrawing.hitTest( + position, + epochToX, + quoteToY, + LineDrawingToolConfig(lineStyle: lineStyle, pattern: pattern), + draggableStartPoint, + setIsOverStartPoint, + draggableEndPoint: draggableEndPoint, + setIsOverEndPoint: setIsOverEndPoint); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.g.dart new file mode 100644 index 000000000..871825eb2 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_line_drawing.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'continuous_line_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ContinuousLineDrawing _$ContinuousLineDrawingFromJson( + Map json) => + ContinuousLineDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + exceedStart: json['exceedStart'] as bool? ?? false, + exceedEnd: json['exceedEnd'] as bool? ?? false, + ); + +Map _$ContinuousLineDrawingToJson( + ContinuousLineDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startEdgePoint': instance.startEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + 'exceedStart': instance.exceedStart, + 'exceedEnd': instance.exceedEnd, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart new file mode 100644 index 000000000..33c0b6a87 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart @@ -0,0 +1,104 @@ +import 'dart:ui'; + +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'draggable_edge_point.g.dart'; + +/// A class that holds draggable edge point data. +/// Draggable edge points are part of the drawings which added by user clicks +/// And we want to hanle difftent types of drag events on them. +/// For example with dots are draggable edge points for the line +/// ⎯⎯⚪️⎯⎯⎯⚪️⎯⎯ +@JsonSerializable() +class DraggableEdgePoint extends EdgePoint { + /// Initializes + DraggableEdgePoint({ + int epoch = 0, + double quote = 0, + this.isDrawingDragged = false, + this.isDragged = false, + }) : super(epoch: epoch, quote: quote); + + /// Initializes from JSON. + factory DraggableEdgePoint.fromJson(Map map) => + _$DraggableEdgePointFromJson(map); + + @override + Map toJson() => _$DraggableEdgePointToJson(this); + + /// Represents whether the whole drawing is currently being dragged or not + final bool isDrawingDragged; + + /// Represents whether the edge point is currently being dragged or not + final bool isDragged; + + /// Holds the current position of the edge point when it is being dragged. + EdgePoint _draggedEdgePoint = const EdgePoint(); + + /// Updated position of the edge point when it is being dragged. + /// + /// This would be obsolete when we keep [epoch] and [quote] fields updated + /// with user's dragging. And we can use them instead of this field. + EdgePoint get draggedEdgePoint => _draggedEdgePoint; + + /// A callback method that takes the relative x and y positions as parameter, + /// sets the draggedPosition field to its value and return epoch and quote + /// values. + Point updatePosition( + int epoch, + double quote, + double Function(int x) epochToX, + double Function(double y) quoteToY, + ) { + final EdgePoint oldEdgePoint = EdgePoint(epoch: epoch, quote: quote); + _draggedEdgePoint = isDrawingDragged ? _draggedEdgePoint : oldEdgePoint; + + final double x = epochToX(_draggedEdgePoint.epoch); + final double y = quoteToY(_draggedEdgePoint.quote); + + return Point(x: x, y: y); + } + + /// A method that takes the gesture delta Offset object as parameter + /// and sets the draggedPosition field to its value. + void updatePositionWithLocalPositions( + Offset delta, + XAxisModel xAxis, + double Function(double) quoteFromCanvasY, + double Function(double y) quoteToY, { + required bool isOtherEndDragged, + }) { + final Offset localPosition = Offset( + xAxis.xFromEpoch(_draggedEdgePoint.epoch), + quoteToY(_draggedEdgePoint.quote)) + + (isOtherEndDragged ? Offset.zero : delta); + + _draggedEdgePoint = EdgePoint( + epoch: xAxis.epochFromX(localPosition.dx), + quote: quoteFromCanvasY(localPosition.dy), + ); + } + + /// Returns the current position of the edge point when it is being dragged. + EdgePoint getEdgePoint() => EdgePoint( + epoch: _draggedEdgePoint.epoch, + quote: _draggedEdgePoint.quote, + ); + + /// Creates a copy of this object. + DraggableEdgePoint copyWith({ + int? epoch, + double? quote, + bool? isDrawingDragged, + bool? isDragged, + }) => + DraggableEdgePoint( + epoch: epoch ?? this.epoch, + quote: quote ?? this.quote, + isDrawingDragged: isDrawingDragged ?? this.isDrawingDragged, + isDragged: isDragged ?? this.isDragged, + ).._draggedEdgePoint = _draggedEdgePoint; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.g.dart new file mode 100644 index 000000000..61f160fdb --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'draggable_edge_point.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DraggableEdgePoint _$DraggableEdgePointFromJson(Map json) => + DraggableEdgePoint( + epoch: json['epoch'] as int? ?? 0, + quote: (json['quote'] as num?)?.toDouble() ?? 0, + isDrawingDragged: json['isDrawingDragged'] as bool? ?? false, + isDragged: json['isDragged'] as bool? ?? false, + ); + +Map _$DraggableEdgePointToJson(DraggableEdgePoint instance) => + { + 'epoch': instance.epoch, + 'quote': instance.quote, + 'isDrawingDragged': instance.isDrawingDragged, + 'isDragged': instance.isDragged, + }; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart new file mode 100644 index 000000000..223c78ae0 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +/// A class that holds the paint style of the drawings +class DrawingPaintStyle { + /// Returns the glowy paint style of the line + Paint glowyLinePaintStyle(Color color, double thickness) => Paint() + ..color = color + ..strokeWidth = thickness + 3 + ..strokeCap = StrokeCap.round; + + /// Returns the paint style of the the line + Paint linePaintStyle(Color color, double thickness) => Paint() + ..color = color + ..strokeWidth = thickness; + + /// Returns the paint style of the the faded line + Paint fadedLinePaintStyle(Color color, double thickness) => Paint() + ..color = color.withOpacity(0.7) + ..strokeWidth = thickness; + + /// Returns the paint style of the inner filling of container + Paint fillPaintStyle(Color color, double thickness) => Paint() + ..color = color.withOpacity(0.2) + ..style = PaintingStyle.fill + ..strokeWidth = thickness; + + /// Returns the paint style of the outer stroke of the container + Paint strokeStyle(Color color, double thickness) => Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = thickness; + + /// Returns the paint style of the circle marker + Paint glowyCirclePaintStyle(Color color) => Paint()..color = color; + + /// Returns the paint style of the circle marker + Paint transparentCirclePaintStyle() => Paint()..color = Colors.transparent; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart new file mode 100644 index 000000000..19182694a --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// Differnet types of drawing parts. +enum DrawingParts { + /// Used to show the marker. + @JsonValue('marker') + marker, + + /// Used to show the line. + @JsonValue('line') + line, + + /// Used to show the rectangle + rectangle +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart new file mode 100644 index 000000000..2f4a8530d --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart @@ -0,0 +1,11 @@ +/// Differnet types of drawing patterns. +enum DrawingPatterns { + /// Used for solid line. + solid, + + /// Used for dotted line. + dotted, + + /// Used for dashed line. + dashed, +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart new file mode 100644 index 000000000..9529582f0 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart @@ -0,0 +1,30 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'edge_point.g.dart'; + +/// A class that holds epoch and yCoord of the edge points. +@JsonSerializable() +class EdgePoint with EquatableMixin { + /// Initializes + const EdgePoint({ + this.epoch = 0, + this.quote = 0, + }); + + /// Initializes from JSON. + factory EdgePoint.fromJson(Map json) => + _$EdgePointFromJson(json); + + /// Serialization to JSON. Serves as value in key-value storage. + Map toJson() => _$EdgePointToJson(this); + + /// Epoch. + final int epoch; + + /// Quote. + final double quote; + + @override + List get props => [epoch, quote]; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.g.dart new file mode 100644 index 000000000..2cb0373ce --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'edge_point.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +EdgePoint _$EdgePointFromJson(Map json) => EdgePoint( + epoch: json['epoch'] as int? ?? 0, + quote: (json['quote'] as num?)?.toDouble() ?? 0, + ); + +Map _$EdgePointToJson(EdgePoint instance) => { + 'epoch': instance.epoch, + 'quote': instance.quote, + }; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/extensions.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/extensions.dart new file mode 100644 index 000000000..a010474e0 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/extensions.dart @@ -0,0 +1,40 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; + +// TODO(NA): consider EdgePoint radius as well in this calculation. +/// The distance from the edge point to the edge of the screen to be considered +/// as off screen for a [DraggableEdgePoint]. +/// +/// This is to make sure the [DraggableEdgePoint] is fully out of the view port. +/// +/// Since the position of a [DraggableEdgePoint] is its center point, we should +/// also consider its radius as well if we want to improve this value and make +/// it accurate. +/// +/// When we know how visually can represent the edge point (circle, square, etc) +/// we can improve this value. +/// +/// Currently as for a safe number we consider the half of screen width for a +/// [DraggableEdgePoint] to be considered as off screen. This will ensure on all +/// granularities the [DraggableEdgePoint] is fully out of the view port. +/// +/// view port half of screen outside +/// ------^-------- ---^--- +/// | | * | -> edge point is NOT considered as off screen. +/// | | | +/// | | | * -> edge point is considered as off screen. +double _edgePointOffScreenSafeDistance(int leftEpoch, int rightEpoch) => + (rightEpoch - leftEpoch) / 2; + +/// An extension on DraggableEdgePoint class that adds some helper methods. +extension DraggableEdgePointExtension on DraggableEdgePoint { + /// Checks if the edge point is on the view port range. + /// + /// The view port range is defined by the left and right epoch values. + /// returns true if the edge point is on the view port range. + bool isInViewPortRange(int leftEpoch, int rightEpoch) => + draggedEdgePoint.epoch >= + (leftEpoch - + _edgePointOffScreenSafeDistance(leftEpoch, rightEpoch)) && + draggedEdgePoint.epoch <= + (rightEpoch + _edgePointOffScreenSafeDistance(leftEpoch, rightEpoch)); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart new file mode 100644 index 000000000..c6fb1c423 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart @@ -0,0 +1,43 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:json_annotation/json_annotation.dart'; + +part 'point.g.dart'; + +/// A class that holds point data +/// This class is equivalent to Offest but with customized methods +@JsonSerializable() +class Point { + /// Initializes + const Point({ + required this.x, + required this.y, + }); + + /// Initializes from JSON. + factory Point.fromJson(Map json) => _$PointFromJson(json); + + /// Converts to JSON. + Map toJson() => _$PointToJson(this); + + /// Related x for the point + final double x; + + /// Related y for the point + final double y; + + /// Checks whether the point has been "clicked" by a user at a certain + /// position on the screen, within a given "affected area" radius. + /// + /// The [position] parameter is the location on the screen where the user + /// clicked, specified as an [Offset] object. + /// + /// The [affectedArea] parameter is the radius of the affected area around + /// the point. + /// + /// Returns `true` if the distance between the [position] and the point is + /// less than the [affectedArea], indicating that the point has been "clicked" + bool isClicked(Offset position, double affectedArea) => + pow(x - position.dx, 2) + pow(y - position.dy, 2) < pow(affectedArea, 2); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.g.dart new file mode 100644 index 000000000..6fb922109 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'point.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Point _$PointFromJson(Map json) => Point( + x: (json['x'] as num).toDouble(), + y: (json['y'] as num).toDouble(), + ); + +Map _$PointToJson(Point instance) => { + 'x': instance.x, + 'y': instance.y, + }; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart new file mode 100644 index 000000000..eeb9ee2e7 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart @@ -0,0 +1,25 @@ +/// A class that holds vector data +class Vector { + /// Initializes + const Vector({ + required this.x0, + required this.y0, + required this.x1, + required this.y1, + }); + + /// Vector with zero coordinate. + const Vector.zero() : this(x0: 0, y0: 0, x1: 0, y1: 0); + + /// Related x for starting point of the vector + final double x0; + + /// Related y for starting point of the vector + final double y0; + + /// Related x for ending point of the vector + final double x1; + + /// Related y for ending point of the vector + final double y1; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart new file mode 100644 index 000000000..2c6d5ff3a --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart @@ -0,0 +1,136 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; + +import 'continuous/continuous_line_drawing.dart'; +import 'vertical/vertical_drawing.dart'; + +/// Base class to draw a particular drawing +abstract class Drawing { + /// Initializes [Drawing]. + const Drawing(); + + /// Initializes from JSON. + /// For restoring drawings add them to the switch statement. + factory Drawing.fromJson(Map json) { + if (!json.containsKey(classNameKey)) { + throw ArgumentError.value(json, 'json', 'Missing indicator name.'); + } + + switch (json[classNameKey]) { + case ChannelDrawing.nameKey: + return ChannelDrawing.fromJson(json); + case ContinuousLineDrawing.nameKey: + return ContinuousLineDrawing.fromJson(json); + case FibfanDrawing.nameKey: + return FibfanDrawing.fromJson(json); + case HorizontalDrawing.nameKey: + return HorizontalDrawing.fromJson(json); + case LineDrawing.nameKey: + return LineDrawing.fromJson(json); + case RayLineDrawing.nameKey: + return RayLineDrawing.fromJson(json); + case RectangleDrawing.nameKey: + return RectangleDrawing.fromJson(json); + case TrendDrawing.nameKey: + return TrendDrawing.fromJson(json); + case VerticalDrawing.nameKey: + return VerticalDrawing.fromJson(json); + + default: + throw ArgumentError.value(json, 'json', 'Invalid indicator name.'); + } + } + + /// Creates a concrete drawing tool from JSON. + Map toJson(); + + /// Key of drawing tool name property in JSON. + static const String classNameKey = 'class_name_key'; + + /// Will be called when the drawing is moved by the user gesture. + /// + /// Some drawing tools might required to handle some logic after the drawing + /// is moved and we don't want this logic be done in the [onPaint] method + /// because it runs more often and it might cause performance issues. + /// + /// The method has an empty implementation so only the [Drawing] subclasses + /// that require this life-cycle method can override it. + /// + // TODO(Bahar-Deriv): Decide if we need to pass the [draggableMiddlePoint] + /// and change the method name + void onDrawingMoved( + int Function(double x) epochFromX, + List ticks, + EdgePoint startPoint, { + EdgePoint? middlePoint, + EdgePoint? endPoint, + }) {} + + /// Is called before repaint the drawing to check if it needs to be + /// repainted. + /// + /// Returns true if the drawing needs to be repainted. + /// + /// Since the [Drawing] class instances are mutable and live across the + /// painting frames, there is no previous instance of it provided in this + /// method to compare with and decide for repainting. + /// + /// Repainting condition for drawing usually is based on whether they are + /// in the chart visible area or not. + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }); + + /// Paint + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }); + + /// Calculates whether a user's touch or click intersects + /// with any of the painted areas on the screen + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart new file mode 100644 index 000000000..e7f1f4072 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart @@ -0,0 +1,117 @@ +import 'dart:math' as math; + +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/gestures/gesture_manager.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +/// This is a function type used as a callback to pass a newly created drawing +/// of type [T] to the parent. +typedef OnAddDrawing = void Function( + String drawingId, + List drawingParts, { + bool isDrawingFinished, + bool isInfiniteDrawing, + List? edgePoints, +}); + +/// This class is an abstract representation of a drawing creator, and it's a +/// [StatefulWidget]. It is generic, with the type parameter [T] constrained to +/// be a subclass of Drawing. +abstract class DrawingCreator extends StatefulWidget { + /// Initializes. + const DrawingCreator({ + required this.onAddDrawing, + required this.quoteFromCanvasY, + this.chartConfig, + Key? key, + }) : super(key: key); + + /// Chart config to get pipSize + final ChartConfig? chartConfig; + + /// A required callback property of type [OnAddDrawing] that is used to + /// pass the newly created drawing to the parent. + final OnAddDrawing onAddDrawing; + + /// Conversion function for converting quote from chart's canvas' Y position. + final double Function(double) quoteFromCanvasY; + + @override + DrawingCreatorState createState(); +} + +/// This class is an abstract representation of the state associated with the +/// DrawingCreator. It extends [State>] and is generic with +/// the type parameter [T] constrained to be a subclass of Drawing. +abstract class DrawingCreatorState + extends State> { + /// Gesture manager state. + late final GestureManagerState _gestureManager; + + /// Parts of a particular drawing, e.g. marker, line etc. + @protected + List drawingParts = []; + + /// Tapped position. + @protected + Offset? position; + + /// Keeps track of how many times user tapped on the chart. + @protected + int tapCount = 0; + + /// Keeps the points tapped by the user to draw the continuous drawing. + final List edgePoints = []; + + /// Unique drawing id. + @protected + String drawingId = ''; + + /// If drawing has been finished. + @protected + bool isDrawingFinished = false; + + /// Get epoch from x. + @protected + int Function(double x)? epochFromX; + + @override + void initState() { + super.initState(); + _gestureManager = context.read() + ..registerCallback(onTap); + } + + @override + void dispose() { + _gestureManager.removeCallback(onTap); + super.dispose(); + } + + /// Returns the specific drawing id base on selected drawing tool name. + void generateDrawingId(String drawingToolName) { + drawingId = '${drawingToolName}_${math.Random().nextInt(1000)}'; + } + + /// Catches each single click on the chart to create a drawing. + void onTap(TapUpDetails details) { + // TODO(bahar-deriv): We need to apply some refactors here once all the + // drawing tools merged. Duplicated codes should be moved here. + generateDrawingId(runtimeType.toString()); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final XAxisModel xAxis = context.watch(); + epochFromX = xAxis.epochFromX; + } + + @override + Widget build(BuildContext context) => const SizedBox.shrink(); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart new file mode 100644 index 000000000..16a2f0afa --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart @@ -0,0 +1,70 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'drawing_data.g.dart'; + +/// A class that hold drawing data. +@JsonSerializable() +class DrawingData { + /// Initializes + DrawingData({ + required this.id, + required this.drawingParts, + this.isDrawingFinished = false, + this.isHovered = false, + this.isSelected = false, + }); + + /// Initializes from JSON. + factory DrawingData.fromJson(Map json) => + _$DrawingDataFromJson(json); + + /// Serialization to JSON. Serves as value in key-value storage. + Map toJson() => _$DrawingDataToJson(this); + + /// Unique id of the current drawing. + final String id; + + /// Drawing list. + final List drawingParts; + + /// If drawing is finished. + bool isDrawingFinished; + + /// If the drawing is selected by the user. + @JsonKey(includeFromJson: false, includeToJson: false) + bool isSelected; + + /// If the drawing is hovered by the user. + @JsonKey(includeFromJson: false, includeToJson: false) + bool isHovered; + + /// If the drawing should be highlighted or not. + bool get shouldHighlight => isSelected || isHovered; + + /// Determines if this [DrawingData] needs to be repainted. + /// Returns `true` if any of the [drawingParts] needs to be repainted. + bool shouldRepaint( + DrawingData oldDrawingData, + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + for (final Drawing drawing in drawingParts) { + if (drawing.needsRepaint( + leftEpoch, + rightEpoch, + draggableStartPoint, + draggableMiddlePoint: draggableMiddlePoint, + draggableEndPoint: draggableEndPoint, + )) { + return true; + } + } + + return false; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.g.dart new file mode 100644 index 000000000..ff555a6ac --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'drawing_data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DrawingData _$DrawingDataFromJson(Map json) => DrawingData( + id: json['id'] as String, + drawingParts: (json['drawingParts'] as List) + .map((e) => Drawing.fromJson(e as Map)) + .toList(), + isDrawingFinished: json['isDrawingFinished'] as bool? ?? false, + ); + +Map _$DrawingDataToJson(DrawingData instance) => + { + 'id': instance.id, + 'drawingParts': instance.drawingParts, + 'isDrawingFinished': instance.isDrawingFinished, + }; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_painter.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_painter.dart new file mode 100644 index 000000000..d7da45f02 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_painter.dart @@ -0,0 +1,461 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; +import 'package:deriv_chart/src/misc/debounce.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +/// Paints every existing drawing. +class DrawingPainter extends StatefulWidget { + /// Initializes + const DrawingPainter({ + required this.drawingData, + required this.quoteToCanvasY, + required this.quoteFromCanvasY, + required this.onMoveDrawing, + required this.setIsDrawingSelected, + required this.isDrawingMoving, + required this.selectedDrawingTool, + required this.onMouseEnter, + required this.onMouseExit, + required this.series, + Key? key, + }) : super(key: key); + + /// Selected drawing tool. + final DrawingToolConfig? selectedDrawingTool; + + /// Contains each drawing data + final DrawingData? drawingData; + + /// Conversion function for converting quote to chart's canvas' Y position. + final double Function(double) quoteToCanvasY; + + /// Whether a drawing is moved or not. + final bool isDrawingMoving; + + @override + _DrawingPainterState createState() => _DrawingPainterState(); + + /// Conversion function for converting quote to chart's canvas' Y position. + final double Function(double) quoteFromCanvasY; + + /// Callback to check if any single part of a single drawing is moved + /// regardless of knowing type of the drawing. + final void Function({bool isDrawingMoved}) onMoveDrawing; + + /// Callback to set if drawing is selected (tapped). + final void Function(DrawingData drawing) setIsDrawingSelected; + + /// Callback to notify mouse enter over the addon. + final void Function() onMouseEnter; + + /// Callback to notify mouse exit over the addon. + final void Function() onMouseExit; + + /// Series of tick + final DataSeries series; +} + +class _DrawingPainterState extends State { + bool _isDrawingDragged = false; + DraggableEdgePoint _draggableStartPoint = DraggableEdgePoint(); + DraggableEdgePoint _draggableMiddlePoint = DraggableEdgePoint(); + DraggableEdgePoint _draggableEndPoint = DraggableEdgePoint(); + Offset? _previousPosition; + bool isTouchHeld = false; + bool isOverStartPoint = false; + bool isOverMiddlePoint = false; + bool isOverEndPoint = false; + + final Debounce _updateDebounce = Debounce(); + + void _onMouseEnter() { + setState(() { + widget.drawingData!.isHovered = true; + }); + widget.onMouseEnter(); + } + + void _onMouseExit() { + setState(() { + widget.drawingData!.isHovered = false; + }); + widget.onMouseExit(); + } + + @override + Widget build(BuildContext context) { + final XAxisModel xAxis = context.watch(); + + final Repository repo = + context.watch>(); + + /// In this method, we are updating the restored drawing tool + /// config with latest data from the chart. + void updateDrawingToolConfig() { + _updateDebounce.run(() { + final DrawingData drawingData = widget.drawingData!; + final int index = repo.items.indexWhere( + (DrawingToolConfig item) => item.configId == drawingData.id, + ); + + if (index > -1) { + final DrawingToolConfig config = repo.items[index]; + + final DrawingToolConfig updatedConfig = config.copyWith( + edgePoints: [ + _draggableStartPoint.getEdgePoint(), + // TODO(Bahar-Deriv): Change the way storing edge points + if (config.configId!.contains('Channel')) + _draggableMiddlePoint.getEdgePoint(), + _draggableEndPoint.getEdgePoint(), + ], + ); + repo.updateAt(index, updatedConfig); + } + }); + } + + void _updateDrawingsMovement() { + if (widget.drawingData == null) { + return; + } + + for (final Drawing drawing in widget.drawingData!.drawingParts) { + drawing.onDrawingMoved( + xAxis.epochFromX, + widget.series.entries!, + _draggableStartPoint, + middlePoint: _draggableMiddlePoint, + endPoint: _draggableEndPoint, + ); + } + + setState(() {}); + } + + void _onPanUpdate(DragUpdateDetails details) { + if (widget.drawingData!.isSelected && + widget.drawingData!.isDrawingFinished) { + setState(() { + _isDrawingDragged = details.delta != Offset.zero; + + _draggableStartPoint = _draggableStartPoint.copyWith( + isDrawingDragged: _isDrawingDragged, + )..updatePositionWithLocalPositions( + details.delta, + xAxis, + widget.quoteFromCanvasY, + widget.quoteToCanvasY, + isOtherEndDragged: _draggableEndPoint.isDragged || + _draggableMiddlePoint.isDragged, + ); + _draggableMiddlePoint = _draggableMiddlePoint.copyWith( + isDrawingDragged: _isDrawingDragged, + )..updatePositionWithLocalPositions( + details.delta, + xAxis, + widget.quoteFromCanvasY, + widget.quoteToCanvasY, + isOtherEndDragged: _draggableEndPoint.isDragged || + _draggableStartPoint.isDragged, + ); + + _draggableEndPoint = _draggableEndPoint.copyWith( + isDrawingDragged: _isDrawingDragged, + )..updatePositionWithLocalPositions( + details.delta, + xAxis, + widget.quoteFromCanvasY, + widget.quoteToCanvasY, + isOtherEndDragged: _draggableStartPoint.isDragged || + _draggableMiddlePoint.isDragged, + ); + }); + + /// Updating restored DrawingToolConfig with latest data from the chart + updateDrawingToolConfig(); + } + } + + DragUpdateDetails convertLongPressToDrag( + LongPressMoveUpdateDetails longPressDetails, Offset? previousPosition) { + final Offset delta = longPressDetails.localPosition - previousPosition!; + return DragUpdateDetails( + delta: delta, + globalPosition: longPressDetails.globalPosition, + localPosition: longPressDetails.localPosition, + ); + } + + return widget.drawingData != null + ? MouseRegion( + onEnter: (PointerEnterEvent event) { + if (!isTouchHeld && !widget.isDrawingMoving) { + _onMouseEnter(); + } + }, + onExit: (PointerExitEvent event) { + if (!isTouchHeld && !widget.isDrawingMoving) { + _onMouseExit(); + } + }, + hitTestBehavior: HitTestBehavior.deferToChild, + child: RepaintBoundary( + child: GestureDetector( + onTapDown: (TapDownDetails details) { + isTouchHeld = true; + if (details.kind == PointerDeviceKind.mouse && + !widget.drawingData!.isSelected) { + widget.setIsDrawingSelected(widget.drawingData!); + _updateDrawingsMovement(); + } + + _draggableStartPoint = _draggableStartPoint.copyWith( + isDragged: isOverStartPoint, + ); + + _draggableMiddlePoint = _draggableMiddlePoint.copyWith( + isDragged: isOverMiddlePoint, + ); + + _draggableEndPoint = _draggableEndPoint.copyWith( + isDragged: isOverEndPoint, + ); + }, + onTapUp: (TapUpDetails details) { + isTouchHeld = false; + if (details.kind != PointerDeviceKind.mouse) { + widget.setIsDrawingSelected(widget.drawingData!); + _updateDrawingsMovement(); + } + widget.onMoveDrawing(isDrawingMoved: false); + }, + onLongPressDown: (LongPressDownDetails details) { + widget.onMoveDrawing(isDrawingMoved: true); + isTouchHeld = true; + _previousPosition = details.localPosition; + _updateDrawingsMovement(); + }, + onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) { + final DragUpdateDetails dragDetails = + convertLongPressToDrag(details, _previousPosition); + _previousPosition = details.localPosition; + + _onPanUpdate(dragDetails); + _updateDrawingsMovement(); + }, + onLongPressUp: () { + widget.onMoveDrawing(isDrawingMoved: false); + _onMouseExit(); + isTouchHeld = false; + _draggableStartPoint = _draggableStartPoint.copyWith( + isDragged: false, + ); + _draggableMiddlePoint = _draggableMiddlePoint.copyWith( + isDragged: false, + ); + _draggableEndPoint = _draggableEndPoint.copyWith( + isDragged: false, + ); + _updateDrawingsMovement(); + }, + onPanStart: (DragStartDetails details) { + widget.onMoveDrawing(isDrawingMoved: true); + isTouchHeld = true; + _updateDrawingsMovement(); + }, + onPanUpdate: (DragUpdateDetails details) { + _onPanUpdate(details); + _updateDrawingsMovement(); + }, + onPanEnd: (DragEndDetails details) { + isTouchHeld = false; + setState(() { + _draggableStartPoint = _draggableStartPoint.copyWith( + isDragged: false, + ); + _draggableMiddlePoint = _draggableMiddlePoint.copyWith( + isDragged: false, + ); + _draggableEndPoint = _draggableEndPoint.copyWith( + isDragged: false, + ); + }); + widget.onMoveDrawing(isDrawingMoved: false); + _onMouseExit(); + _updateDrawingsMovement(); + }, + child: CustomPaint( + foregroundPainter: _DrawingPainter( + drawingData: widget.drawingData!, + series: widget.series, + config: repo.items + .where((DrawingToolConfig config) => + config.configId == widget.drawingData!.id) + .first, + theme: context.watch(), + epochFromX: xAxis.epochFromX, + epochToX: xAxis.xFromEpoch, + quoteToY: widget.quoteToCanvasY, + quoteFromY: widget.quoteFromCanvasY, + draggableStartPoint: _draggableStartPoint, + draggableMiddlePoint: _draggableMiddlePoint, + isTouchHeld: isTouchHeld, + isDrawingToolSelected: widget.selectedDrawingTool != null, + draggableEndPoint: _draggableEndPoint, + leftEpoch: xAxis.leftBoundEpoch, + rightEpoch: xAxis.rightBoundEpoch, + updatePositionCallback: ( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) => + draggableEdgePoint.updatePosition( + edgePoint.epoch, + edgePoint.quote, + xAxis.xFromEpoch, + widget.quoteToCanvasY, + ), + setIsOverStartPoint: ({required bool isOverPoint}) { + isOverStartPoint = isOverPoint; + }, + setIsOverMiddlePoint: ({required bool isOverPoint}) { + isOverMiddlePoint = isOverPoint; + }, + setIsOverEndPoint: ({required bool isOverPoint}) { + isOverEndPoint = isOverPoint; + }, + ), + ), + ), + )) + : const SizedBox(); + } +} + +class _DrawingPainter extends CustomPainter { + _DrawingPainter({ + required this.drawingData, + required this.series, + required this.config, + required this.theme, + required this.epochFromX, + required this.epochToX, + required this.quoteToY, + required this.quoteFromY, + required this.draggableStartPoint, + required this.setIsOverStartPoint, + required this.updatePositionCallback, + required this.leftEpoch, + required this.rightEpoch, + this.isDrawingToolSelected = false, + this.isTouchHeld = false, + this.draggableMiddlePoint, + this.draggableEndPoint, + this.setIsOverMiddlePoint, + this.setIsOverEndPoint, + }); + + final DrawingData drawingData; + final DataSeries series; + final DrawingToolConfig config; + final ChartTheme theme; + final bool isDrawingToolSelected; + final bool isTouchHeld; + final int Function(double x) epochFromX; + final double Function(int x) epochToX; + final double Function(double y) quoteToY; + final DraggableEdgePoint draggableStartPoint; + final DraggableEdgePoint? draggableMiddlePoint; + final DraggableEdgePoint? draggableEndPoint; + final void Function({required bool isOverPoint}) setIsOverStartPoint; + final void Function({required bool isOverPoint})? setIsOverMiddlePoint; + final void Function({required bool isOverPoint})? setIsOverEndPoint; + final Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback; + + /// Current left epoch of the chart. + final int leftEpoch; + + /// Current right epoch of the chart. + final int rightEpoch; + + double Function(double) quoteFromY; + + @override + void paint(Canvas canvas, Size size) { + for (final Drawing drawingPart in drawingData.drawingParts) { + drawingPart.onPaint( + canvas, + size, + theme, + epochFromX, + quoteFromY, + epochToX, + quoteToY, + config, + drawingData, + series, + updatePositionCallback, + draggableStartPoint, + draggableMiddlePoint: draggableMiddlePoint, + draggableEndPoint: draggableEndPoint, + ); + } + } + + @override + bool shouldRepaint(_DrawingPainter oldDelegate) => drawingData.shouldRepaint( + oldDelegate.drawingData, + leftEpoch, + rightEpoch, + draggableStartPoint, + draggableEndPoint: draggableEndPoint, + ); + + @override + bool shouldRebuildSemantics(_DrawingPainter oldDelegate) => false; + + @override + bool hitTest(Offset position) { + for (final Drawing drawingPart in drawingData.drawingParts) { + if (drawingPart.hitTest( + position, + epochToX, + quoteToY, + config, + draggableStartPoint, + setIsOverStartPoint, + draggableMiddlePoint: draggableMiddlePoint, + draggableEndPoint: draggableEndPoint, + setIsOverMiddlePoint: setIsOverMiddlePoint, + setIsOverEndPoint: setIsOverEndPoint, + )) { + if (isDrawingToolSelected) { + return false; + } + return true; + } + } + + if (!isTouchHeld && drawingData.isDrawingFinished) { + /// For deselecting the drawing when tapping outside of the drawing. + drawingData + ..isSelected = false + ..isHovered = false; + } + return false; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_tool_widget.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_tool_widget.dart new file mode 100644 index 000000000..f80833c28 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_tool_widget.dart @@ -0,0 +1,144 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/channel/channel_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/continuous/continuous_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing_creator.dart'; + +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing_creator.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; + +/// The class acts as a bridge between the selected drawing tool and the +/// drawing creation process, delegating the specific creation logic to the +/// corresponding drawing tool configurations. +class DrawingToolWidget extends StatelessWidget { + /// Initializes drawing creator area. + const DrawingToolWidget({ + required this.onAddDrawing, + required this.selectedDrawingTool, + required this.quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + required this.chartConfig, + required this.series, + this.shouldStopDrawing, + Key? key, + }) : super(key: key); + + /// ChartConfig for getting pipsize + final ChartConfig chartConfig; + + /// Selected drawing tool. + final DrawingToolConfig selectedDrawingTool; + + /// Series of tick + final DataSeries series; + + /// Callback to pass a newly created drawing to the parent. + final void Function( + String drawingId, + List drawingParts, { + bool isDrawingFinished, + bool isInfiniteDrawing, + List? edgePoints, + }) onAddDrawing; + + /// Conversion function for converting quote to chart's canvas' Y position. + final double Function(double) quoteFromCanvasY; + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove unfinished drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + /// A flag to show when to stop drawing only for drawings which don't have + /// fixed number of points like continuous drawing + final bool? shouldStopDrawing; + + @override + Widget build(BuildContext context) { + // TODO(bahar-deriv): Deligate the creation of drawing to the specific + // drawing tool config + final String drawingToolType = selectedDrawingTool.toJson()['name']; + + switch (drawingToolType) { + case 'dt_channel': + return ChannelDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + clearDrawingToolSelection: clearDrawingToolSelection, + removeUnfinishedDrawing: removeUnfinishedDrawing, + shouldStopDrawing: shouldStopDrawing!, + ); + case 'dt_continuous': + return ContinuousDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + clearDrawingToolSelection: clearDrawingToolSelection, + removeUnfinishedDrawing: removeUnfinishedDrawing, + shouldStopDrawing: shouldStopDrawing!, + ); + case 'dt_fibfan': + return FibfanDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + clearDrawingToolSelection: clearDrawingToolSelection, + removeUnfinishedDrawing: removeUnfinishedDrawing, + ); + case 'dt_horizontal': + return HorizontalDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + chartConfig: chartConfig, + ); + case 'dt_line': + return LineDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + clearDrawingToolSelection: clearDrawingToolSelection, + removeUnfinishedDrawing: removeUnfinishedDrawing, + ); + case 'dt_ray': + return RayDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + clearDrawingToolSelection: clearDrawingToolSelection, + removeUnfinishedDrawing: removeUnfinishedDrawing, + ); + case 'dt_rectangle': + return RectangleDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + clearDrawingToolSelection: clearDrawingToolSelection, + removeUnfinishedDrawing: removeUnfinishedDrawing, + ); + case 'dt_trend': + return TrendDrawingCreator( + onAddDrawing: onAddDrawing, + clearDrawingToolSelection: clearDrawingToolSelection, + quoteFromCanvasY: quoteFromCanvasY, + removeUnfinishedDrawing: removeUnfinishedDrawing, + series: series, + ); + case 'dt_vertical': + return VerticalDrawingCreator( + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + chartConfig: chartConfig, + ); + + // TODO(maryia-binary): add the rest of drawing tools here + default: + return Container(); + } + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.dart new file mode 100644 index 000000000..96e9188a7 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.dart @@ -0,0 +1,350 @@ +import 'dart:math'; + +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/fibfan/fibfan_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/label.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line_vector_drawing_mixin.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'fibfan_drawing.g.dart'; + +/// Fibfan drawing tool. +@JsonSerializable() +class FibfanDrawing extends Drawing with LineVectorDrawingMixin { + /// Initializes + FibfanDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + this.exceedStart = false, + this.exceedEnd = false, + }); + + /// Initializes from JSON. + factory FibfanDrawing.fromJson(Map json) => + _$FibfanDrawingFromJson(json); + + @override + Map toJson() => _$FibfanDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'FibfanDrawing'; + + /// Part of a drawing: 'marker' or 'line' + final DrawingParts drawingPart; + + /// Starting point of drawing + final EdgePoint startEdgePoint; + + /// Ending point of drawing + final EdgePoint endEdgePoint; + + /// If the line pass the start point. + final bool exceedStart; + + /// If the line pass the end point. + final bool exceedEnd; + + /// Marker radius. + final double markerRadius = 10; + + Vector _zeroDegreeVector = const Vector.zero(); + Vector _initialInnerVector = const Vector.zero(); + Vector _middleInnerVector = const Vector.zero(); + Vector _finalInnerVector = const Vector.zero(); + Vector _baseVector = const Vector.zero(); + + /// Keeps the latest position of the start and end point of drawing + Point? _startPoint, _endPoint; + + /// Check if the vector is hit + bool isVectorHit( + Vector vector, + Offset position, + LineStyle lineStyle, + ) { + final double _lineLength = + sqrt(pow(vector.y1 - vector.y0, 2) + pow(vector.x1 - vector.x0, 2)); + + /// Computes the distance between a point and a line which should be less + /// than the line thickness + 6 to make sure the user can easily click on + final double _distance = ((vector.y1 - vector.y0) * position.dx - + (vector.x1 - vector.x0) * position.dy + + vector.x1 * vector.y0 - + vector.y1 * vector.x0) / + sqrt(pow(vector.y1 - vector.y0, 2) + pow(vector.x1 - vector.x0, 2)); + + final double _xDistToStart = position.dx - vector.x0; + final double _yDistToStart = position.dy - vector.y0; + + /// Limit the detection to start and end point of the line + final double _dotProduct = (_xDistToStart * (vector.x1 - vector.x0) + + _yDistToStart * (vector.y1 - vector.y0)) / + _lineLength; + + final bool _isWithinRange = _dotProduct > 0 && _dotProduct < _lineLength; + + return _isWithinRange && _distance.abs() <= lineStyle.thickness + 6; + } + + /// Returns the Triangle path + Path getTrianglePath( + Vector startVector, + Vector endVector, + ) => + Path() + ..moveTo(startVector.x0, startVector.y0) + ..lineTo(startVector.x1, startVector.y1) + ..lineTo(endVector.x1, endVector.y1) + ..close(); + + /// Draw the shaded area between two vectors + void _drawTriangle( + Canvas canvas, + DrawingPaintStyle paint, + FibfanDrawingToolConfig config, + Vector endVector, + ) { + final LineStyle fillStyle = config.fillStyle; + final Path path = getTrianglePath(_baseVector, endVector); + + canvas.drawPath( + path, + paint.fillPaintStyle( + fillStyle.color, + fillStyle.thickness, + )); + } + + // TODO(NA): Return true if FibfanDrawing's on chart view port. + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + true; + + /// Paint the line + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + final DrawingPaintStyle paint = DrawingPaintStyle(); + config as FibfanDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final Paint linePaintStyle = + paint.linePaintStyle(lineStyle.color, lineStyle.thickness); + final List edgePoints = config.edgePoints; + + _startPoint = updatePositionCallback(edgePoints.first, draggableStartPoint); + if (edgePoints.length > 1) { + _endPoint = updatePositionCallback(edgePoints.last, draggableEndPoint!); + } else { + _endPoint = updatePositionCallback(endEdgePoint, draggableEndPoint!); + } + + final double startXCoord = _startPoint!.x; + final double startQuoteToY = _startPoint!.y; + + final double endXCoord = _endPoint!.x; + final double endQuoteToY = _endPoint!.y; + + if (drawingPart == DrawingParts.marker) { + if (endEdgePoint.epoch != 0 && endQuoteToY != 0) { + /// Draw second point + canvas.drawCircle( + Offset(endXCoord, endQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } else if (startEdgePoint.epoch != 0 && startQuoteToY != 0) { + /// Draw first point + canvas.drawCircle( + Offset(startXCoord, startQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } + } else if (drawingPart == DrawingParts.line) { + /// Draw the shaded area between two vectors + Vector _getLineVector( + double _endXCoord, + double _startQuoteToY, + ) => + getLineVector( + startXCoord, + startQuoteToY, + _endXCoord, + _startQuoteToY, + exceedEnd: true, + ); + + /// Defines the degree of initial inner vector from the base vector + const double _initialVectorDegree = 0.618; + + /// Defines the degree of middle inner vector from the base vector + const double _middleVectorDegree = 0.5; + + /// Defines the degree of final inner vector from the base vector + const double _finalVectorDegree = 0.382; + + /// Add vectors + _zeroDegreeVector = _getLineVector(endXCoord, endQuoteToY); + _initialInnerVector = _getLineVector( + endXCoord, + ((endQuoteToY - startQuoteToY) * _initialVectorDegree) + startQuoteToY, + ); + + _middleInnerVector = _getLineVector( + endXCoord, + ((endQuoteToY - startQuoteToY) * _middleVectorDegree) + startQuoteToY, + ); + _finalInnerVector = _getLineVector( + endXCoord, + ((endQuoteToY - startQuoteToY) * _finalVectorDegree) + startQuoteToY, + ); + _baseVector = _getLineVector(endXCoord, startQuoteToY); + + /// Draw shadows + _drawTriangle(canvas, paint, config, _zeroDegreeVector); + _drawTriangle(canvas, paint, config, _initialInnerVector); + _drawTriangle(canvas, paint, config, _middleInnerVector); + _drawTriangle(canvas, paint, config, _finalInnerVector); + + /// Draw markers again to hide their overlap with shadows + canvas + ..drawCircle( + Offset(startXCoord, startQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()) + ..drawCircle( + Offset(endXCoord, endQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()) + + /// Draw vectors + ..drawLine( + Offset(_baseVector.x0, _baseVector.y0), + Offset(_baseVector.x1, _baseVector.y1), + linePaintStyle, + ) + ..drawLine( + Offset(_finalInnerVector.x0, _finalInnerVector.y0), + Offset(_finalInnerVector.x1, _finalInnerVector.y1), + linePaintStyle, + ) + ..drawLine( + Offset(_middleInnerVector.x0, _middleInnerVector.y0), + Offset(_middleInnerVector.x1, _middleInnerVector.y1), + linePaintStyle, + ) + ..drawLine( + Offset(_initialInnerVector.x0, _initialInnerVector.y0), + Offset(_initialInnerVector.x1, _initialInnerVector.y1), + linePaintStyle, + ) + ..drawLine( + Offset(_zeroDegreeVector.x0, _zeroDegreeVector.y0), + Offset(_zeroDegreeVector.x1, _zeroDegreeVector.y1), + linePaintStyle, + ); + + /// Draw labels + Label( + startXCoord: startXCoord.toInt(), + endXCoord: endXCoord.toInt(), + ) + ..drawLabel(canvas, size, lineStyle, zeroDegreeVectorPercentage, + _zeroDegreeVector) + ..drawLabel(canvas, size, lineStyle, initialInnerVectorPercentage, + _initialInnerVector) + ..drawLabel(canvas, size, lineStyle, middleInnerVectorPercentage, + _middleInnerVector) + ..drawLabel(canvas, size, lineStyle, finalInnerVectorPercentage, + _finalInnerVector) + ..drawLabel(canvas, size, lineStyle, baseVectorPercentage, _baseVector); + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen, for any of the edge points + /// it will set "isDragged" to determine which point is clicked + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + final LineStyle lineStyle = config.toJson()['lineStyle']; + bool _isVectorHit(Vector vector) => + isVectorHit(vector, position, lineStyle); + + /// Check if start point clicked + if (_startPoint!.isClicked(position, markerRadius)) { + setIsOverStartPoint(isOverPoint: true); + } else { + setIsOverStartPoint(isOverPoint: false); + } + + /// Check if end point clicked + if (_endPoint!.isClicked(position, markerRadius)) { + setIsOverEndPoint!(isOverPoint: true); + } else { + setIsOverEndPoint!(isOverPoint: false); + } + return _isVectorHit(_baseVector) || + _isVectorHit(_finalInnerVector) || + _isVectorHit(_middleInnerVector) || + _isVectorHit(_initialInnerVector) || + _isVectorHit(_zeroDegreeVector) || + (_startPoint!.isClicked(position, markerRadius) || + _endPoint!.isClicked(position, markerRadius)); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.g.dart new file mode 100644 index 000000000..9b654ce05 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fibfan_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FibfanDrawing _$FibfanDrawingFromJson(Map json) => + FibfanDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + exceedStart: json['exceedStart'] as bool? ?? false, + exceedEnd: json['exceedEnd'] as bool? ?? false, + ); + +Map _$FibfanDrawingToJson(FibfanDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startEdgePoint': instance.startEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + 'exceedStart': instance.exceedStart, + 'exceedEnd': instance.exceedEnd, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing_creator.dart new file mode 100644 index 000000000..b4619b3bb --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing_creator.dart @@ -0,0 +1,108 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/fibfan_drawing.dart'; +import 'package:flutter/material.dart'; + +/// Creates a Fibfan drawing piece by piece collected on every gesture +/// exists in a widget tree starting from selecting a line drawing tool and +/// until drawing is finished +class FibfanDrawingCreator extends DrawingCreator { + /// Initializes the fibfan drawing creator. + const FibfanDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove specific drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + @override + DrawingCreatorState createState() => + _FibfanDrawingCreatorState(); +} + +class _FibfanDrawingCreatorState extends DrawingCreatorState { + /// If drawing has been started. + bool _isPenDown = false; + + @override + void onTap(TapUpDetails details) { + super.onTap(details); + final FibfanDrawingCreator _widget = widget as FibfanDrawingCreator; + + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + tapCount++; + + if (!_isPenDown) { + /// Draw the initial point of the line. + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + _isPenDown = true; + + drawingParts.add(FibfanDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + )); + } else if (!isDrawingFinished) { + /// Draw final point and the whole line. + _isPenDown = false; + isDrawingFinished = true; + final int _currentTap = tapCount - 1; + final int _previousTap = tapCount - 2; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + /// Checks if the initial point and the final point are the same. + if (edgePoints[1] == edgePoints.first) { + /// If the initial point and the 2nd point are the same, + /// remove the drawing and clean the drawing tool selection. + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } else { + /// If the initial point and the final point are not the same, + /// draw the final point and the whole line. + drawingParts.addAll([ + FibfanDrawing( + drawingPart: DrawingParts.marker, + endEdgePoint: edgePoints[_currentTap], + ), + FibfanDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: edgePoints[_previousTap], + endEdgePoint: edgePoints[_currentTap], + exceedStart: true, + exceedEnd: true, + ) + ]); + } + } + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/label.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/label.dart new file mode 100644 index 000000000..8abcf9562 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/fibfan/label.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; + +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; + +/// A constant for the zero degree vector label +const String zeroDegreeVectorPercentage = '0'; + +/// A constant for the initial inner vector label +const String initialInnerVectorPercentage = '38.2'; + +/// A constant for the middle vector label +const String middleInnerVectorPercentage = '50'; + +/// A constant for the final vector label +const String finalInnerVectorPercentage = '61.8'; + +/// A constant for the base vector label +const String baseVectorPercentage = '100'; + +/// A constant for the label horizontal padding +const double labelHorizontalPadding = 25; + +/// A constant for the chart horizontal padding +// TODO(NA): Remove this constant and calculate the y axis label width +const double chartPadding = 60; + +/// A class for drawing the vector label +class Label { + /// Initializes the vector label class + Label({ + required this.startXCoord, + required this.endXCoord, + }); + + /// Start of the drawing + final int startXCoord; + + /// End of the drawing + final int endXCoord; + + /// Returns the x position of the label + double _getX( + String label, + Size size, { + bool isRightSide = false, + }) => + isRightSide + ? size.width - chartPadding - labelHorizontalPadding + : labelHorizontalPadding; + + /// Returns the position of the label on the right side of the chart + Offset _getRightSideLabelsPosition(String label, Size size, Vector vector) { + final double x = _getX(label, size, isRightSide: true); + + final double y = (((vector.y1 - vector.y0) / (vector.x1 - vector.x0)) * + (x - vector.x0)) + + vector.y0; + return Offset(x, y); + } + + /// Returns the position of the label on the left side of the chart + Offset _getLeftSideLabelsPosition(String label, Vector vector) { + final double x = _getX(label, Size.zero); + + final double y = (((vector.y1 - vector.y0) / (vector.x1 - vector.x0)) * + (x - vector.x0)) + + vector.y0; + return Offset(5, y); + } + + /// Returns the text painter for adding labels + TextPainter getTextPainter(String label, Offset textOffset, Color color) { + final TextStyle textStyle = TextStyle( + color: color, + fontWeight: FontWeight.bold, + fontSize: 13, + ); + + final TextSpan textSpan = TextSpan( + text: '$label%', + style: textStyle, + ); + + return TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + } + + /// Draw the vector label + void drawLabel( + Canvas canvas, + Size size, + LineStyle lineStyle, + String label, + Vector endVector, + ) { + final Offset labelOffset = (startXCoord > endXCoord && startXCoord > 10) + ? _getLeftSideLabelsPosition(label, endVector) + : (startXCoord < endXCoord && startXCoord < size.width - chartPadding) + ? _getRightSideLabelsPosition(label, size, endVector) + : Offset.zero; + if (labelOffset != Offset.zero) { + getTextPainter(label, labelOffset, lineStyle.color) + ..layout() + ..paint(canvas, labelOffset); + } + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.dart new file mode 100644 index 000000000..bf3f605c8 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.dart @@ -0,0 +1,149 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/distance_constants.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/horizontal/horizontal_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/paint_drawing_label.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'horizontal_drawing.g.dart'; + +/// Horizontal drawing tool. +/// A tool used to draw straight infinite horizontal line on the chart +@JsonSerializable() +class HorizontalDrawing extends Drawing { + /// Initializes + HorizontalDrawing({ + required this.drawingPart, + required this.chartConfig, + required this.edgePoint, + }); + + /// Initializes from JSON. + factory HorizontalDrawing.fromJson(Map json) => + _$HorizontalDrawingFromJson(json); + + @override + Map toJson() => _$HorizontalDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'HorizontalDrawing'; + + /// Chart config to get pipSize + final ChartConfig? chartConfig; + + /// Part of a drawing: 'horizontal' + final DrawingParts drawingPart; + + /// Starting point of drawing + final EdgePoint edgePoint; + + /// Keeps the latest position of the horizontal line + Point? startPoint; + + // TODO(NA): Return true when the horizontal drawing is in the epoch range. + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + true; + + /// Paint + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + final DrawingPaintStyle paint = DrawingPaintStyle(); + config as HorizontalDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final DrawingPatterns pattern = config.pattern; + final List edgePoints = config.edgePoints; + + startPoint = updatePositionCallback(edgePoints.first, draggableStartPoint); + + final double pointYCoord = startPoint!.y; + final double pointXCoord = startPoint!.x; + + final double startX = pointXCoord - DrawingToolDistance.horizontalDistance, + endingX = pointXCoord + DrawingToolDistance.horizontalDistance; + + if (pattern == DrawingPatterns.solid) { + canvas.drawLine( + Offset(startX, pointYCoord), + Offset(endingX, pointYCoord), + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle(lineStyle.color, lineStyle.thickness) + : paint.linePaintStyle(lineStyle.color, lineStyle.thickness), + ); + if (config.enableLabel) { + paintDrawingLabel( + canvas, + size, + pointYCoord, + 'horizontal', + theme, + chartConfig!, + quoteFromY: quoteFromY, + color: lineStyle.color, + ); + } + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + config as HorizontalDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + + return position.dy > startPoint!.y - lineStyle.thickness - 5 && + position.dy < startPoint!.y + lineStyle.thickness + 5; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.g.dart new file mode 100644 index 000000000..0a289f3cc --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'horizontal_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +HorizontalDrawing _$HorizontalDrawingFromJson(Map json) => + HorizontalDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + chartConfig: json['chartConfig'] == null + ? null + : ChartConfig.fromJson(json['chartConfig'] as Map), + edgePoint: EdgePoint.fromJson(json['edgePoint'] as Map), + )..startPoint = json['startPoint'] == null + ? null + : Point.fromJson(json['startPoint'] as Map); + +Map _$HorizontalDrawingToJson(HorizontalDrawing instance) => + { + 'chartConfig': instance.chartConfig, + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'edgePoint': instance.edgePoint, + 'startPoint': instance.startPoint, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing_creator.dart new file mode 100644 index 000000000..05b67830d --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/horizontal/horizontal_drawing_creator.dart @@ -0,0 +1,60 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:flutter/material.dart'; +import './horizontal_drawing.dart'; + +/// Creates a Horizontal line drawing +class HorizontalDrawingCreator extends DrawingCreator { + /// Initializes the horizontal drawing creator. + const HorizontalDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required ChartConfig chartConfig, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + chartConfig: chartConfig, + ); + + @override + DrawingCreatorState createState() => + _HorizontalDrawingCreatorState(); +} + +class _HorizontalDrawingCreatorState + extends DrawingCreatorState { + @override + void onTap(TapUpDetails details) { + super.onTap(details); + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + isDrawingFinished = true; + + drawingParts.add(HorizontalDrawing( + drawingPart: DrawingParts.line, + edgePoint: edgePoints.first, + chartConfig: widget.chartConfig, + )); + + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart new file mode 100644 index 000000000..077997e2f --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart @@ -0,0 +1,237 @@ +import 'dart:math'; + +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/vector.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line_vector_drawing_mixin.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'line_drawing.g.dart'; + +/// Line drawing tool. A line is a vector defined by two points that is +/// infinite in both directions. +@JsonSerializable() +class LineDrawing extends Drawing with LineVectorDrawingMixin { + /// Initializes + LineDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + this.exceedStart = false, + this.exceedEnd = false, + }); + + /// Initializes from JSON. + factory LineDrawing.fromJson(Map json) => + _$LineDrawingFromJson(json); + + @override + Map toJson() => _$LineDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'LineDrawing'; + + /// Part of a drawing: 'marker' or 'line' + final DrawingParts drawingPart; + + /// Starting point of drawing + final EdgePoint startEdgePoint; + + /// Ending point of drawing + final EdgePoint endEdgePoint; + + /// If the line pass the start point. + final bool exceedStart; + + /// If the line pass the end point. + final bool exceedEnd; + + /// Marker radius. + final double markerRadius = 10; + + Vector _vector = const Vector.zero(); + + /// Keeps the latest position of the start and end point of drawing + Point? _startPoint, _endPoint; + + // TODO(NA): Return true when the line drawing is in epoch range. + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + true; + + /// Paint the line + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + final DrawingPaintStyle paint = DrawingPaintStyle(); + + /// Get the latest config of any drawing tool which is used to draw the line + config as LineDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final DrawingPatterns pattern = config.pattern; + final List edgePoints = config.edgePoints; + + _startPoint = updatePositionCallback(edgePoints.first, draggableStartPoint); + if (edgePoints.length > 1) { + _endPoint = updatePositionCallback(edgePoints.last, draggableEndPoint!); + } else { + _endPoint = updatePositionCallback(endEdgePoint, draggableEndPoint!); + } + + final double startXCoord = _startPoint!.x; + final double startQuoteToY = _startPoint!.y; + + final double endXCoord = _endPoint!.x; + final double endQuoteToY = _endPoint!.y; + + if (drawingPart == DrawingParts.marker) { + if (endEdgePoint.epoch != 0 && endQuoteToY != 0) { + /// Draw first point + canvas.drawCircle( + Offset(endXCoord, endQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } else if (startEdgePoint.epoch != 0 && startQuoteToY != 0) { + /// Draw second point + canvas.drawCircle( + Offset(startXCoord, startQuoteToY), + markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } + } else if (drawingPart == DrawingParts.line) { + _vector = getLineVector( + startXCoord, + startQuoteToY, + endXCoord, + endQuoteToY, + exceedStart: exceedStart, + exceedEnd: exceedEnd, + ); + + if (pattern == DrawingPatterns.solid) { + canvas.drawLine( + Offset(_vector.x0, _vector.y0), + Offset(_vector.x1, _vector.y1), + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle(lineStyle.color, lineStyle.thickness) + : paint.linePaintStyle(lineStyle.color, lineStyle.thickness), + ); + } + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen, for any of the edge points + /// it will call "setIsEdgeDragged" callback function to determine which + /// point is clicked + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + config as LineDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + + double startXCoord = _startPoint!.x; + double startQuoteToY = _startPoint!.y; + + double endXCoord = _endPoint!.x; + double endQuoteToY = _endPoint!.y; + + /// Check if start point clicked + if (_startPoint!.isClicked(position, markerRadius)) { + setIsOverStartPoint(isOverPoint: true); + } else { + setIsOverStartPoint(isOverPoint: false); + } + + /// Check if end point clicked + if (_endPoint!.isClicked(position, markerRadius)) { + setIsOverEndPoint!(isOverPoint: true); + } else { + setIsOverEndPoint!(isOverPoint: false); + } + + startXCoord = _vector.x0; + startQuoteToY = _vector.y0; + endXCoord = _vector.x1; + endQuoteToY = _vector.y1; + + final double lineLength = sqrt( + pow(endQuoteToY - startQuoteToY, 2) + pow(endXCoord - startXCoord, 2)); + + /// Computes the distance between a point and a line which should be less + /// than the line thickness + 6 to make sure the user can easily click on + final double distance = ((endQuoteToY - startQuoteToY) * position.dx - + (endXCoord - startXCoord) * position.dy + + endXCoord * startQuoteToY - + endQuoteToY * startXCoord) / + sqrt(pow(endQuoteToY - startQuoteToY, 2) + + pow(endXCoord - startXCoord, 2)); + + final double xDistToStart = position.dx - startXCoord; + final double yDistToStart = position.dy - startQuoteToY; + + /// Limit the detection to start and end point of the line + final double dotProduct = (xDistToStart * (endXCoord - startXCoord) + + yDistToStart * (endQuoteToY - startQuoteToY)) / + lineLength; + + final bool isWithinRange = dotProduct > 0 && dotProduct < lineLength; + + return isWithinRange && distance.abs() <= lineStyle.thickness + 6 || + (_startPoint!.isClicked(position, markerRadius) || + _endPoint!.isClicked(position, markerRadius)); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.g.dart new file mode 100644 index 000000000..c187e958b --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'line_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LineDrawing _$LineDrawingFromJson(Map json) => LineDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + exceedStart: json['exceedStart'] as bool? ?? false, + exceedEnd: json['exceedEnd'] as bool? ?? false, + ); + +Map _$LineDrawingToJson(LineDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startEdgePoint': instance.startEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + 'exceedStart': instance.exceedStart, + 'exceedEnd': instance.exceedEnd, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing_creator.dart new file mode 100644 index 000000000..905cc3b73 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing_creator.dart @@ -0,0 +1,108 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart'; +import 'package:flutter/material.dart'; + +/// Creates a Line drawing piece by piece collected on every gesture +/// exists in a widget tree starting from selecting a line drawing tool and +/// until drawing is finished +class LineDrawingCreator extends DrawingCreator { + /// Initializes the line drawing creator. + const LineDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove unfinished drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + @override + DrawingCreatorState createState() => _LineDrawingCreatorState(); +} + +class _LineDrawingCreatorState extends DrawingCreatorState { + /// If drawing has been started. + bool _isPenDown = false; + + @override + void onTap(TapUpDetails details) { + super.onTap(details); + + final LineDrawingCreator _widget = widget as LineDrawingCreator; + + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + tapCount++; + + if (!_isPenDown) { + /// Draw the initial point of the line. + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + _isPenDown = true; + + drawingParts.add(LineDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + )); + } else if (!isDrawingFinished) { + /// Draw final point and the whole line. + _isPenDown = false; + isDrawingFinished = true; + final int currentTap = tapCount - 1; + final int previousTap = tapCount - 2; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + /// Checks if the initial point and the final point are the same. + if (edgePoints[1] == edgePoints.first) { + /// If the initial point and the 2nd point are the same, + /// remove the drawing and clean the drawing tool selection. + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } else { + /// If the initial point and the final point are not the same, + /// draw the final point and the whole line. + drawingParts.addAll([ + LineDrawing( + drawingPart: DrawingParts.marker, + endEdgePoint: edgePoints[currentTap], + ), + LineDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: edgePoints[previousTap], + endEdgePoint: edgePoints[currentTap], + exceedStart: true, + exceedEnd: true, + ) + ]); + } + } + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line_vector_drawing_mixin.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line_vector_drawing_mixin.dart new file mode 100644 index 000000000..aeb140f22 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/line_vector_drawing_mixin.dart @@ -0,0 +1,106 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/distance_constants.dart'; + +import 'data_model/vector.dart'; + +/// Mixin to calculate the vector of a line and related functionalities which +/// a drawing tool with a line should have. +mixin LineVectorDrawingMixin { + /// Gets the vector of a line which is defined by two points. (4 values) + /// + /// If [exceedStart] is true, the vector line will be extended further than + /// the start coordinates. Same thing is there for [exceedEnd] and end + /// coordinates. + Vector getLineVector( + double startXCoord, + double startYCoord, + double endXCoord, + double endYCoord, { + bool exceedStart = false, + bool exceedEnd = false, + }) { + Vector vec = Vector( + x0: startXCoord, + y0: startYCoord, + x1: endXCoord, + y1: endYCoord, + ); + + late double earlier, later; + if (exceedEnd && !exceedStart) { + earlier = vec.x0; + if (vec.x0 > vec.x1) { + later = vec.x1 - DrawingToolDistance.horizontalDistance; + } else { + later = vec.x1 + DrawingToolDistance.horizontalDistance; + } + } + if (exceedStart && !exceedEnd) { + later = vec.x1; + + if (vec.x0 > vec.x1) { + earlier = vec.x0 + DrawingToolDistance.horizontalDistance; + } else { + earlier = vec.x0 - DrawingToolDistance.horizontalDistance; + } + } + + if (exceedStart && exceedEnd) { + if (vec.x0 > vec.x1) { + vec = Vector( + x0: endXCoord, + y0: endYCoord, + x1: startXCoord, + y1: startYCoord, + ); + } + + earlier = vec.x0 - DrawingToolDistance.horizontalDistance; + later = vec.x1 + DrawingToolDistance.horizontalDistance; + } + + if (!exceedEnd && !exceedStart) { + if (vec.x0 > vec.x1) { + vec = Vector( + x0: endXCoord, + y0: endYCoord, + x1: startXCoord, + y1: startYCoord, + ); + } + earlier = vec.x0; + later = vec.x1; + } + + final double startY = _getYIntersection(vec, earlier) ?? 0, + endingY = _getYIntersection(vec, later) ?? 0, + startX = earlier, + endingX = later; + + return Vector( + x0: startX, + y0: startY, + x1: endingX, + y1: endingY, + ); + } + + /// Calculates y intersection based on vector points. + double? _getYIntersection(Vector vector, double x) { + final double x1 = vector.x0, x2 = vector.x1, x3 = x, x4 = x; + final double y1 = vector.y0, y2 = vector.y1, y3 = 0, y4 = 10000; + final double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + final double numerator = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + + double mua = numerator / denominator; + if (denominator == 0) { + if (numerator == 0) { + mua = 1; + } else { + return null; + } + } + + final double y = y1 + mua * (y2 - y1); + return y; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/paint_drawing_label.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/paint_drawing_label.dart new file mode 100644 index 000000000..a0438ef8f --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/paint_drawing_label.dart @@ -0,0 +1,91 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'dart:ui' as ui; + +/// Function is responsible for creating the label for vertical and +/// horizontal drawing +void paintDrawingLabel( + Canvas canvas, + Size size, + double coord, + String drawingType, + ChartTheme theme, + ChartConfig config, { + int Function(double x)? epochFromX, + double Function(double)? quoteFromY, + Color color = Colors.white, +}) { + /// Name of the of label + String _labelString = ''; + + final HorizontalBarrierStyle horizontalBarrierStyle = + theme.horizontalBarrierStyle; + + if (drawingType == 'horizontal') { + _labelString = quoteFromY!(coord).toStringAsFixed(config.pipSize); + } else { + final DateTime _dateTime = + DateTime.fromMillisecondsSinceEpoch(epochFromX!(coord), isUtc: true); + + _labelString = DateFormat('MM-dd HH:mm:ss').format(_dateTime); + } + + const double padding = 6; + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: _labelString, + style: TextStyle( + color: calculateTextColor(color), + fontSize: horizontalBarrierStyle.textStyle.fontSize, + height: horizontalBarrierStyle.textStyle.height, + fontFeatures: horizontalBarrierStyle.textStyle.fontFeatures, + fontWeight: horizontalBarrierStyle.textStyle.fontWeight, + ), + ), + textDirection: ui.TextDirection.ltr, + maxLines: 1, + )..layout(maxWidth: size.width); + + final double rectWidth = textPainter.width + 2 * padding; + const double rectHeight = 24; + + RRect rect = RRect.zero; + + if (drawingType == 'horizontal') { + rect = RRect.fromLTRBR( + size.width - rectWidth - 1, + coord - (rectHeight / 2), + size.width - 4, + coord + (rectHeight / 2), + const Radius.circular(4), + ); + } else { + rect = RRect.fromLTRBR( + coord - (rectWidth / 2), + size.height - rectHeight, + coord + (rectWidth / 2), + size.height, + const Radius.circular(4), + ); + } + + canvas.drawRRect( + rect, + Paint() + ..color = color + ..style = PaintingStyle.fill, + ); + if (drawingType == 'horizontal') { + textPainter.paint( + canvas, + Offset(size.width - rectWidth + padding - 2, + coord - textPainter.height / 2)); + } else { + textPainter.paint( + canvas, + Offset(coord - textPainter.width / 2, + size.height - textPainter.height - padding + 1)); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_drawing_creator.dart new file mode 100644 index 000000000..ff74ced7f --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_drawing_creator.dart @@ -0,0 +1,107 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.dart'; +import 'package:flutter/material.dart'; + +/// Creates a Ray drawing piece by piece collected on every gesture +/// exists in a widget tree starting from selecting a line drawing tool and +/// until drawing is finished +class RayDrawingCreator extends DrawingCreator { + /// Initializes the line drawing creator. + const RayDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove specific drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + @override + DrawingCreatorState createState() => + _RayDrawingCreatorState(); +} + +class _RayDrawingCreatorState extends DrawingCreatorState { + /// If drawing has been started. + bool _isPenDown = false; + + @override + void onTap(TapUpDetails details) { + super.onTap(details); + final RayDrawingCreator _widget = widget as RayDrawingCreator; + + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + tapCount++; + + if (!_isPenDown) { + /// Draw the initial point of the line. + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + _isPenDown = true; + + drawingParts.add(RayLineDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + )); + } else if (!isDrawingFinished) { + /// Draw final point and the whole line. + _isPenDown = false; + isDrawingFinished = true; + final int currentTap = tapCount - 1; + final int previousTap = tapCount - 2; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + /// Checks if the initial point and the final point are the same. + if (edgePoints[1] == edgePoints.first) { + /// If the initial point and the 2nd point are the same, + /// remove the drawing and clean the drawing tool selection. + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } else { + /// If the initial point and the final point are not the same, + /// draw the final point and the whole line. + drawingParts.addAll([ + RayLineDrawing( + drawingPart: DrawingParts.marker, + endEdgePoint: edgePoints[currentTap], + ), + RayLineDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: edgePoints[previousTap], + endEdgePoint: edgePoints[currentTap], + exceedEnd: true, + ) + ]); + } + } + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.dart new file mode 100644 index 000000000..cf811f551 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.dart @@ -0,0 +1,167 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/line/line_drawing.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/line/line_drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/ray/ray_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +export 'package:deriv_chart/src/models/tick.dart'; + +part 'ray_line_drawing.g.dart'; + +/// Ray drawing tool. Ray is a vector defined by two points that is +/// infinite in end direction. +@JsonSerializable() +class RayLineDrawing extends Drawing { + /// Initializes + RayLineDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + this.exceedStart = false, + this.exceedEnd = false, + }) : _lineDrawing = LineDrawing( + drawingPart: drawingPart, + startEdgePoint: startEdgePoint, + endEdgePoint: endEdgePoint, + exceedStart: exceedStart, + exceedEnd: exceedEnd); + + /// Initializes from JSON. + factory RayLineDrawing.fromJson(Map json) => + _$RayLineDrawingFromJson(json); + + @override + Map toJson() => _$RayLineDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Drawing part. + final DrawingParts drawingPart; + + /// Start edge point. + final EdgePoint startEdgePoint; + + /// End edge point. + final EdgePoint endEdgePoint; + + /// Whether the start point is exceeded. + final bool exceedStart; + + /// Whether the end point is exceeded. + final bool exceedEnd; + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'RayLineDrawing'; + + final LineDrawing _lineDrawing; + + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + _lineDrawing.needsRepaint( + leftEpoch, + rightEpoch, + draggableStartPoint, + draggableMiddlePoint: draggableMiddlePoint, + draggableEndPoint: draggableEndPoint, + ); + + /// Paint the line + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + config as RayDrawingToolConfig; + + _lineDrawing.onPaint( + canvas, + size, + theme, + epochFromX, + quoteFromY, + epochToX, + quoteToY, + LineDrawingToolConfig( + configId: config.configId, + drawingData: config.drawingData, + lineStyle: config.lineStyle, + pattern: config.pattern, + edgePoints: config.edgePoints, + ), + DrawingData( + id: drawingData.id, + drawingParts: drawingData.drawingParts, + isDrawingFinished: drawingData.isDrawingFinished, + isSelected: drawingData.isSelected, + isHovered: drawingData.isHovered, + ), + series, + updatePositionCallback, + draggableStartPoint, + draggableEndPoint: draggableEndPoint); + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen, for any of the edge points + /// it will call "setIsEdgeDragged" callback function to determine which + /// point is clicked + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + config as RayDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final DrawingPatterns pattern = config.pattern; + + return _lineDrawing.hitTest( + position, + epochToX, + quoteToY, + LineDrawingToolConfig(lineStyle: lineStyle, pattern: pattern), + draggableStartPoint, + setIsOverStartPoint, + draggableEndPoint: draggableEndPoint, + setIsOverEndPoint: setIsOverEndPoint); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.g.dart new file mode 100644 index 000000000..6ef697e7d --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/ray/ray_line_drawing.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ray_line_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RayLineDrawing _$RayLineDrawingFromJson(Map json) => + RayLineDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + exceedStart: json['exceedStart'] as bool? ?? false, + exceedEnd: json['exceedEnd'] as bool? ?? false, + ); + +Map _$RayLineDrawingToJson(RayLineDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startEdgePoint': instance.startEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + 'exceedStart': instance.exceedStart, + 'exceedEnd': instance.exceedEnd, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.dart new file mode 100644 index 000000000..9a4b53052 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.dart @@ -0,0 +1,253 @@ +import 'dart:math'; + +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/rectangle/rectangle_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'rectangle_drawing.g.dart'; + +/// Rectangle drawing tool. +@JsonSerializable() +class RectangleDrawing extends Drawing { + /// Initializes + RectangleDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + }); + + /// Initializes from JSON. + factory RectangleDrawing.fromJson(Map json) => + _$RectangleDrawingFromJson(json); + + @override + Map toJson() => _$RectangleDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'RectangleDrawing'; + + /// Instance of enum including all possible drawing parts(marker,rectangle) + final DrawingParts drawingPart; + + /// Marker radius. + final double _markerRadius = 10; + + /// Keeps the latest position of the start and end point of drawing + Point? _startPoint, _endPoint; + + /// Store the created rectangle in this variable + /// (so it can be used for hitTest as well). + Rect _rect = Rect.zero; + + /// Store the starting X Coordinate + double startXCoord = 0; + + /// Store the starting Y Coordinate + double startYCoord = 0; + + /// Store the ending X Coordinate + double endXCoord = 0; + + /// Store the ending Y Coordinate + double endYCoord = 0; + + /// Starting point of drawing + EdgePoint startEdgePoint; + + /// Ending point of drawing + EdgePoint endEdgePoint; + + /// Function to check if the clicked position (Offset) is on + /// boundary of the rectangle + bool _isClickedOnRectangleBoundary(Rect rect, Offset position) { + /// Width of the rectangle line + const double lineWidth = 3; + const int touchTolerance = 10; + + final List rectangleLinesBoundaries = [ + Rect.fromLTWH( + rect.left - touchTolerance, + rect.top - touchTolerance, + rect.width + touchTolerance * 2, + lineWidth + touchTolerance * 2, + ), + Rect.fromLTWH( + rect.left - touchTolerance, + rect.top - touchTolerance, + lineWidth + touchTolerance * 2, + rect.height + touchTolerance * 2, + ), + Rect.fromLTWH( + rect.right - lineWidth - touchTolerance * 2, + rect.top - touchTolerance, + lineWidth + touchTolerance * 2, + rect.height + touchTolerance * 2, + ), + Rect.fromLTWH( + rect.left - touchTolerance, + rect.bottom - lineWidth - touchTolerance * 2, + rect.width + touchTolerance * 2 + 2, + lineWidth + touchTolerance * 2 + 2, + ), + ]; + + return rectangleLinesBoundaries + .any((Rect lineBound) => lineBound.inflate(2).contains(position)); + } + + /// Paint the rectangle + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + final DrawingPaintStyle paint = DrawingPaintStyle(); + config as RectangleDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final LineStyle fillStyle = config.fillStyle; + final DrawingPatterns pattern = config.pattern; + final List edgePoints = config.edgePoints; + + _startPoint = updatePositionCallback(edgePoints.first, draggableStartPoint); + if (edgePoints.length > 1) { + _endPoint = updatePositionCallback(edgePoints.last, draggableEndPoint!); + } else { + _endPoint = updatePositionCallback(endEdgePoint, draggableEndPoint!); + } + + startXCoord = _startPoint!.x; + startYCoord = _startPoint!.y; + + endXCoord = _endPoint!.x; + endYCoord = _endPoint!.y; + + if (drawingPart == DrawingParts.marker) { + if (endEdgePoint.epoch != 0 && endYCoord != 0) { + /// Draw first marker + canvas.drawCircle( + Offset(endXCoord, endYCoord), + _markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } else if (startEdgePoint.epoch != 0 && startYCoord != 0) { + /// Draw second marker + canvas.drawCircle( + Offset(startXCoord, startYCoord), + _markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle()); + } + } else if (drawingPart == DrawingParts.rectangle) { + if (pattern == DrawingPatterns.solid) { + _rect = Rect.fromPoints( + Offset(startXCoord, startYCoord), Offset(endXCoord, endYCoord)); + + canvas + ..drawRect( + _rect, + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle( + fillStyle.color.withOpacity(0.3), lineStyle.thickness) + : paint.fillPaintStyle( + fillStyle.color.withOpacity(0.3), lineStyle.thickness)) + ..drawRect( + _rect, paint.strokeStyle(lineStyle.color, lineStyle.thickness)); + } + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted lines on the screen, + /// the drawing is selected on clicking on any boundary(line) and markers of + /// the drawing + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + // Calculate the difference between the start marker and the tap point. + final double startDx = position.dx - startXCoord; + final double startDy = position.dy - startYCoord; + + // Calculate the difference between the end marker and the tap point. + final double endDx = position.dx - endXCoord; + final double endDy = position.dy - endYCoord; + + // Getting the distance from end marker + final double endPointDistance = sqrt(endDx * endDx + endDy * endDy); + + // Getting the distance from start marker + final double startPointDistance = + sqrt(startDx * startDx + startDy * startDy); + + /// Check if end point clicked + if (endPointDistance <= _markerRadius) { + setIsOverEndPoint!(isOverPoint: true); + } else { + setIsOverEndPoint!(isOverPoint: false); + } + + /// Check if start point clicked + if (startPointDistance <= _markerRadius) { + setIsOverStartPoint(isOverPoint: true); + } else { + setIsOverStartPoint(isOverPoint: false); + } + + return draggableStartPoint.isDragged || + draggableEndPoint!.isDragged || + (_isClickedOnRectangleBoundary(_rect, position) && + endEdgePoint.epoch != 0); + } + + // TODO(NA): return when the rectangle drawing is inside the epoch range. + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + true; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.g.dart new file mode 100644 index 000000000..839c1d885 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'rectangle_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RectangleDrawing _$RectangleDrawingFromJson(Map json) => + RectangleDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + ) + ..startXCoord = (json['startXCoord'] as num).toDouble() + ..startYCoord = (json['startYCoord'] as num).toDouble() + ..endXCoord = (json['endXCoord'] as num).toDouble() + ..endYCoord = (json['endYCoord'] as num).toDouble(); + +Map _$RectangleDrawingToJson(RectangleDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startXCoord': instance.startXCoord, + 'startYCoord': instance.startYCoord, + 'endXCoord': instance.endXCoord, + 'endYCoord': instance.endYCoord, + 'startEdgePoint': instance.startEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing_creator.dart new file mode 100644 index 000000000..0f475171e --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing_creator.dart @@ -0,0 +1,106 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/rectangle/rectangle_drawing.dart'; +import 'package:flutter/material.dart'; +import '../data_model/drawing_parts.dart'; + +/// Creates a Rectangle drawing piece by piece collected on every gesture +/// exists in a widget tree starting from selecting a rectangle drawing tool and +/// until drawing is finished +class RectangleDrawingCreator extends DrawingCreator { + /// Initializes the rectangle drawing creator. + const RectangleDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove specific drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + @override + _RectangleDrawingCreatorState createState() => + _RectangleDrawingCreatorState(); +} + +class _RectangleDrawingCreatorState + extends DrawingCreatorState { + /// If drawing has been started. + bool _isPenDown = false; + + @override + void onTap(TapUpDetails details) { + super.onTap(details); + + final RectangleDrawingCreator _widget = widget as RectangleDrawingCreator; + + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + if (!_isPenDown) { + /// Draw the initial point. + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + _isPenDown = true; + + drawingParts.add( + RectangleDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + ), + ); + } else if (!isDrawingFinished) { + /// Draw second point and the rectangle. + _isPenDown = false; + isDrawingFinished = true; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + final EdgePoint startEdgePoint = edgePoints.first; + final EdgePoint endEdgePoint = edgePoints[1]; + + if (endEdgePoint == startEdgePoint) { + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } else { + drawingParts.addAll([ + RectangleDrawing( + drawingPart: DrawingParts.marker, + endEdgePoint: endEdgePoint, + ), + RectangleDrawing( + drawingPart: DrawingParts.rectangle, + startEdgePoint: startEdgePoint, + endEdgePoint: endEdgePoint, + ) + ]); + } + } + + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.dart new file mode 100644 index 000000000..65fd84f5a --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.dart @@ -0,0 +1,445 @@ +import 'dart:math'; + +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/trend/trend_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/crosshair/find.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/extensions.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/functions/min_max_calculator.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'trend_drawing.g.dart'; + +/// Trend drawing tool. +@JsonSerializable() +class TrendDrawing extends Drawing { + /// Initializes + TrendDrawing({ + required this.drawingPart, + this.startEdgePoint = const EdgePoint(), + this.endEdgePoint = const EdgePoint(), + }); + + /// Initializes from JSON. + factory TrendDrawing.fromJson(Map json) => + _$TrendDrawingFromJson(json); + + @override + Map toJson() => _$TrendDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'TrendDrawing'; + + /// Instance of enum including all possible drawing parts(marker,rectangle) + final DrawingParts drawingPart; + + /// Marker radius. + final double _markerRadius = 10; + + /// Keeps the latest position of the start and end point of drawing + Point? _startPoint, _endPoint; + + /// Instance of MinMaxCalculator class that holds the minimum + /// and maximum quote in the trend range w.r.t epoch + MinMaxCalculator? _calculator; + + /// Store the starting X Coordinate + double startXCoord = 0; + + /// Store the starting Y Coordinate + double startYCoord = 0; + + /// Store the ending X Coordinate + double endXCoord = 0; + + /// Starting point of drawing + EdgePoint startEdgePoint; + + /// Ending point of drawing + EdgePoint endEdgePoint; + + /// Store the complete rectangle between start,end epoch and + /// minimum,maximum quote. + Rect _mainRect = Rect.zero; + + /// Stores the middle rectangle for the trend , + Rect _middleRect = Rect.zero; + + /// Stores the center of the area for the markers + double _rectCenter = 0; + + /// Stores a flag if the rectangle sides are swapped .i.e the left + /// side is dragged to the right of the right side + bool _isRectangleSwapped = false; + + /// The area impacted upon touch on all lines within the + /// trend drawing tool. .i.e outer rectangle , inner rectangle + /// and center line. + final double _touchTolerance = 5; + + /// Setting the minmax calculator between the range of + /// start and end epoch + MinMaxCalculator? _setCalculator( + int minimumEpoch, + int maximumEpoch, + List? series, + ) { + int minimumEpochIndex = findClosestIndexBinarySearch(minimumEpoch, series); + int maximumEpochIndex = findClosestIndexBinarySearch(maximumEpoch, series); + + if (minimumEpochIndex > maximumEpochIndex) { + final int tempEpochIndex = minimumEpochIndex; + minimumEpochIndex = maximumEpochIndex; + maximumEpochIndex = tempEpochIndex; + } + + final List? epochRange = + series!.sublist(minimumEpochIndex, maximumEpochIndex); + + double minValueOf(Tick t) => t.quote; + double maxValueOf(Tick t) => t.quote; + + return MinMaxCalculator(minValueOf, maxValueOf)..calculate(epochRange!); + } + + /// Function to check if the clicked position (Offset) is on + /// boundary of the rectangle + bool _isClickedOnRectangleBoundary(Rect rect, Offset position) { + /// Width of the rectangle line + const double lineWidth = 3; + + final Rect topLineBounds = Rect.fromLTWH( + rect.left - _touchTolerance, + rect.top - _touchTolerance, + rect.width + _touchTolerance * 2, + lineWidth + _touchTolerance * 2, + ); + + final Rect leftLineBounds = Rect.fromLTWH( + rect.left - _touchTolerance, + rect.top - _touchTolerance, + lineWidth + _touchTolerance * 2, + rect.height + _touchTolerance * 2, + ); + + final Rect rightLineBounds = Rect.fromLTWH( + rect.right - lineWidth - _touchTolerance * 2, + rect.top - _touchTolerance, + lineWidth + _touchTolerance * 2, + rect.height + _touchTolerance * 2, + ); + + final Rect bottomLineBounds = Rect.fromLTWH( + rect.left - _touchTolerance, + rect.bottom - lineWidth - _touchTolerance * 2, + rect.width + _touchTolerance * 2 + 2, + lineWidth + _touchTolerance * 2 + 2, + ); + + return topLineBounds.inflate(2).contains(position) || + leftLineBounds.inflate(2).contains(position) || + rightLineBounds.inflate(2).contains(position) || + bottomLineBounds.inflate(2).contains(position); + } + + // TODO(Bahar-deriv): implement onDrawingMoved here later + + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + { + if (draggableStartPoint.isInViewPortRange(leftEpoch, rightEpoch) || + (draggableEndPoint == null || + draggableEndPoint.isInViewPortRange(leftEpoch, rightEpoch))) { + return true; + } + + return false; + } + } + + /// Paint the trend drawing tools + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + config as TrendDrawingToolConfig; + + final DrawingPaintStyle paint = DrawingPaintStyle(); + final List edgePoints = config.edgePoints; + + // Maximum epoch of the drawing + final int minimumEpoch = draggableStartPoint.getEdgePoint().epoch != 0 + ? draggableStartPoint.getEdgePoint().epoch + : edgePoints.first.epoch; + + // Minimum epoch of the drawing + final int maximumEpoch = draggableEndPoint != null && + draggableEndPoint.getEdgePoint().epoch != 0 + ? draggableEndPoint.getEdgePoint().epoch + : (edgePoints.length > 1 ? edgePoints.last.epoch : endEdgePoint.epoch); + + if (maximumEpoch != 0 && minimumEpoch != 0) { + // setting calculator + _calculator = _setCalculator(minimumEpoch, maximumEpoch, series.entries); + + if (_calculator != null) { + // center of rectangle + _rectCenter = quoteToY(_calculator!.min) + + ((quoteToY(_calculator!.max) - quoteToY(_calculator!.min)) / 2); + } + } + + final LineStyle lineStyle = config.lineStyle; + final LineStyle fillStyle = config.fillStyle; + final DrawingPatterns pattern = config.pattern; + + if (_calculator != null) { + if (maximumEpoch != 0 && minimumEpoch != 0) { + // center of rectangle + _rectCenter = quoteToY(_calculator!.min) + + ((quoteToY(_calculator!.max) - quoteToY(_calculator!.min)) / 2); + } + + _startPoint = updatePositionCallback( + EdgePoint( + epoch: edgePoints.first.epoch, + quote: + _calculator!.min + (_calculator!.max - _calculator!.min) / 2), + draggableStartPoint); + + _endPoint = updatePositionCallback( + EdgePoint( + epoch: (edgePoints.length > 1 + ? edgePoints.last.epoch + : endEdgePoint.epoch), + quote: + _calculator!.min + (_calculator!.max - _calculator!.min) / 2), + draggableEndPoint!); + + startXCoord = _startPoint!.x; + startYCoord = _startPoint!.y; + + endXCoord = _endPoint!.x; + } + + // If the rectangle vertical side are swapped + // .i.e dragging left side to the right of the right side + if (endXCoord < startXCoord && endEdgePoint.epoch != 0) { + final double _tempCoord = endXCoord; + endXCoord = startXCoord; + startXCoord = _tempCoord; + _isRectangleSwapped = true; + } else { + _isRectangleSwapped = false; + } + + /// When both points are dragged to same point + if (_calculator != null && quoteToY(_calculator!.max).isNaN) { + return; + } + + if (drawingPart == DrawingParts.marker) { + if (edgePoints.length == 1) { + _startPoint = updatePositionCallback( + EdgePoint( + epoch: edgePoints.first.epoch, quote: edgePoints.last.quote), + draggableStartPoint); + + startXCoord = _startPoint!.x; + startYCoord = _startPoint!.y; + + canvas.drawCircle( + Offset(startXCoord, startYCoord), + _markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle(), + ); + } else { + canvas + ..drawCircle( + Offset(startXCoord, _rectCenter), + _markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle(), + ) + ..drawCircle( + Offset(endXCoord, _rectCenter), + _markerRadius, + drawingData.shouldHighlight + ? paint.glowyCirclePaintStyle(lineStyle.color) + : paint.transparentCirclePaintStyle(), + ); + } + } + + if (drawingPart == DrawingParts.rectangle) { + /// Store the distance between minimum and maximum quote of the drawing + final double _distance = + (quoteToY(_calculator!.min) - quoteToY(_calculator!.max)).abs(); + + if (pattern == DrawingPatterns.solid) { + _middleRect = Rect.fromLTRB( + startXCoord, + quoteToY(_calculator!.max) + _distance / 3, + endXCoord, + quoteToY(_calculator!.max) + (_distance - _distance / 3), + ); + + _mainRect = Rect.fromLTRB( + startXCoord, + quoteToY(_calculator!.max), + endXCoord, + quoteToY(_calculator!.min), + ); + + canvas + ..drawRect( + _mainRect, + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle( + fillStyle.color.withOpacity(0.2), lineStyle.thickness) + : paint.fillPaintStyle(fillStyle.color, lineStyle.thickness), + ) + ..drawRect( + _mainRect, + paint.strokeStyle(lineStyle.color, lineStyle.thickness), + ) + ..drawRect( + _middleRect, + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle( + fillStyle.color.withOpacity(0.2), lineStyle.thickness) + : paint.fillPaintStyle(fillStyle.color, lineStyle.thickness), + ) + ..drawRect( + _middleRect, + paint.strokeStyle(lineStyle.color, lineStyle.thickness), + ); + } + } + + if (drawingPart == DrawingParts.line) { + if (pattern == DrawingPatterns.solid) { + canvas.drawLine( + Offset(startXCoord, _rectCenter), + Offset(endXCoord, _rectCenter), + paint.glowyCirclePaintStyle(lineStyle.color), + ); + } + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen, + /// the drawing is selected on clicking on any boundary(line) of the drawing + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + // Calculate the difference between the start Point and the tap point. + final double startDx = position.dx - startXCoord; + final double startDy = position.dy - _rectCenter; + + config as TrendDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + + // Calculate the difference between the end Point and the tap point. + final double endDx = position.dx - endXCoord; + final double endDy = position.dy - _rectCenter; + + // Getting the distance of end point + double endPointDistance = sqrt(endDx * endDx + endDy * endDy); + + // Getting the distance of start point + double startPointDistance = sqrt(startDx * startDx + startDy * startDy); + + if (_isRectangleSwapped) { + final double tempDistance = startPointDistance; + startPointDistance = endPointDistance; + endPointDistance = tempDistance; + } + + if (startPointDistance <= _markerRadius) { + setIsOverStartPoint(isOverPoint: true); + } else { + setIsOverStartPoint(isOverPoint: false); + } + + if (endPointDistance <= _markerRadius) { + setIsOverEndPoint!(isOverPoint: true); + } else { + setIsOverEndPoint!(isOverPoint: false); + } + + // For clicking the center line + final double lineArea = (0.5 * + (startXCoord * _rectCenter + + endXCoord * position.dy + + position.dx * _rectCenter - + endXCoord * _rectCenter - + position.dx * _rectCenter - + startXCoord * position.dy)) + .abs(); + + final double baseArea = endXCoord - startXCoord; + final double lineHeight = 2 * lineArea / baseArea; + + if (endEdgePoint.epoch != 0) { + return _isClickedOnRectangleBoundary(_mainRect, position) || + _isClickedOnRectangleBoundary(_middleRect, position) || + startPointDistance <= _markerRadius || + endPointDistance <= _markerRadius || + (lineHeight <= lineStyle.thickness + 6 && + position.dx > startXCoord && + position.dx < endXCoord); + } + return false; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.g.dart new file mode 100644 index 000000000..2f976b353 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'trend_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TrendDrawing _$TrendDrawingFromJson(Map json) => TrendDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + startEdgePoint: json['startEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['startEdgePoint'] as Map), + endEdgePoint: json['endEdgePoint'] == null + ? const EdgePoint() + : EdgePoint.fromJson(json['endEdgePoint'] as Map), + ) + ..startXCoord = (json['startXCoord'] as num).toDouble() + ..startYCoord = (json['startYCoord'] as num).toDouble() + ..endXCoord = (json['endXCoord'] as num).toDouble(); + +Map _$TrendDrawingToJson(TrendDrawing instance) => + { + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'startXCoord': instance.startXCoord, + 'startYCoord': instance.startYCoord, + 'endXCoord': instance.endXCoord, + 'startEdgePoint': instance.startEdgePoint, + 'endEdgePoint': instance.endEdgePoint, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing_creator.dart new file mode 100644 index 000000000..6540598d5 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing_creator.dart @@ -0,0 +1,141 @@ +// ignore_for_file: use_setters_to_change_properties + +import 'package:deriv_chart/src/deriv_chart/chart/crosshair/find.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/trend/trend_drawing.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:flutter/material.dart'; +import '../data_model/drawing_parts.dart'; + +/// Creates a Trend drawing right after selecting the trend drawing tool +/// and until drawing is finished +class TrendDrawingCreator extends DrawingCreator { + /// Initializes the trend drawing creator. + const TrendDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required this.clearDrawingToolSelection, + required this.removeUnfinishedDrawing, + required this.series, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + ); + + /// Series of tick + final DataSeries series; + + /// Callback to clean drawing tool selection. + final VoidCallback clearDrawingToolSelection; + + /// Callback to remove specific drawing from the list of drawings. + final VoidCallback removeUnfinishedDrawing; + + @override + DrawingCreatorState createState() => + _TrendDrawingCreatorState(); +} + +class _TrendDrawingCreatorState extends DrawingCreatorState { + /// If drawing has been started. + bool _isPenDown = false; + + /// Stores coordinate of first point on the graph + int? _startingPointEpoch; + + static const int touchDistanceThreshold = 200; + + @override + void onTap(TapUpDetails details) { + super.onTap(details); + final TrendDrawingCreator _widget = widget as TrendDrawingCreator; + + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + if (!_isPenDown) { + // index of the start point in the series + final int startPointIndex = findClosestIndexBinarySearch( + epochFromX!(position!.dx), _widget.series.entries); + + // starting point on graph + final Tick? startingPoint = _widget.series.entries![startPointIndex]; + + _startingPointEpoch = startingPoint!.epoch; + + /// Draw the initial point of the line. + edgePoints.add( + EdgePoint( + epoch: startingPoint.epoch, + quote: startingPoint.quote, + ), + ); + + _isPenDown = true; + + drawingParts.add( + TrendDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: edgePoints.first, + ), + ); + } else if (!isDrawingFinished) { + edgePoints.add( + EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + ), + ); + + /// Draw final drawing + _isPenDown = false; + isDrawingFinished = true; + final EdgePoint startingEdgePoint = edgePoints.first; + final EdgePoint endingEdgePoint = edgePoints.last; + + // When the second point is on the same y + // coordinate as the first point + if ((_startingPointEpoch! - endingEdgePoint.epoch).abs() <= + touchDistanceThreshold) { + /// remove the drawing and clean the drawing tool selection. + _widget.removeUnfinishedDrawing(); + _widget.clearDrawingToolSelection(); + return; + } + + drawingParts + ..removeAt(0) + ..addAll([ + TrendDrawing( + drawingPart: DrawingParts.rectangle, + startEdgePoint: startingEdgePoint, + endEdgePoint: endingEdgePoint, + ), + TrendDrawing( + drawingPart: DrawingParts.line, + startEdgePoint: startingEdgePoint, + endEdgePoint: endingEdgePoint, + ), + TrendDrawing( + drawingPart: DrawingParts.marker, + startEdgePoint: startingEdgePoint, + endEdgePoint: endingEdgePoint, + ) + ]); + } + + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing.dart new file mode 100644 index 000000000..c2d7a82a7 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing.dart @@ -0,0 +1,148 @@ +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/vertical/vertical_drawing_tool_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_paint_style.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_pattern.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/point.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/paint_drawing_label.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/painting_styles/line_style.dart'; +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'vertical_drawing.g.dart'; + +/// Vertical drawing tool. A vertical is a vertical line defined by one point +/// that is infinite in both directions. +@JsonSerializable() +class VerticalDrawing extends Drawing { + /// Initializes + VerticalDrawing({ + required this.drawingPart, + required this.chartConfig, + required this.edgePoint, + }); + + /// Initializes from JSON. + factory VerticalDrawing.fromJson(Map json) => + _$VerticalDrawingFromJson(json); + + @override + Map toJson() => _$VerticalDrawingToJson(this) + ..putIfAbsent(Drawing.classNameKey, () => nameKey); + + /// Key of drawing tool name property in JSON. + static const String nameKey = 'VerticalDrawing'; + + /// Chart config to get pipSize + final ChartConfig? chartConfig; + + /// Part of a drawing: 'vertical' + final DrawingParts drawingPart; + + /// Starting point of drawing + final EdgePoint edgePoint; + + /// Keeps the latest position of the start of drawing + Point? startPoint; + + @override + bool needsRepaint( + int leftEpoch, + int rightEpoch, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) => + true; + + /// Paint + @override + void onPaint( + Canvas canvas, + Size size, + ChartTheme theme, + int Function(double x) epochFromX, + double Function(double) quoteFromY, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DrawingData drawingData, + DataSeries series, + Point Function( + EdgePoint edgePoint, + DraggableEdgePoint draggableEdgePoint, + ) updatePositionCallback, + DraggableEdgePoint draggableStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + }) { + final DrawingPaintStyle paint = DrawingPaintStyle(); + config as VerticalDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + final DrawingPatterns pattern = config.pattern; + final List edgePoints = config.edgePoints; + + startPoint = updatePositionCallback(edgePoints.first, draggableStartPoint); + + final double xCoord = startPoint!.x; + final double startQuoteToY = startPoint!.y; + if (drawingPart == DrawingParts.line) { + final double startY = startQuoteToY - 10000, + endingY = startQuoteToY + 10000; + + if (pattern == DrawingPatterns.solid) { + canvas.drawLine( + Offset(xCoord, startY), + Offset(xCoord, endingY), + drawingData.shouldHighlight + ? paint.glowyLinePaintStyle(lineStyle.color, lineStyle.thickness) + : paint.linePaintStyle(lineStyle.color, lineStyle.thickness), + ); + if (config.enableLabel) { + paintDrawingLabel( + canvas, + size, + xCoord, + 'vertical', + theme, + chartConfig!, + epochFromX: epochFromX, + color: lineStyle.color, + ); + } + } + } + } + + /// Calculation for detemining whether a user's touch or click intersects + /// with any of the painted areas on the screen + @override + bool hitTest( + Offset position, + double Function(int x) epochToX, + double Function(double y) quoteToY, + DrawingToolConfig config, + DraggableEdgePoint draggableStartPoint, + void Function({required bool isOverPoint}) setIsOverStartPoint, { + DraggableEdgePoint? draggableMiddlePoint, + DraggableEdgePoint? draggableEndPoint, + void Function({required bool isOverPoint})? setIsOverMiddlePoint, + void Function({required bool isOverPoint})? setIsOverEndPoint, + }) { + config as VerticalDrawingToolConfig; + + final LineStyle lineStyle = config.lineStyle; + + return position.dx > startPoint!.x - lineStyle.thickness - 5 && + position.dx < startPoint!.x + lineStyle.thickness + 5; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing.g.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing.g.dart new file mode 100644 index 000000000..9f1f4568f --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'vertical_drawing.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VerticalDrawing _$VerticalDrawingFromJson(Map json) => + VerticalDrawing( + drawingPart: $enumDecode(_$DrawingPartsEnumMap, json['drawingPart']), + chartConfig: json['chartConfig'] == null + ? null + : ChartConfig.fromJson(json['chartConfig'] as Map), + edgePoint: EdgePoint.fromJson(json['edgePoint'] as Map), + )..startPoint = json['startPoint'] == null + ? null + : Point.fromJson(json['startPoint'] as Map); + +Map _$VerticalDrawingToJson(VerticalDrawing instance) => + { + 'chartConfig': instance.chartConfig, + 'drawingPart': _$DrawingPartsEnumMap[instance.drawingPart]!, + 'edgePoint': instance.edgePoint, + 'startPoint': instance.startPoint, + }; + +const _$DrawingPartsEnumMap = { + DrawingParts.marker: 'marker', + DrawingParts.line: 'line', + DrawingParts.rectangle: 'rectangle', +}; diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing_creator.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing_creator.dart new file mode 100644 index 000000000..1cccf2c08 --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/vertical/vertical_drawing_creator.dart @@ -0,0 +1,60 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_creator.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/drawing_parts.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/edge_point.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:flutter/material.dart'; +import './vertical_drawing.dart'; + +/// Creates a Vertical line drawing +class VerticalDrawingCreator extends DrawingCreator { + /// Initializes the vertical drawing creator. + const VerticalDrawingCreator({ + required OnAddDrawing onAddDrawing, + required double Function(double) quoteFromCanvasY, + required ChartConfig chartConfig, + Key? key, + }) : super( + key: key, + onAddDrawing: onAddDrawing, + quoteFromCanvasY: quoteFromCanvasY, + chartConfig: chartConfig, + ); + + @override + DrawingCreatorState createState() => + _VerticalDrawingCreatorState(); +} + +class _VerticalDrawingCreatorState + extends DrawingCreatorState { + @override + void onTap(TapUpDetails details) { + super.onTap(details); + if (isDrawingFinished) { + return; + } + setState(() { + position = details.localPosition; + + edgePoints.add(EdgePoint( + epoch: epochFromX!(position!.dx), + quote: widget.quoteFromCanvasY(position!.dy), + )); + + isDrawingFinished = true; + + drawingParts.add(VerticalDrawing( + drawingPart: DrawingParts.line, + edgePoint: edgePoints.first, + chartConfig: widget.chartConfig, + )); + + widget.onAddDrawing( + drawingId, + drawingParts, + isDrawingFinished: isDrawingFinished, + edgePoints: [...edgePoints], + ); + }); + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/helpers/combine_paths.dart b/lib/src/deriv_chart/chart/data_visualization/helpers/combine_paths.dart new file mode 100644 index 000000000..32702322b --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/helpers/combine_paths.dart @@ -0,0 +1,122 @@ +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/helpers/find_intersection.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:flutter/material.dart'; + +/// Combines and intersects two paths to generate upper and lower paths. +/// +/// Given two paths, this function intersects them to find the common portion +/// and splits the paths into an upper path and a lower path. +(Path, Path) combinePaths( + DataSeries firstSeries, + List firstSeriesEntries, + List secondSeriesEntries, + EpochToX epochToX, + QuoteToY quoteToY, +) { + final Path upperPath = Path(); + final Path lowerPath = Path(); + + Offset convertTickToPoint(Tick tick, int i) => + Offset(epochToX(firstSeries.getEpochOf(tick, i)), quoteToY(tick.quote)); + + void addPoint(List pointsList, Tick tick, int i) { + pointsList.add(convertTickToPoint(tick, i)); + } + + void addPath(Path path, {bool isTopBand = true}) { + if (isTopBand) { + upperPath.addPath(path, Offset.zero); + } else { + lowerPath.addPath(path, Offset.zero); + } + } + + List topPoints = []; + List bottomPoints = []; + + bool isTopBand = false, isBottomBand = false; + + final int startIndex = firstSeries.visibleEntries.startIndex; + final int endIndex = firstSeries.visibleEntries.endIndex; + + for (int i = startIndex; i <= endIndex - 1; i++) { + final Tick firstSeriesTick = firstSeriesEntries[i]; + final Tick secondSeriesTick = secondSeriesEntries[i]; + + final bool newRegion = + (isTopBand && firstSeriesTick.quote < secondSeriesTick.quote) || + (isBottomBand && firstSeriesTick.quote > secondSeriesTick.quote); + + if (newRegion) { + // Find intersection point and creates a path + // with the current set of points. + final Tick? firstSeriesPreviousTick = firstSeriesEntries[i - 1]; + final Tick? secondSeriesPreviousTick = secondSeriesEntries[i - 1]; + Offset? intersecion; + + if (firstSeriesPreviousTick != null && secondSeriesPreviousTick != null) { + intersecion = findIntersection( + convertTickToPoint(firstSeriesTick, i), + convertTickToPoint(firstSeriesPreviousTick, i - 1), + convertTickToPoint(secondSeriesTick, i), + convertTickToPoint(secondSeriesPreviousTick, i - 1), + ); + + if (intersecion != null) { + topPoints.add(intersecion); + } + } + + addPath(_createPath(topPoints, bottomPoints), isTopBand: isTopBand); + + topPoints = intersecion != null ? [intersecion] : []; + bottomPoints = []; + isTopBand = isBottomBand = false; + } else if (firstSeriesTick.quote == secondSeriesTick.quote) { + addPoint(topPoints, firstSeriesTick, i); + // When both the first and second series's quotes are the same, + // it is considered an intersection point, + // and a path with the current set of points is created. + addPath(_createPath(topPoints, bottomPoints), isTopBand: isTopBand); + topPoints = []; + bottomPoints = []; + isTopBand = isBottomBand = false; + } + + if (firstSeriesTick.quote > secondSeriesTick.quote) { + addPoint(topPoints, firstSeriesTick, i); + addPoint(bottomPoints, secondSeriesTick, i); + isTopBand = true; + } else if (firstSeriesTick.quote < secondSeriesTick.quote) { + addPoint(topPoints, secondSeriesTick, i); + addPoint(bottomPoints, firstSeriesTick, i); + isBottomBand = true; + } + } + + addPath(_createPath(topPoints, bottomPoints), isTopBand: isTopBand); + + return (upperPath, lowerPath); +} + +Path _createPath(List topPoints, List bottomPoints) { + final Path path = Path(); + final List allPoints = List.from(topPoints) + ..addAll(bottomPoints.reversed) + ..add(topPoints[0]); + + for (int i = 0; i < allPoints.length; i++) { + final Offset point = allPoints[i]; + + if (i == 0) { + path.moveTo(point.dx, point.dy); + continue; + } + + path.lineTo(point.dx, point.dy); + } + + return path; +} diff --git a/lib/src/deriv_chart/chart/data_visualization/helpers/find_intersection.dart b/lib/src/deriv_chart/chart/data_visualization/helpers/find_intersection.dart new file mode 100644 index 000000000..17491bada --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/helpers/find_intersection.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +/// Find intersection point of two lines. +/// +/// [p1] is first point of line 1. +/// [p2] is second point of line 1. +/// [p3] is first point of line 2. +/// [p4] is second point of line 2. +/// +/// Returns `null` if the two lines don't have an intersection. +Offset? findIntersection(Offset p1, Offset p2, Offset p3, Offset p4) { + if ((p1.dx == p2.dx && p1.dy == p2.dy) || + (p3.dx == p4.dx && p3.dy == p4.dy)) { + return null; + } + + final double denominator = + (p4.dy - p3.dy) * (p2.dx - p1.dx) - (p4.dx - p3.dx) * (p2.dy - p1.dy); + + if (denominator == 0) { + return null; + } + + final double ua = + ((p4.dx - p3.dx) * (p1.dy - p3.dy) - (p4.dy - p3.dy) * (p1.dx - p3.dx)) / + denominator; + final double ub = + ((p2.dx - p1.dx) * (p1.dy - p3.dy) - (p2.dy - p1.dy) * (p1.dx - p3.dx)) / + denominator; + + if (ua < 0 || ua > 1 || ub < 0 || ub > 1) { + return null; + } + + final double x = p1.dx + ua * (p2.dx - p1.dx); + final double y = p1.dy + ua * (p2.dy - p1.dy); + + return Offset(x, y); +} diff --git a/lib/src/deriv_chart/chart/data_visualization/markers/marker_area.dart b/lib/src/deriv_chart/chart/data_visualization/markers/marker_area.dart index 503bcd854..60d578b27 100644 --- a/lib/src/deriv_chart/chart/data_visualization/markers/marker_area.dart +++ b/lib/src/deriv_chart/chart/data_visualization/markers/marker_area.dart @@ -1,12 +1,14 @@ -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/markers/marker_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; import 'package:deriv_chart/src/deriv_chart/chart/gestures/gesture_manager.dart'; import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../chart_data.dart'; import 'animated_active_marker.dart'; +import 'marker.dart'; /// Layer with markers. class MarkerArea extends StatefulWidget { @@ -77,6 +79,7 @@ class _MarkerAreaState extends State { duration: animationDuration, opacity: widget.markerSeries.activeMarker != null ? 0.5 : 1, child: CustomPaint( + child: const SizedBox.shrink(), painter: _MarkerPainter( series: widget.markerSeries, epochToX: xAxis.xFromEpoch, diff --git a/lib/src/deriv_chart/chart/data_visualization/markers/marker_series.dart b/lib/src/deriv_chart/chart/data_visualization/markers/marker_series.dart index 10dc8a3ed..2382e8e36 100644 --- a/lib/src/deriv_chart/chart/data_visualization/markers/marker_series.dart +++ b/lib/src/deriv_chart/chart/data_visualization/markers/marker_series.dart @@ -1,11 +1,15 @@ import 'dart:collection'; -import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/crosshair/find.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:deriv_chart/src/theme/painting_styles/marker_style.dart'; import '../chart_data.dart'; - +import '../chart_series/series.dart'; +import 'active_marker.dart'; +import 'marker.dart'; +import 'marker_icon_painters/marker_icon_painter.dart'; import 'marker_painter.dart'; /// Marker series diff --git a/lib/src/deriv_chart/chart/helpers/functions/conversion.dart b/lib/src/deriv_chart/chart/helpers/functions/conversion.dart index 3f6235fd5..f9cc2392f 100644 --- a/lib/src/deriv_chart/chart/helpers/functions/conversion.dart +++ b/lib/src/deriv_chart/chart/helpers/functions/conversion.dart @@ -94,3 +94,28 @@ double quoteToCanvasY({ return topPadding + pxFromTopBound; } + +/// Returns quote based on the y-coordinate. +double quoteFromCanvasY({ + required double y, + required double topBoundQuote, + required double bottomBoundQuote, + required double canvasHeight, + required double topPadding, + required double bottomPadding, +}) { + final double drawingRange = canvasHeight - topPadding - bottomPadding; + final double quoteRange = topBoundQuote - bottomBoundQuote; + + final double yTopBound = topPadding; + + if (quoteRange == 0) { + return topBoundQuote; + } + + final double yToTopBoundFraction = (y - yTopBound) / drawingRange; + + final double quoteDiffFromTopBound = yToTopBoundFraction * quoteRange; + + return topBoundQuote - quoteDiffFromTopBound; +} diff --git a/lib/src/deriv_chart/chart/helpers/indicator.dart b/lib/src/deriv_chart/chart/helpers/indicator.dart new file mode 100644 index 000000000..8f0b150df --- /dev/null +++ b/lib/src/deriv_chart/chart/helpers/indicator.dart @@ -0,0 +1,27 @@ +import 'dart:ui'; + +import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; +import 'package:flutter/material.dart'; + +import 'functions/helper_functions.dart'; + +/// Returns the last indicator style. +HorizontalBarrierStyle? getLastIndicatorStyle( + Color color, { + bool showLastIndicator = false, +}) { + if (showLastIndicator) { + return HorizontalBarrierStyle( + color: color, + hasLine: false, + textStyle: TextStyle( + fontSize: 10, + height: 1.3, + fontWeight: FontWeight.normal, + color: calculateTextColor(color), + fontFeatures: const [FontFeature.tabularFigures()], + ), + ); + } + return null; +} diff --git a/lib/src/deriv_chart/chart/helpers/paint_functions/paint_loading.dart b/lib/src/deriv_chart/chart/helpers/paint_functions/paint_loading.dart index 4533df568..4ad88c638 100644 --- a/lib/src/deriv_chart/chart/helpers/paint_functions/paint_loading.dart +++ b/lib/src/deriv_chart/chart/helpers/paint_functions/paint_loading.dart @@ -8,9 +8,10 @@ void paintLoadingAnimation({ required Size size, required double loadingAnimationProgress, required double loadingRightBoundX, + Color? color, }) { final Paint loadingPaint = Paint() - ..color = Colors.white12 + ..color = color ?? Colors.white12 ..strokeWidth = 1 ..style = PaintingStyle.fill; diff --git a/lib/src/deriv_chart/chart/loading_animation.dart b/lib/src/deriv_chart/chart/loading_animation.dart index bc86e6e8f..192d74528 100644 --- a/lib/src/deriv_chart/chart/loading_animation.dart +++ b/lib/src/deriv_chart/chart/loading_animation.dart @@ -6,12 +6,16 @@ class LoadingAnimationArea extends StatefulWidget { /// Creates loading animation area. const LoadingAnimationArea({ required this.loadingRightBoundX, + this.loadingAnimationColor, Key? key, }) : super(key: key); /// The right bound in the chart area when loading area is showing. final double loadingRightBoundX; + /// The color of the loading animation. + final Color? loadingAnimationColor; + @override _LoadingAnimationAreaState createState() => _LoadingAnimationAreaState(); } @@ -50,6 +54,7 @@ class _LoadingAnimationAreaState extends State painter: LoadingPainter( loadingAnimationProgress: _loadingAnimationController.value, loadingRightBoundX: widget.loadingRightBoundX, + loadingAnimationColor: widget.loadingAnimationColor, ), ), ), diff --git a/lib/src/deriv_chart/chart/main_chart.dart b/lib/src/deriv_chart/chart/main_chart.dart index c54d14bec..9569febe5 100644 --- a/lib/src/deriv_chart/chart/main_chart.dart +++ b/lib/src/deriv_chart/chart/main_chart.dart @@ -1,5 +1,9 @@ import 'package:collection/collection.dart' show IterableExtension; -import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/misc/chart_controller.dart'; +import 'package:deriv_chart/src/models/chart_axis_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:deriv_chart/src/deriv_chart/chart/crosshair/crosshair_area_web.dart'; import 'package:deriv_chart/src/deriv_chart/chart/crosshair/crosshair_area.dart'; import 'package:deriv_chart/src/deriv_chart/chart/custom_painters/chart_data_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/custom_painters/chart_painter.dart'; @@ -9,30 +13,49 @@ import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - +import '../drawing_tool_chart/drawing_tool_chart.dart'; import 'basic_chart.dart'; +import 'multiple_animated_builder.dart'; +import 'data_visualization/annotations/chart_annotation.dart'; import 'data_visualization/chart_data.dart'; +import 'data_visualization/chart_series/data_series.dart'; +import 'data_visualization/chart_series/series.dart'; +import 'data_visualization/markers/marker_series.dart'; import 'data_visualization/models/animation_info.dart'; +import 'data_visualization/models/chart_object.dart'; import 'helpers/functions/helper_functions.dart'; -import 'multiple_animated_builder.dart'; +import '../../misc/callbacks.dart'; +import '../../theme/chart_theme.dart'; +import 'package:deriv_chart/src/deriv_chart/drawing_tool_chart/drawing_tools.dart'; /// The main chart to display in the chart widget. class MainChart extends BasicChart { /// Initializes the main chart to display in the chart widget. MainChart({ required DataSeries mainSeries, + required this.drawingTools, this.isLive = false, int pipSize = 4, Key? key, this.showLoadingAnimationForHistoricalData = true, this.showDataFitButton = false, + this.showScrollToLastTickButton = true, this.markerSeries, this.controller, this.onCrosshairAppeared, + this.onCrosshairDisappeared, + this.onCrosshairHover, this.overlaySeries, this.annotations, + this.verticalPaddingFraction, + this.loadingAnimationColor, + this.showCurrentTickBlinkAnimation = true, + super.currentTickAnimationDuration, + super.quoteBoundsAnimationDuration, double opacity = 1, ChartAxisConfig? chartAxisConfig, + VisibleQuoteAreaChangedCallback? onQuoteAreaChanged, + this.showCrosshair = false, }) : _mainSeries = mainSeries, chartDataList = [ mainSeries, @@ -45,6 +68,7 @@ class MainChart extends BasicChart { pipSize: pipSize, opacity: opacity, chartAxisConfig: chartAxisConfig, + onQuoteAreaChanged: onQuoteAreaChanged, ); /// The indicator series that are displayed on the main chart. @@ -57,9 +81,19 @@ class MainChart extends BasicChart { /// The series that hold the list markers. final MarkerSeries? markerSeries; + /// Keep the reference to the drawing tools class for + /// sharing data between the DerivChart and the DrawingToolsDialog + final DrawingTools drawingTools; + /// The function that gets called on crosshair appearance. final VoidCallback? onCrosshairAppeared; + /// Called when the crosshair is dismissed. + final VoidCallback? onCrosshairDisappeared; + + /// Called when the crosshair cursor is hovered/moved. + final OnCrosshairHover? onCrosshairHover; + /// Chart's widget controller. final ChartController? controller; @@ -72,9 +106,25 @@ class MainChart extends BasicChart { /// Whether to show the data fit button or not. final bool showDataFitButton; + /// Whether to show the scroll to last tick button or not. + final bool showScrollToLastTickButton; + /// Convenience list to access all chart data. final List chartDataList; + /// Whether the crosshair should be shown or not. + final bool showCrosshair; + + /// Fraction of the chart's height taken by top or bottom padding. + /// Quote scaling (drag on quote area) is controlled by this variable. + final double? verticalPaddingFraction; + + /// The color of the loading animation. + final Color? loadingAnimationColor; + + /// Whether to show current tick blink animation or not. + final bool showCurrentTickBlinkAnimation; + @override _ChartImplementationState createState() => _ChartImplementationState(); } @@ -123,12 +173,11 @@ class _ChartImplementationState extends BasicChartState { void initState() { super.initState(); - widget.controller?.onScrollToLastTick = ({required bool animate}) { - if (mounted) { - // TODO(Ramin): add the ability to close the controller. - xAxis.scrollToLastTick(animate: animate); - } - }; + if (widget.verticalPaddingFraction != null) { + verticalPaddingFraction = widget.verticalPaddingFraction!; + } + + _setupController(); } @override @@ -137,7 +186,9 @@ class _ChartImplementationState extends BasicChartState { didUpdateChartData(oldChart); - if (widget.isLive != oldChart.isLive) { + if (widget.isLive != oldChart.isLive || + widget.showCurrentTickBlinkAnimation != + oldChart.showCurrentTickBlinkAnimation) { _updateBlinkingAnimationStatus(); } @@ -148,7 +199,7 @@ class _ChartImplementationState extends BasicChartState { } void _updateBlinkingAnimationStatus() { - if (widget.isLive) { + if (widget.isLive && widget.showCurrentTickBlinkAnimation) { _currentTickBlinkingController.repeat(reverse: true); } else { _currentTickBlinkingController @@ -186,6 +237,43 @@ class _ChartImplementationState extends BasicChartState { _setupCrosshairZoomOutAnimation(); } + void _setupController() { + widget.controller?.onScrollToLastTick = ({required bool animate}) { + if (mounted) { + // TODO(Ramin): add the ability to close the controller. + xAxis.scrollToLastTick(animate: animate); + } + }; + + widget.controller?.onScale = (double scale) { + xAxis + ..onScaleAndPanStart(ScaleStartDetails()) + ..onScaleUpdate(ScaleUpdateDetails(scale: scale)); + return xAxis.msPerPx; + }; + + widget.controller?.onScroll = (double pxShift) { + xAxis.scrollBy(pxShift); + }; + + widget.controller?.toggleDataFitMode = ({required bool enableDataFit}) { + if (enableDataFit) { + xAxis.enableDataFit(); + } else { + xAxis.disableDataFit(); + } + }; + + widget.controller?.getXFromEpoch = (int epoch) => xAxis.xFromEpoch(epoch); + widget.controller?.getYFromQuote = + (double quote) => chartQuoteToCanvasY(quote); + + widget.controller?.getEpochFromX = (double x) => xAxis.epochFromX(x); + widget.controller?.getQuoteFromY = (double y) => chartQuoteFromCanvasY(y); + + widget.controller?.getMsPerPx = () => xAxis.msPerPx; + } + void _setupCrosshairZoomOutAnimation() { crosshairZoomOutAnimationController = AnimationController( vsync: this, @@ -213,12 +301,16 @@ class _ChartImplementationState extends BasicChartState { _currentTickBlinkingController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), - )..repeat(reverse: true); + ); _currentTickBlinkAnimation = CurvedAnimation( parent: _currentTickBlinkingController, curve: Curves.easeInOut, ); + + if (widget.showCurrentTickBlinkAnimation) { + _currentTickBlinkingController.repeat(reverse: true); + } } @override @@ -255,20 +347,13 @@ class _ChartImplementationState extends BasicChartState { super.build(context), _buildSeries(), _buildAnnotations(), - if (widget.markerSeries != null) - MultipleAnimatedBuilder( - animations: [ - currentTickAnimation, - topBoundQuoteAnimationController, - bottomBoundQuoteAnimationController, - ], - builder: (_, __) => MarkerArea( - markerSeries: widget.markerSeries!, - quoteToCanvasY: chartQuoteToCanvasY, - ), - ), - _buildCrosshairArea(), - if (_isScrollToLastTickAvailable) + if (widget.markerSeries != null) _buildMarkerArea(), + _buildDrawingToolChart(), + if (kIsWeb) _buildCrosshairAreaWeb(), + if (!kIsWeb && !widget.drawingTools.isDrawingMoving) + _buildCrosshairArea(), + if (widget.showScrollToLastTickButton && + _isScrollToLastTickAvailable) Positioned( bottom: 0, right: quoteLabelsTouchAreaWidth, @@ -287,10 +372,24 @@ class _ChartImplementationState extends BasicChartState { }, ); + Widget _buildDrawingToolChart() => MultipleAnimatedBuilder( + animations: [ + topBoundQuoteAnimationController, + bottomBoundQuoteAnimationController, + ], + builder: (_, Widget? child) => DrawingToolChart( + series: widget.mainSeries as DataSeries, + chartQuoteToCanvasY: chartQuoteToCanvasY, + chartQuoteFromCanvasY: chartQuoteFromCanvasY, + drawingTools: widget.drawingTools, + ), + ); + Widget _buildLoadingAnimation() => LoadingAnimationArea( loadingRightBoundX: widget._mainSeries.input.isEmpty ? xAxis.width! : xAxis.xFromEpoch(widget._mainSeries.input.first.epoch), + loadingAnimationColor: widget.loadingAnimationColor, ); Widget _buildAnnotations() => MultipleAnimatedBuilder( @@ -338,6 +437,18 @@ class _ChartImplementationState extends BasicChartState { ), ); + Widget _buildCrosshairAreaWeb() => CrosshairAreaWeb( + mainSeries: widget.mainSeries as DataSeries, + epochFromCanvasX: xAxis.epochFromX, + quoteFromCanvasY: chartQuoteFromCanvasY, + epochToCanvasX: xAxis.xFromEpoch, + quoteToCanvasY: chartQuoteToCanvasY, + quoteLabelsTouchAreaWidth: quoteLabelsTouchAreaWidth, + showCrosshairCursor: widget.showCrosshair, + onCrosshairDisappeared: widget.onCrosshairDisappeared, + onCrosshairHover: widget.onCrosshairHover, + ); + Widget _buildScrollToLastTickButton() => Material( type: MaterialType.circle, color: Colors.transparent, @@ -345,6 +456,7 @@ class _ChartImplementationState extends BasicChartState { child: IconButton( icon: const Icon(Icons.arrow_forward), onPressed: xAxis.scrollToLastTick, + color: context.read().base01Color, ), ); @@ -375,6 +487,18 @@ class _ChartImplementationState extends BasicChartState { ), ); + Widget _buildMarkerArea() => MultipleAnimatedBuilder( + animations: [ + currentTickAnimation, + topBoundQuoteAnimationController, + bottomBoundQuoteAnimationController, + ], + builder: (BuildContext context, _) => MarkerArea( + markerSeries: widget.markerSeries!, + quoteToCanvasY: chartQuoteToCanvasY, + ), + ); + Widget _buildDataFitButton() { final XAxisModel xAxis = context.read(); return Material( diff --git a/lib/src/deriv_chart/chart/x_axis/grid/calc_time_grid.dart b/lib/src/deriv_chart/chart/x_axis/grid/calc_time_grid.dart index 96e1c0ed7..59e0c3c00 100644 --- a/lib/src/deriv_chart/chart/x_axis/grid/calc_time_grid.dart +++ b/lib/src/deriv_chart/chart/x_axis/grid/calc_time_grid.dart @@ -62,6 +62,9 @@ Duration timeGridInterval( PxFromMs pxFromMs, { double minDistanceBetweenLines = 100, List intervals = const [ + Duration(seconds: 1), + Duration(seconds: 2), + Duration(seconds: 3), Duration(seconds: 5), Duration(seconds: 10), Duration(seconds: 30), diff --git a/lib/src/deriv_chart/chart/x_axis/grid/check_new_day.dart b/lib/src/deriv_chart/chart/x_axis/grid/check_new_day.dart new file mode 100644 index 000000000..f9dd79868 --- /dev/null +++ b/lib/src/deriv_chart/chart/x_axis/grid/check_new_day.dart @@ -0,0 +1,8 @@ +/// Returns true if a new day is started 0:0:0 +bool checkNewDate(DateTime time) { + final bool is0h0m0s = time.hour == 0 && time.minute == 0 && time.second == 0; + + return (time.month == 1 && time.day == 1 && is0h0m0s) || + (time.day == 1 && is0h0m0s) || + is0h0m0s; +} diff --git a/lib/src/deriv_chart/chart/x_axis/grid/paint_x_grid.dart b/lib/src/deriv_chart/chart/x_axis/grid/paint_x_grid.dart index 1ef027c94..344520968 100644 --- a/lib/src/deriv_chart/chart/x_axis/grid/paint_x_grid.dart +++ b/lib/src/deriv_chart/chart/x_axis/grid/paint_x_grid.dart @@ -1,4 +1,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/x_axis/grid/check_new_day.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/x_axis/grid/time_label.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_chart/src/theme/painting_styles/grid_style.dart'; import 'package:flutter/material.dart'; @@ -6,19 +9,28 @@ import 'package:flutter/material.dart'; void paintXGrid( Canvas canvas, Size size, { - required List timeLabels, required List xCoords, - required GridStyle style, + required ChartTheme style, + required List timestamps, }) { - assert(timeLabels.length == xCoords.length); + assert(timestamps.length == xCoords.length); + final GridStyle gridStyle = style.gridStyle; + + _paintTimeGridLines( + canvas, + size, + xCoords, + style, + gridStyle, + timestamps, + ); - _paintTimeGridLines(canvas, size, xCoords, style); _paintTimeLabels( canvas, size, xCoords: xCoords, - timeLabels: timeLabels, - style: style, + gridStyle: gridStyle, + timestamps: timestamps, ); } @@ -26,16 +38,20 @@ void _paintTimeGridLines( Canvas canvas, Size size, List xCoords, - GridStyle style, + ChartTheme style, + GridStyle gridStyle, + List time, ) { - for (final double x in xCoords) { + for (int i = 0; i < xCoords.length; i++) { canvas.drawLine( - Offset(x, 0), - Offset(x, size.height - style.xLabelsAreaHeight), + Offset(xCoords[i], 0), + Offset(xCoords[i], size.height - gridStyle.xLabelsAreaHeight), Paint() - ..color = style.gridLineColor + ..color = checkNewDate(time[i]) + ? style.verticalBarrierStyle.color + : gridStyle.gridLineColor ..style = PaintingStyle.stroke - ..strokeWidth = style.lineThickness, + ..strokeWidth = gridStyle.lineThickness, ); } } @@ -43,19 +59,19 @@ void _paintTimeGridLines( void _paintTimeLabels( Canvas canvas, Size size, { - required List timeLabels, required List xCoords, - required GridStyle style, + required GridStyle gridStyle, + required List timestamps, }) { - timeLabels.asMap().forEach((int index, String timeLabel) { + for (int index = 0; index < timestamps.length; index++) { paintText( canvas, - text: timeLabel, + text: timeLabel(timestamps[index]), anchor: Offset( xCoords[index], - size.height - style.xLabelsAreaHeight / 2, + size.height - gridStyle.xLabelsAreaHeight / 2, ), - style: style.xLabelStyle, + style: gridStyle.xLabelStyle, ); - }); + } } diff --git a/lib/src/deriv_chart/chart/x_axis/grid/x_grid_painter.dart b/lib/src/deriv_chart/chart/x_axis/grid/x_grid_painter.dart index 872aa0e5b..8df7e577d 100644 --- a/lib/src/deriv_chart/chart/x_axis/grid/x_grid_painter.dart +++ b/lib/src/deriv_chart/chart/x_axis/grid/x_grid_painter.dart @@ -1,4 +1,4 @@ -import 'package:deriv_chart/src/theme/painting_styles/grid_style.dart'; +import 'package:deriv_chart/deriv_chart.dart'; import 'package:flutter/material.dart'; import 'paint_x_grid.dart'; @@ -7,30 +7,30 @@ import 'paint_x_grid.dart'; class XGridPainter extends CustomPainter { /// Creates x-axis painter. XGridPainter({ - required this.timeLabels, required this.xCoords, required this.style, + required this.timestamps, }); - /// Time labels. - final List timeLabels; - /// X-coordinates of time labels. final List xCoords; /// Style of the grid. - final GridStyle style; + final ChartTheme style; + + /// List of DateTime on screen + final List timestamps; @override void paint(Canvas canvas, Size size) { - if (timeLabels.isEmpty || xCoords.isEmpty) { + if (timestamps.isEmpty || xCoords.isEmpty) { return; } paintXGrid( canvas, size, - timeLabels: timeLabels, + timestamps: timestamps, xCoords: xCoords, style: style, ); @@ -38,7 +38,7 @@ class XGridPainter extends CustomPainter { @override bool shouldRepaint(XGridPainter oldDelegate) => - timeLabels != oldDelegate.timeLabels || + timestamps != oldDelegate.timestamps || xCoords != oldDelegate.xCoords || style != oldDelegate.style; diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis.dart b/lib/src/deriv_chart/chart/x_axis/x_axis.dart index d1281ec52..d5ba688f6 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'grid/time_label.dart'; import 'grid/x_grid_painter.dart'; import 'x_axis_model.dart'; @@ -27,6 +26,11 @@ class XAxis extends StatefulWidget { this.onVisibleAreaChanged, this.minEpoch, this.maxEpoch, + this.maxCurrentTickOffset, + this.msPerPx, + this.minIntervalWidth, + this.maxIntervalWidth, + this.minElapsedTimeToFollow = 0, Key? key, }) : super(key: key); @@ -54,6 +58,25 @@ class XAxis extends StatefulWidget { /// Number of digits after decimal point in price final int pipSize; + /// Max distance between rightBoundEpoch and nowEpoch in pixels. + final double? maxCurrentTickOffset; + + /// Specifies the zoom level of the chart. + final double? msPerPx; + + /// Specifies the minimum interval width + /// that is used for calculating the maximum msPerPx. + final double? minIntervalWidth; + + /// Specifies the maximum interval width + /// that is used for calculating the maximum msPerPx. + final double? maxIntervalWidth; + + /// Specifies the minimum time in milliseconds before which it can update the + /// rightBoundEpoch when the chart is in follow mode. This is used to control + /// the number of frames painted each second. + final int minElapsedTimeToFollow; + @override _XAxisState createState() => _XAxisState(); } @@ -84,6 +107,10 @@ class _XAxisState extends State with TickerProviderStateMixin { maxEpoch: widget.maxEpoch, maxCurrentTickOffset: chartConfig.chartAxisConfig.maxCurrentTickOffset, defaultIntervalWidth: chartConfig.chartAxisConfig.defaultIntervalWidth, + msPerPx: widget.msPerPx, + minIntervalWidth: widget.minIntervalWidth, + maxIntervalWidth: widget.maxIntervalWidth, + minElapsedTimeToFollow: widget.minElapsedTimeToFollow, ); _ticker = createTicker(_model.onNewFrame)..start(); @@ -110,6 +137,7 @@ class _XAxisState extends State with TickerProviderStateMixin { isLive: widget.isLive, granularity: context.read().granularity, entries: widget.entries, + minElapsedTimeToFollow: widget.minElapsedTimeToFollow, ); } @@ -143,21 +171,21 @@ class _XAxisState extends State with TickerProviderStateMixin { return Stack( fit: StackFit.expand, children: [ - if (context.read().chartAxisConfig.showEpochGrid) - RepaintBoundary( - child: CustomPaint( - painter: XGridPainter( - timeLabels: _noOverlapGridTimestamps - .map((DateTime time) => timeLabel(time)) - .toList(), - xCoords: _noOverlapGridTimestamps - .map((DateTime time) => - _model.xFromEpoch(time.millisecondsSinceEpoch)) - .toList(), - style: _chartTheme.gridStyle, - ), + if (context.read().chartAxisConfig.showEpochGrid) + RepaintBoundary( + child: CustomPaint( + painter: XGridPainter( + timestamps: _noOverlapGridTimestamps + .map((DateTime time) => time) + .toList(), + xCoords: _noOverlapGridTimestamps + .map((DateTime time) => + _model.xFromEpoch(time.millisecondsSinceEpoch)) + .toList(), + style: _chartTheme, ), ), + ), Padding( padding: EdgeInsets.only( bottom: _chartTheme.gridStyle.xLabelsAreaHeight, diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index fc5f8732f..b5bcfcae2 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -58,6 +58,10 @@ class XAxisModel extends ChangeNotifier { bool startWithDataFitMode = false, int? minEpoch, int? maxEpoch, + double? msPerPx, + double? minIntervalWidth, + double? maxIntervalWidth, + int minElapsedTimeToFollow = 0, this.onScale, this.onScroll, }) { @@ -72,10 +76,14 @@ class XAxisModel extends ChangeNotifier { _lastEpoch = DateTime.now().millisecondsSinceEpoch; _granularity = granularity; - _msPerPx = _defaultMsPerPx; + _msPerPx = msPerPx ?? _defaultMsPerPx; _isLive = isLive; + _maxCurrentTickOffset = maxCurrentTickOffset; _rightBoundEpoch = _maxRightBoundEpoch; _dataFitMode = startWithDataFitMode; + _minIntervalWidth = minIntervalWidth ?? 1; + _maxIntervalWidth = maxIntervalWidth ?? 80; + _minElapsedTimeToFollow = minElapsedTimeToFollow; _updateEntries(entries); @@ -83,7 +91,7 @@ class XAxisModel extends ChangeNotifier { ..addListener(() { final double diff = _scrollAnimationController.value - (_prevScrollAnimationValue ?? 0); - _scrollBy(diff); + scrollBy(diff); if (hasHitLimit) { _scrollAnimationController.stop(); @@ -96,17 +104,19 @@ class XAxisModel extends ChangeNotifier { /// Limits panning to the right. final double maxCurrentTickOffset; - // TODO(NA): Allow customization of this setting. - /// Scaling will not resize intervals to be smaller than this. - static const int minIntervalWidth = 1; + late double _minIntervalWidth; + + late double _maxIntervalWidth; - // TODO(NA): Allow customization of this setting. - /// Scaling will not resize intervals to be bigger than this. - static const int maxIntervalWidth = 80; + late int _minElapsedTimeToFollow; /// Default to this interval width on granularity change. final double defaultIntervalWidth; + /// Max distance between [rightBoundEpoch] and [_nowEpoch] in pixels. + /// Limits panning to the right. + late double _maxCurrentTickOffset; + late bool _isLive; /// for calculating time between two frames @@ -152,10 +162,10 @@ class XAxisModel extends ChangeNotifier { set rightBoundEpoch(int value) => _rightBoundEpoch = value; /// Current scrolling lower bound. - int get _minRightBoundEpoch => _shiftEpoch(_minEpoch, maxCurrentTickOffset); + int get _minRightBoundEpoch => _shiftEpoch(_minEpoch, _maxCurrentTickOffset); /// Current scrolling upper bound. - int get _maxRightBoundEpoch => _shiftEpoch(_maxEpoch, maxCurrentTickOffset); + int get _maxRightBoundEpoch => _shiftEpoch(_maxEpoch, _maxCurrentTickOffset); /// Has hit left or right panning limit. bool get hasHitLimit => @@ -176,10 +186,10 @@ class XAxisModel extends ChangeNotifier { double get msPerPx => _msPerPx; /// Min value for [_msPerPx]. Limits zooming in. - double get _minMsPerPx => _granularity / maxIntervalWidth; + double get _minMsPerPx => _granularity / _maxIntervalWidth; /// Max value for [_msPerPx]. Limits zooming out. - double get _maxMsPerPx => _granularity / minIntervalWidth; + double get _maxMsPerPx => _granularity / _minIntervalWidth; /// Starting value for [_msPerPx]. double get _defaultMsPerPx => _granularity / defaultIntervalWidth; @@ -211,10 +221,12 @@ class XAxisModel extends ChangeNotifier { _nowEpoch = (_entries?.isNotEmpty ?? false) ? _entries!.last.epoch : _nowEpoch + elapsedMs; - _lastEpoch = newNowTime; // TODO(NA): Consider refactoring the switch with OOP pattern. https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html switch (_currentViewingMode) { case ViewingMode.followCurrentTick: + if (elapsedMs < _minElapsedTimeToFollow) { + return; + } _scrollTo(_rightBoundEpoch + elapsedMs); break; case ViewingMode.fitData: @@ -227,11 +239,13 @@ class XAxisModel extends ChangeNotifier { } break; case ViewingMode.constantScrollSpeed: - _scrollBy(_panSpeed * elapsedMs); + scrollBy(_panSpeed * elapsedMs); break; case ViewingMode.stationary: break; } + + _lastEpoch = newNowTime; } /// Updates scrolling bounds and time gaps based on the main chart's entries. @@ -304,7 +318,18 @@ class XAxisModel extends ChangeNotifier { /// Updates chart's isLive property. /// /// Should be called before [_updateGranularity] and [_updateEntries] - void _updateIsLive(bool? isLive) => _isLive = isLive ?? true; + void _updateIsLive(bool? isLive) { + if (isLive == null || _isLive == isLive) { + return; + } + _isLive = isLive; + } + + void _updateMinElapsedTimeToFollow(int? minElapsedTimeToFollow) { + if (minElapsedTimeToFollow != null) { + _minElapsedTimeToFollow = minElapsedTimeToFollow; + } + } /// Fits available data to screen. void _fitData() { @@ -407,7 +432,7 @@ class XAxisModel extends ChangeNotifier { /// Called when user is panning the chart. void onPanUpdate(DragUpdateDetails details) { - _scrollBy(-details.delta.dx); + scrollBy(-details.delta.dx); } /// Called at the end of scale and pan gestures. @@ -415,27 +440,36 @@ class XAxisModel extends ChangeNotifier { _triggerScrollMomentum(details.velocity); } + /// Called to scale the chart + void scale(double scale) { + _msPerPx = (_prevMsPerPx! / scale).clamp(_minMsPerPx, _maxMsPerPx); + onScale?.call(); + notifyListeners(); + } + + /// Called to scroll the chart + void scrollBy(double pxShift) { + _rightBoundEpoch = _shiftEpoch(_rightBoundEpoch, pxShift); + _clampRightBoundEpoch(); + onScroll?.call(); + notifyListeners(); + } + void _scaleWithNowFixed(ScaleUpdateDetails details) { final double nowToRightBound = pxBetween(_nowEpoch, rightBoundEpoch); - _scale(details.scale); + scale(details.scale); _rightBoundEpoch = _shiftEpoch(_nowEpoch, nowToRightBound); } void _scaleWithFocalPointFixed(ScaleUpdateDetails details) { final double focalToRightBound = width! - details.focalPoint.dx; final int focalEpoch = _shiftEpoch(rightBoundEpoch, -focalToRightBound); - _scale(details.scale); + scale(details.scale); _rightBoundEpoch = _shiftEpoch(focalEpoch, focalToRightBound); } - void _scale(double scale) { - _msPerPx = (_prevMsPerPx! / scale).clamp(_minMsPerPx, _maxMsPerPx); - onScale?.call(); - notifyListeners(); - } - void _scrollTo(int rightBoundEpoch) { - if (_rightBoundEpoch != rightBoundEpoch) { + if (width != null && _rightBoundEpoch != rightBoundEpoch) { _rightBoundEpoch = rightBoundEpoch; _clampRightBoundEpoch(); onScroll?.call(); @@ -443,13 +477,6 @@ class XAxisModel extends ChangeNotifier { } } - void _scrollBy(double pxShift) { - _rightBoundEpoch = _shiftEpoch(_rightBoundEpoch, pxShift); - _clampRightBoundEpoch(); - onScroll?.call(); - notifyListeners(); - } - /// Animate scrolling to current tick. void scrollToLastTick({bool animate = true}) { final Duration duration = @@ -457,7 +484,7 @@ class XAxisModel extends ChangeNotifier { final int target = _shiftEpoch( // _lastEntryEpoch will be removed later. (_entries?.isNotEmpty ?? false) ? _entries!.last.epoch : _nowEpoch, - maxCurrentTickOffset) + + _maxCurrentTickOffset) + duration.inMilliseconds; final double distance = target > _rightBoundEpoch @@ -498,10 +525,12 @@ class XAxisModel extends ChangeNotifier { List? entries, int? minEpoch, int? maxEpoch, + int? minElapsedTimeToFollow, }) { _updateIsLive(isLive); _updateGranularity(granularity); _updateEntries(entries); + _updateMinElapsedTimeToFollow(minElapsedTimeToFollow); _minEpoch = minEpoch ?? _minEpoch; _maxEpoch = maxEpoch ?? _maxEpoch; diff --git a/lib/src/deriv_chart/deriv_chart.dart b/lib/src/deriv_chart/deriv_chart.dart index dec9c1410..d2799909c 100644 --- a/lib/src/deriv_chart/deriv_chart.dart +++ b/lib/src/deriv_chart/deriv_chart.dart @@ -1,15 +1,20 @@ -import 'package:deriv_chart/generated/l10n.dart'; +import 'package:deriv_chart/src/add_ons/add_on_config.dart'; +import 'package:deriv_chart/src/add_ons/add_ons_repository.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tools_dialog.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_config.dart'; -import 'package:deriv_chart/src/add_ons/indicators_ui/indicator_repository.dart'; import 'package:deriv_chart/src/add_ons/indicators_ui/indicators_dialog.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; -import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/markers/marker_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/chart_object.dart'; +import 'package:deriv_chart/src/deriv_chart/drawing_tool_chart/drawing_tools.dart'; import 'package:deriv_chart/src/misc/callbacks.dart'; import 'package:deriv_chart/src/misc/chart_controller.dart'; import 'package:deriv_chart/src/models/chart_axis_config.dart'; -import 'package:deriv_chart/src/models/indicator_input.dart'; +import 'package:deriv_chart/src/misc/extensions.dart'; import 'package:deriv_chart/src/models/tick.dart'; import 'package:deriv_chart/src/theme/chart_theme.dart'; import 'package:deriv_chart/src/widgets/animated_popup.dart'; @@ -17,26 +22,44 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'chart/chart.dart'; -import 'chart/data_visualization/models/chart_object.dart'; - /// A wrapper around the [Chart] which handles adding indicators to the chart. class DerivChart extends StatefulWidget { /// Initializes const DerivChart({ required this.mainSeries, required this.granularity, + required this.activeSymbol, this.markerSeries, this.controller, this.onCrosshairAppeared, + this.onCrosshairDisappeared, + this.onCrosshairHover, this.onVisibleAreaChanged, + this.onQuoteAreaChanged, this.theme, this.isLive = false, this.dataFitEnabled = false, + this.showCrosshair = true, this.annotations, this.opacity = 1.0, this.pipSize = 4, this.chartAxisConfig = const ChartAxisConfig(), + this.indicatorsRepo, + this.drawingToolsRepo, + this.drawingTools, + this.maxCurrentTickOffset, + this.msPerPx, + this.minIntervalWidth, + this.maxIntervalWidth, + this.minElapsedTimeToFollow = 0, + this.currentTickAnimationDuration, + this.quoteBoundsAnimationDuration, + this.showCurrentTickBlinkAnimation, + this.verticalPaddingFraction, + this.bottomChartTitleMargin, + this.showDataFitButton, + this.showScrollToLastTickButton, + this.loadingAnimationColor, Key? key, }) : super(key: key); @@ -46,6 +69,9 @@ class DerivChart extends StatefulWidget { /// Open position marker series. final MarkerSeries? markerSeries; + /// Current active symbol. + final String activeSymbol; + /// Chart's controller final ChartController? controller; @@ -59,9 +85,18 @@ class DerivChart extends StatefulWidget { /// Called when crosshair details appear after long press. final VoidCallback? onCrosshairAppeared; + /// Called when the crosshair is dismissed. + final VoidCallback? onCrosshairDisappeared; + + /// Called when the crosshair cursor is hovered/moved. + final OnCrosshairHoverCallback? onCrosshairHover; + /// Called when chart is scrolled or zoomed. final VisibleAreaChangedCallback? onVisibleAreaChanged; + /// Callback provided by library user. + final VisibleQuoteAreaChangedCallback? onQuoteAreaChanged; + /// Chart's theme. final ChartTheme? theme; @@ -72,7 +107,6 @@ class DerivChart extends StatefulWidget { final ChartAxisConfig chartAxisConfig; /// Whether the chart should be showing live data or not. - /// /// In case of being true the chart will keep auto-scrolling when its visible /// area is on the newest ticks/candles. final bool isLive; @@ -83,105 +117,247 @@ class DerivChart extends StatefulWidget { /// Chart's opacity, Will be applied on the [mainSeries]. final double opacity; + /// Whether the crosshair should be shown or not. + final bool showCrosshair; + + /// Max distance between rightBoundEpoch and nowEpoch in pixels. + final double? maxCurrentTickOffset; + + /// Specifies the zoom level of the chart. + final double? msPerPx; + + /// Specifies the minimum interval width + /// that is used for calculating the maximum msPerPx. + final double? minIntervalWidth; + + /// Specifies the maximum interval width + /// that is used for calculating the maximum msPerPx. + final double? maxIntervalWidth; + + /// Specifies the minimum time in milliseconds before which it can update the + /// rightBoundEpoch when the chart is in follow mode. This is used to control + /// the number of frames painted each second. + final int minElapsedTimeToFollow; + + /// Duration of the current tick animated transition. + final Duration? currentTickAnimationDuration; + + /// Duration of quote bounds animated transition. + final Duration? quoteBoundsAnimationDuration; + + /// Whether to show current tick blink animation or not. + final bool? showCurrentTickBlinkAnimation; + + /// Fraction of the chart's height taken by top or bottom padding. + /// Quote scaling (drag on quote area) is controlled by this variable. + final double? verticalPaddingFraction; + + /// Specifies the margin to prevent overlap. + final EdgeInsets? bottomChartTitleMargin; + + /// Whether the data fit button is shown or not. + final bool? showDataFitButton; + + /// Whether to show the scroll to last tick button or not. + final bool? showScrollToLastTickButton; + + /// The color of the loading animation. + final Color? loadingAnimationColor; + + /// Chart's indicators + final Repository? indicatorsRepo; + + /// Chart's drawings + final Repository? drawingToolsRepo; + + /// Drawing tools + final DrawingTools? drawingTools; + @override _DerivChartState createState() => _DerivChartState(); } class _DerivChartState extends State { - final IndicatorsRepository _indicatorsRepo = IndicatorsRepository(); + late AddOnsRepository _indicatorsRepo; + + late AddOnsRepository _drawingToolsRepo; + + final DrawingTools _drawingTools = DrawingTools(); @override void initState() { super.initState(); - loadSavedIndicators(); + + _initRepos(); } - Future loadSavedIndicators() async { + @override + void didUpdateWidget(covariant DerivChart oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.drawingToolsRepo == null && + widget.activeSymbol != oldWidget.activeSymbol) { + loadSavedIndicatorsAndDrawingTools(); + } + } + + void _initRepos() { + _indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + onEditCallback: showIndicatorsDialog, + currentSymbol: widget.activeSymbol, + ); + + _drawingToolsRepo = AddOnsRepository( + createAddOn: (Map map) => + DrawingToolConfig.fromJson(map), + onEditCallback: showDrawingToolsDialog, + currentSymbol: widget.activeSymbol, + ); + if (widget.drawingToolsRepo == null) { + loadSavedIndicatorsAndDrawingTools(); + } + } + + Future loadSavedIndicatorsAndDrawingTools() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - try { - _indicatorsRepo.loadFromPrefs(prefs); - } on Exception { - // ignore: unawaited_futures - showDialog( + final List> _stateRepos = + >[_indicatorsRepo, _drawingToolsRepo]; + + _stateRepos + .asMap() + .forEach((int index, AddOnsRepository element) { + try { + element.loadFromPrefs(prefs, widget.activeSymbol); + } on Exception { + // ignore: unawaited_futures + showDialog( context: context, builder: (BuildContext context) => AnimatedPopupDialog( - child: Center( - child: Text( - ChartLocalization.of(context).warnFailedLoadingIndicators, - ), - ), - )); - } + child: Center( + child: element is Repository + ? Text(context.localization.warnFailedLoadingIndicators) + : Text(context.localization.warnFailedLoadingDrawingTools), + ), + ), + ); + } + }); } - @override - Widget build(BuildContext context) => - ChangeNotifierProvider.value( + void showIndicatorsDialog() { + showDialog( + context: context, + builder: ( + BuildContext context, + ) => + ChangeNotifierProvider>.value( value: _indicatorsRepo, - builder: (BuildContext context, _) => Stack( - children: [ - Chart( - mainSeries: widget.mainSeries, - pipSize: widget.pipSize, - granularity: widget.granularity, - controller: widget.controller, - overlaySeries: [ - ...context - .watch() - .indicators - .where((IndicatorConfig indicatorConfig) => - indicatorConfig.isOverlay) - .map((IndicatorConfig indicatorConfig) => - indicatorConfig.getSeries( - IndicatorInput( - widget.mainSeries.input, - widget.granularity, - ), - )) - ], - bottomSeries: [ - ...context - .watch() - .indicators - .where((IndicatorConfig indicatorConfig) => - !indicatorConfig.isOverlay) - .map((IndicatorConfig indicatorConfig) => - indicatorConfig.getSeries( - IndicatorInput( - widget.mainSeries.input, - widget.granularity, - ), - )) - ], - markerSeries: widget.markerSeries, - theme: widget.theme, - onCrosshairAppeared: widget.onCrosshairAppeared, - onVisibleAreaChanged: widget.onVisibleAreaChanged, - isLive: widget.isLive, - dataFitEnabled: widget.dataFitEnabled, - opacity: widget.opacity, - annotations: widget.annotations, - chartAxisConfig: widget.chartAxisConfig, - ), - Align( - alignment: Alignment.topLeft, - child: IconButton( - icon: const Icon(Icons.architecture), - onPressed: () { - showDialog( - context: context, - builder: ( - BuildContext context, - ) => - ChangeNotifierProvider.value( - value: _indicatorsRepo, - child: IndicatorsDialog(), - ), - ); - }, + child: IndicatorsDialog(), + ), + ); + } + + void showDrawingToolsDialog() { + setState(() { + _drawingTools + ..init() + ..drawingToolsRepo = _drawingToolsRepo; + }); + showDialog( + context: context, + builder: ( + BuildContext context, + ) => + ChangeNotifierProvider>.value( + value: _drawingToolsRepo, + child: DrawingToolsDialog( + drawingTools: _drawingTools, + ), + ), + ); + } + + Widget _buildIndicatorsIcon() => Align( + alignment: Alignment.topLeft, + child: IconButton( + icon: const Icon(Icons.architecture), + onPressed: showIndicatorsDialog, + ), + ); + + Widget _buildDrawingToolsIcon() => Align( + alignment: const FractionalOffset(0.1, 0), + child: IconButton( + icon: const Icon(Icons.drive_file_rename_outline_outlined), + onPressed: showDrawingToolsDialog, + ), + ); + + @override + Widget build(BuildContext context) => MultiProvider( + providers: >>[ + ChangeNotifierProvider>.value( + value: widget.indicatorsRepo ?? _indicatorsRepo), + ChangeNotifierProvider>.value( + value: widget.drawingToolsRepo ?? _drawingToolsRepo), + ], + child: Builder( + builder: (BuildContext context) => Stack( + children: [ + Chart( + mainSeries: widget.mainSeries, + pipSize: widget.pipSize, + granularity: widget.granularity, + controller: widget.controller, + overlayConfigs: [ + ...context + .watch>() + .items + .where((IndicatorConfig config) => config.isOverlay) + ], + bottomConfigs: [ + ...context + .watch>() + .items + .where((IndicatorConfig config) => !config.isOverlay) + ], + drawingTools: widget.drawingTools ?? _drawingTools, + markerSeries: widget.markerSeries, + theme: widget.theme, + onCrosshairAppeared: widget.onCrosshairAppeared, + onCrosshairDisappeared: widget.onCrosshairDisappeared, + onCrosshairHover: widget.onCrosshairHover, + onVisibleAreaChanged: widget.onVisibleAreaChanged, + onQuoteAreaChanged: widget.onQuoteAreaChanged, + isLive: widget.isLive, + dataFitEnabled: widget.dataFitEnabled, + opacity: widget.opacity, + annotations: widget.annotations, + chartAxisConfig: widget.chartAxisConfig, + showCrosshair: widget.showCrosshair, + indicatorsRepo: widget.indicatorsRepo ?? _indicatorsRepo, + maxCurrentTickOffset: widget.maxCurrentTickOffset, + msPerPx: widget.msPerPx, + minIntervalWidth: widget.minIntervalWidth, + maxIntervalWidth: widget.maxIntervalWidth, + minElapsedTimeToFollow: widget.minElapsedTimeToFollow, + currentTickAnimationDuration: + widget.currentTickAnimationDuration, + quoteBoundsAnimationDuration: + widget.quoteBoundsAnimationDuration, + showCurrentTickBlinkAnimation: + widget.showCurrentTickBlinkAnimation, + verticalPaddingFraction: widget.verticalPaddingFraction, + bottomChartTitleMargin: widget.bottomChartTitleMargin, + showDataFitButton: widget.showDataFitButton, + showScrollToLastTickButton: widget.showScrollToLastTickButton, + loadingAnimationColor: widget.loadingAnimationColor, ), - ) - ], + if (widget.indicatorsRepo == null) _buildIndicatorsIcon(), + if (widget.drawingToolsRepo == null) _buildDrawingToolsIcon(), + ], + ), ), ); } diff --git a/lib/src/deriv_chart/drawing_tool_chart/drawing_tool_chart.dart b/lib/src/deriv_chart/drawing_tool_chart/drawing_tool_chart.dart new file mode 100644 index 000000000..cce6859db --- /dev/null +++ b/lib/src/deriv_chart/drawing_tool_chart/drawing_tool_chart.dart @@ -0,0 +1,121 @@ +import 'package:collection/collection.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_tool_widget.dart'; +import 'package:deriv_chart/src/deriv_chart/drawing_tool_chart/drawing_tools.dart'; +import 'package:deriv_chart/src/models/chart_config.dart'; +import 'package:deriv_chart/src/models/tick.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +/// A wigdet for encapsulating drawing tools related business logic +class DrawingToolChart extends StatefulWidget { + /// Creates chart that expands to available space. + const DrawingToolChart({ + required this.chartQuoteFromCanvasY, + required this.chartQuoteToCanvasY, + required this.drawingTools, + required this.series, + Key? key, + }) : super(key: key); + + /// Series of tick + final DataSeries series; + + /// Conversion function for converting quote from chart's canvas' Y position. + final double Function(double) chartQuoteFromCanvasY; + + /// Conversion function for converting quote to chart's canvas' Y position. + final double Function(double) chartQuoteToCanvasY; + + /// Contains drawing tools related data and methods + final DrawingTools drawingTools; + + @override + State createState() => _DrawingToolChartState(); +} + +class _DrawingToolChartState extends State { + late Repository repo; + + /// A method to get the list of drawing data from the repository + List getDrawingData() => repo.items + .map((DrawingToolConfig config) => config.drawingData) + .toList(); + + /// Sets drawing as selected and unselects the rest of drawings + /// if any of the drawing is not finished , it selects the unfinished drawing + void _setIsDrawingSelected(DrawingData drawing) { + setState(() { + drawing.isSelected = !drawing.isSelected; + + for (final DrawingData? data in getDrawingData()) { + if (data!.id != drawing.id) { + data.isSelected = false; + } + } + }); + } + + /// Removes specific drawing from the list of drawings + void removeUnfinishedDrawing() { + final List unfinishedDrawings = getDrawingData() + .where((DrawingData? data) => !data!.isDrawingFinished) + .toList(); + repo.removeAt(getDrawingData().indexOf(unfinishedDrawings.first)); + } + + @override + Widget build(BuildContext context) { + repo = context.watch>(); + + final List configs = repo.items.toList(); + + final List drawings = configs + .map((DrawingToolConfig config) => config.drawingData) + .toList(); + + return ClipRect( + child: RepaintBoundary( + child: Stack( + fit: StackFit.expand, + children: [ + if (drawings.isNotEmpty) + ...drawings.mapIndexed( + (int index, DrawingData? drawingData) => DrawingPainter( + key: ValueKey( + '''${drawingData?.id}_${context.watch().granularity}''', + ), + drawingData: drawingData, + quoteToCanvasY: widget.chartQuoteToCanvasY, + onMouseEnter: () => widget.drawingTools.onMouseEnter(index), + onMouseExit: () => widget.drawingTools.onMouseExit(index), + quoteFromCanvasY: widget.chartQuoteFromCanvasY, + isDrawingMoving: widget.drawingTools.isDrawingMoving, + onMoveDrawing: widget.drawingTools.onMoveDrawing, + setIsDrawingSelected: _setIsDrawingSelected, + selectedDrawingTool: widget.drawingTools.selectedDrawingTool, + series: widget.series, + ), + ), + if (widget.drawingTools.selectedDrawingTool != null) + DrawingToolWidget( + onAddDrawing: widget.drawingTools.onAddDrawing, + selectedDrawingTool: widget.drawingTools.selectedDrawingTool!, + quoteFromCanvasY: widget.chartQuoteFromCanvasY, + chartConfig: context.watch(), + clearDrawingToolSelection: + widget.drawingTools.clearDrawingToolSelection, + series: widget.series, + removeUnfinishedDrawing: removeUnfinishedDrawing, + shouldStopDrawing: widget.drawingTools.shouldStopDrawing, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/deriv_chart/drawing_tool_chart/drawing_tools.dart b/lib/src/deriv_chart/drawing_tool_chart/drawing_tools.dart new file mode 100644 index 000000000..f5bd84e7b --- /dev/null +++ b/lib/src/deriv_chart/drawing_tool_chart/drawing_tools.dart @@ -0,0 +1,146 @@ +import 'package:collection/collection.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/drawing_tools/drawing_data.dart'; +import 'package:deriv_chart/src/add_ons/drawing_tools_ui/drawing_tool_config.dart'; +import 'package:deriv_chart/src/add_ons/repository.dart'; +import '../chart/data_visualization/drawing_tools/data_model/edge_point.dart'; + +/// Callback to notify mouse enter over the addon. +typedef OnMouseEnterCallback = void Function(int index); + +/// Callback to notify mouse exit over the addon. +typedef OnMouseExitCallback = void Function(int index); + +/// This calss is used to keep all the methods and data related to drawing tools +/// Which need to be shared between the DerivChart and the DrawingToolsDialog +class DrawingTools { + /// Initializes + DrawingTools({ + this.onMouseEnterCallback, + this.onMouseExitCallback, + }); + + /// A flag to show when to stop drawing only for drawings which don't have + /// fixed number of points like continuous drawing + bool? shouldStopDrawing; + + /// Selected drawing tool. + DrawingToolConfig? selectedDrawingTool; + + /// Keep the reference to the drawing tools repository. + Repository? drawingToolsRepo; + + /// For tracking the drawing movement + bool isDrawingMoving = false; + + /// Callback to notify mouse enter over the addon. + OnMouseEnterCallback? onMouseEnterCallback; + + /// Callback to notify mouse exit over the addon. + OnMouseExitCallback? onMouseExitCallback; + + /// Remove unfinished drawings before openning the dialog. + /// For the scenario where the user adds part of a drawing + /// and then opens the dialog. + void init() { + if (selectedDrawingTool != null) { + shouldStopDrawing = true; + } + + /// Remove unfinshed drawings before openning the dialog. + final List unfinishedDrawings = getDrawingData() + .where((DrawingData? data) => !data!.isDrawingFinished) + .toList(); + if (unfinishedDrawings.isNotEmpty) { + drawingToolsRepo! + .removeAt(getDrawingData().indexOf(unfinishedDrawings.first)); + } + + clearDrawingToolSelection(); + } + + /// Callback for keeping the selected drawing tool data. + void onDrawingToolSelection(DrawingToolConfig config) { + shouldStopDrawing = false; + selectedDrawingTool = config; + } + + /// Callback to add the new drawing to the list of drawings + /// isInfiniteDrawing used for drawings which don't have fixed number of + /// points + void onAddDrawing( + String drawingId, + List drawingParts, { + bool isDrawingFinished = false, + bool isInfiniteDrawing = false, + List? edgePoints, + }) { + final DrawingData? existingDrawing = getDrawingData().firstWhereOrNull( + (DrawingData? drawing) => drawing!.id == drawingId, + ); + + if (existingDrawing == null) { + selectedDrawingTool = selectedDrawingTool!.copyWith( + configId: drawingId, + edgePoints: edgePoints, + drawingData: DrawingData( + id: drawingId, + drawingParts: drawingParts, + isDrawingFinished: isDrawingFinished, + isSelected: true, + ), + ); + } + + if (drawingToolsRepo!.items + .where((DrawingToolConfig element) => element.configId == drawingId) + .isEmpty) { + drawingToolsRepo!.add(selectedDrawingTool!); + } + + /// Clear the selected drawing tool if the drawing is finished + if (isDrawingFinished && + ((isInfiniteDrawing && shouldStopDrawing!) || !isInfiniteDrawing)) { + clearDrawingToolSelection(); + } + + /// Remove any unfinished drawing before adding a new one. + final List unfinishedDrawings = getDrawingData() + .where((DrawingData? data) => + data!.id != drawingId && !data.isDrawingFinished) + .toList(); + + if (unfinishedDrawings.isNotEmpty) { + drawingToolsRepo! + .removeAt(getDrawingData().indexOf(unfinishedDrawings.first)); + } + } + + /// Callback to inform parent about drawing tool removal. + void clearDrawingToolSelection() { + selectedDrawingTool = null; + } + + /// Callback for tracking the drawing movement. + // ignore: use_setters_to_change_properties + void onMoveDrawing({bool isDrawingMoved = false}) { + isDrawingMoving = isDrawingMoved; + } + + /// Called when mouse enters over a drawing tool. + void onMouseEnter(int index) { + onMouseEnterCallback?.call(index); + } + + /// Called when mouse leaves over a drawing tool. + void onMouseExit(int index) { + onMouseExitCallback?.call(index); + } + + /// A method to get the list of drawing data from the repository + List getDrawingData() => drawingToolsRepo != null + ? drawingToolsRepo!.items + .map((DrawingToolConfig config) => config.drawingData) + .toList() + : []; +} diff --git a/lib/src/misc/callbacks.dart b/lib/src/misc/callbacks.dart index 4cbbaedb3..86f7f94b0 100644 --- a/lib/src/misc/callbacks.dart +++ b/lib/src/misc/callbacks.dart @@ -1,5 +1,52 @@ +import 'package:deriv_chart/src/add_ons/add_on_config.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:flutter/gestures.dart'; + /// Called when chart is scrolled or zoomed. /// /// [leftEpoch] is an epoch value of the chart's left edge. /// [rightEpoch] is an epoch value of the chart's right edge. typedef VisibleAreaChangedCallback = Function(int leftEpoch, int rightEpoch); + +/// Called when the quotes in y-axis is changed +/// +/// [topQuote] is an quote value of the chart's top edge. +/// [bottomQuote] is an quote value of the chart's bottom edge. +typedef VisibleQuoteAreaChangedCallback = Function( + double topQuote, double bottomQuote); + +/// Called when the crosshair is moved +/// +/// [globalPosition] of the pointer. +/// [localPosition] of the pointer. +/// [epochToX] is a function to convert epoch value to canvas X. +/// [quoteToY] is a function to convert value(quote) value to canvas Y. +/// [epochFromX] is a function to convert canvas X to epoch value. +/// [quoteFromY] is a function to convert canvas Y to value(quote). +typedef OnCrosshairHover = void Function( + Offset globalPosition, + Offset localPosition, + EpochToX epochToX, + QuoteToY quoteToY, + EpochFromX epochFromX, + QuoteFromY quoteFromY, +); + +/// Called when the crosshair is moved +/// +/// [globalPosition] of the pointer. +/// [localPosition] of the pointer. +/// [epochToX] is a function to convert epoch value to canvas X. +/// [quoteToY] is a function to convert value(quote) value to canvas Y. +/// [epochFromX] is a function to convert canvas X to epoch value. +/// [quoteFromY] is a function to convert canvas Y to value(quote). +/// [config] is the config of the Indicator if it the hover is in BottomChart. +typedef OnCrosshairHoverCallback = void Function( + Offset globalPosition, + Offset localPosition, + EpochToX epochToX, + QuoteToY quoteToY, + EpochFromX epochFromX, + QuoteFromY quoteFromY, + AddOnConfig? config, +); diff --git a/lib/src/misc/chart_controller.dart b/lib/src/misc/chart_controller.dart index 3e483a702..2478aede3 100644 --- a/lib/src/misc/chart_controller.dart +++ b/lib/src/misc/chart_controller.dart @@ -1,13 +1,81 @@ +import 'package:deriv_chart/deriv_chart.dart'; + /// ScrollToLastTick callback. typedef OnScrollToLastTick = Function({required bool animate}); +/// Scale callback; +typedef OnScale = double? Function(double); + +/// Scroll callback; +typedef OnScroll = Function(double); + +/// To get X position +typedef GetXFromEpoch = double? Function(int); + +/// To get Y position +typedef GetYFromQuote = double? Function(double); + +/// To get epoch +typedef GetEpochFromX = int? Function(double); + +/// To get quote +typedef GetQuoteFromY = double? Function(double); + +/// To get overlay/bottom series +typedef GetSeriesList = List? Function(); + +/// To get overlay/bottom configs +typedef GetConfigsList = List? Function(); + +/// Toggles data fit mode +typedef ToggleDataFitMode = Function({required bool enableDataFit}); + +/// To get msPerPx +typedef GetMsPerPx = double? Function(); + /// Chart widget's controller. class ChartController { /// Called to scroll the current display chart to last tick. OnScrollToLastTick? onScrollToLastTick; + /// Called to scale the chart + OnScale? onScale; + + /// Called to scroll the chart + OnScroll? onScroll; + + /// Called to toggle data fit mode + ToggleDataFitMode? toggleDataFitMode; + + /// Called to get X position from epoch + GetXFromEpoch? getXFromEpoch; + + /// Called to get Y position from quote + GetYFromQuote? getYFromQuote; + + /// Called to get epoch from x position + GetEpochFromX? getEpochFromX; + + /// Called to get quote from y position + GetQuoteFromY? getQuoteFromY; + + /// Called to get overlay and bottom series + GetSeriesList? getSeriesList; + + /// Called to get overlay and bottom configs + GetConfigsList? getConfigsList; + + /// Called to get msPerPx + GetMsPerPx? getMsPerPx; + /// Scroll chart visible area to the newest data. void scrollToLastTick({bool animate = false}) => onScrollToLastTick?.call(animate: animate); + + /// Scales the chart. + double? scale(double scale) => onScale?.call(scale); + + /// Scroll chart visible area. + void scroll(double pxShift) => onScroll?.call(pxShift); } diff --git a/lib/src/misc/debounce.dart b/lib/src/misc/debounce.dart new file mode 100644 index 000000000..17eb3406b --- /dev/null +++ b/lib/src/misc/debounce.dart @@ -0,0 +1,39 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; + +/// A utility class for implementing debouncing of function calls. +class Debounce { + /// Creates a [Debounce] instance. + /// + /// The [delay] parameter determines the duration of the debounce window. + Debounce({ + this.delay = const Duration(milliseconds: 1000), + }); + + /// The duration of the debounce. + /// Default debounce duration is 1000 milliseconds (1 second) + final Duration delay; + + /// The function to be executed after the debounce window. + VoidCallback? action; + + /// Timer to manage the debounce window and to cancel. + Timer? _timer; + + /// Runs the provided [action] function after the debounce window. + /// + /// If another call to [run] is made before the debounce window completes, + /// the previous timer is canceled and a new timer is started. + void run(VoidCallback action) { + /// Cancel the previous timer if it exists + if (_timer != null) { + _timer!.cancel(); + } + + /// Start a new timer with the specified [delay] and [action] + _timer = Timer( + delay, + action, + ); + } +} diff --git a/lib/src/models/chart_config.dart b/lib/src/models/chart_config.dart index b4f2d2b60..da0c89fe4 100644 --- a/lib/src/models/chart_config.dart +++ b/lib/src/models/chart_config.dart @@ -1,8 +1,12 @@ import 'package:deriv_chart/deriv_chart.dart'; import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'chart_config.g.dart'; /// Chart's general configuration. @immutable +@JsonSerializable() class ChartConfig { /// Initializes chart's general configuration. const ChartConfig({ @@ -11,6 +15,13 @@ class ChartConfig { this.pipSize = 4, }); + /// Initializes from JSON. + factory ChartConfig.fromJson(Map json) => + _$ChartConfigFromJson(json); + + /// Serialization to JSON. Serves as value in key-value storage. + Map toJson() => _$ChartConfigToJson(this); + /// PipSize, number of decimal digits when showing prices on the chart. final int pipSize; diff --git a/lib/src/models/chart_config.g.dart b/lib/src/models/chart_config.g.dart new file mode 100644 index 000000000..1c59a6904 --- /dev/null +++ b/lib/src/models/chart_config.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chart_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ChartConfig _$ChartConfigFromJson(Map json) => ChartConfig( + granularity: json['granularity'] as int, + pipSize: json['pipSize'] as int? ?? 4, + ); + +Map _$ChartConfigToJson(ChartConfig instance) => + { + 'pipSize': instance.pipSize, + 'granularity': instance.granularity, + }; diff --git a/lib/src/models/chart_style.dart b/lib/src/models/chart_style.dart index f0ebf0d58..e6269b6aa 100644 --- a/lib/src/models/chart_style.dart +++ b/lib/src/models/chart_style.dart @@ -5,4 +5,10 @@ enum ChartStyle { /// Used to show the chart with candles. candles, + + /// Used to show the hollow candle chart. + hollow, + + /// Used to show the ohlc chart. + ohlc, } diff --git a/lib/src/theme/chart_default_theme.dart b/lib/src/theme/chart_default_theme.dart index 41a5d7893..19bfafcd2 100644 --- a/lib/src/theme/chart_default_theme.dart +++ b/lib/src/theme/chart_default_theme.dart @@ -95,7 +95,7 @@ abstract class ChartDefaultTheme implements ChartTheme { CandleStyle get candleStyle => CandleStyle( positiveColor: accentGreenColor, negativeColor: accentRedColor, - lineColor: base04Color, + neutralColor: base04Color, ); @override diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 66a4ec7f3..b987db7a8 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -28,14 +28,14 @@ class DarkThemeColors { /// These colors suits the light theme of Deriv. // TODO(Ramin): replace values based on light theme when available class LightThemeColors { - static const Color base01 = Color(0xFFFFFFFF); - static const Color base02 = Color(0xFFEAECED); - static const Color base03 = Color(0xFFC2C2C2); - static const Color base04 = Color(0xFF6E6E6E); - static const Color base05 = Color(0xFF3E3E3E); - static const Color base06 = Color(0xFF323738); - static const Color base07 = Color(0xFF151717); - static const Color base08 = Color(0xFF0E0E0E); + static const Color base01 = Color(0xFF0E0E0E); + static const Color base02 = Color(0xFF151717); + static const Color base03 = Color(0xFF323738); + static const Color base04 = Color(0xFF3E3E3E); + static const Color base05 = Color(0xFF6E6E6E); + static const Color base06 = Color(0xFFC2C2C2); + static const Color base07 = Color(0xFFEAECED); + static const Color base08 = Color(0xFFFFFFFF); static const Color accentGreen = Color(0xFF00A79E); static const Color accentYellow = Color(0xFFFFAD3A); static const Color accentRed = Color(0xFFCC2E3D); diff --git a/lib/src/theme/painting_styles/bar_style.g.dart b/lib/src/theme/painting_styles/bar_style.g.dart index b314e12b1..fba93a9ae 100644 --- a/lib/src/theme/painting_styles/bar_style.g.dart +++ b/lib/src/theme/painting_styles/bar_style.g.dart @@ -6,14 +6,14 @@ part of 'bar_style.dart'; // JsonSerializableGenerator // ************************************************************************** -BarStyle _$BarStyleFromJson(Map json) { - return BarStyle( - positiveColor: - const ColorConverter().fromJson(json['positiveColor'] as int), - negativeColor: - const ColorConverter().fromJson(json['negativeColor'] as int), - ); -} +BarStyle _$BarStyleFromJson(Map json) => BarStyle( + positiveColor: json['positiveColor'] == null + ? const Color(0xFF4CAF50) + : const ColorConverter().fromJson(json['positiveColor'] as int), + negativeColor: json['negativeColor'] == null + ? const Color(0xFFCC2E3D) + : const ColorConverter().fromJson(json['negativeColor'] as int), + ); Map _$BarStyleToJson(BarStyle instance) => { 'positiveColor': const ColorConverter().toJson(instance.positiveColor), diff --git a/lib/src/theme/painting_styles/candle_style.dart b/lib/src/theme/painting_styles/candle_style.dart index 6fbd4e721..dbb34fa15 100644 --- a/lib/src/theme/painting_styles/candle_style.dart +++ b/lib/src/theme/painting_styles/candle_style.dart @@ -9,7 +9,7 @@ class CandleStyle extends DataSeriesStyle with EquatableMixin { const CandleStyle({ this.positiveColor = const Color(0xFF00A79E), this.negativeColor = const Color(0xFFCC2E3D), - this.lineColor = const Color(0xFF6E6E6E), + this.neutralColor = const Color(0xFF6E6E6E), }); /// Color of candles in which the price moved HIGHER during their period. @@ -18,13 +18,15 @@ class CandleStyle extends DataSeriesStyle with EquatableMixin { /// Color of candles in which the price moved LOWER during their period. final Color negativeColor; - /// The vertical line inside candle which represents high/low. - final Color lineColor; + /// Neutral color for candles in which the price remains same + /// or The vertical line inside candle which represents high/low. + final Color neutralColor; @override String toString() => - '${super.toString()}$positiveColor, $negativeColor, $lineColor'; + '${super.toString()}$positiveColor, $negativeColor, $neutralColor'; @override - List get props => [positiveColor, negativeColor, lineColor]; + List get props => + [positiveColor, negativeColor, neutralColor]; } diff --git a/lib/src/theme/painting_styles/line_style.g.dart b/lib/src/theme/painting_styles/line_style.g.dart index 90ff5e8c0..11951739a 100644 --- a/lib/src/theme/painting_styles/line_style.g.dart +++ b/lib/src/theme/painting_styles/line_style.g.dart @@ -6,13 +6,13 @@ part of 'line_style.dart'; // JsonSerializableGenerator // ************************************************************************** -LineStyle _$LineStyleFromJson(Map json) { - return LineStyle( - color: const ColorConverter().fromJson(json['color'] as int), - thickness: (json['thickness'] as num).toDouble(), - hasArea: json['hasArea'] as bool, - ); -} +LineStyle _$LineStyleFromJson(Map json) => LineStyle( + color: json['color'] == null + ? const Color(0xFF85ACB0) + : const ColorConverter().fromJson(json['color'] as int), + thickness: (json['thickness'] as num?)?.toDouble() ?? 1, + hasArea: json['hasArea'] as bool? ?? false, + ); Map _$LineStyleToJson(LineStyle instance) => { 'color': const ColorConverter().toJson(instance.color), diff --git a/lib/src/theme/painting_styles/scatter_style.g.dart b/lib/src/theme/painting_styles/scatter_style.g.dart index 7875fdb7a..454e88b29 100644 --- a/lib/src/theme/painting_styles/scatter_style.g.dart +++ b/lib/src/theme/painting_styles/scatter_style.g.dart @@ -6,12 +6,12 @@ part of 'scatter_style.dart'; // JsonSerializableGenerator // ************************************************************************** -ScatterStyle _$ScatterStyleFromJson(Map json) { - return ScatterStyle( - color: const ColorConverter().fromJson(json['color'] as int), - radius: (json['radius'] as num).toDouble(), - ); -} +ScatterStyle _$ScatterStyleFromJson(Map json) => ScatterStyle( + color: json['color'] == null + ? const Color(0xFF85ACB0) + : const ColorConverter().fromJson(json['color'] as int), + radius: (json['radius'] as num?)?.toDouble() ?? 1.5, + ); Map _$ScatterStyleToJson(ScatterStyle instance) => { diff --git a/lib/src/widgets/bottom_indicator_title.dart b/lib/src/widgets/bottom_indicator_title.dart new file mode 100644 index 000000000..223751f9d --- /dev/null +++ b/lib/src/widgets/bottom_indicator_title.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +/// Widget that shows the bottom indicator title. +class BottomIndicatorTitle extends StatelessWidget { + /// Creates a widget that shows the title of the indicator. + const BottomIndicatorTitle(this.title, this.textStyle); + + /// The title of the indicator. + final String title; + + /// The style of the text. + final TextStyle textStyle; + + @override + Widget build(BuildContext context) => Text( + title, + style: textStyle, + ); +} diff --git a/pubspec.yaml b/pubspec.yaml index 45e53f113..668986287 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_chart description: Chart for multiplier flutter app. -version: 0.0.1 +version: 0.1.0 publish_to: none environment: diff --git a/test/deriv_chart/chart/data_visualization/chart_series/indicator_series/abstract_single_indicator_series_test.dart b/test/deriv_chart/chart/data_visualization/chart_series/indicator_series/abstract_single_indicator_series_test.dart index d8aad0a69..5f60a3f20 100644 --- a/test/deriv_chart/chart/data_visualization/chart_series/indicator_series/abstract_single_indicator_series_test.dart +++ b/test/deriv_chart/chart/data_visualization/chart_series/indicator_series/abstract_single_indicator_series_test.dart @@ -1,11 +1,7 @@ import 'package:deriv_chart/deriv_chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/abstract_single_indicator_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/indicators_series/models/indicator_options.dart'; -import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series.dart'; -import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; -import 'package:deriv_chart/src/models/indicator_input.dart'; import 'package:deriv_technical_analysis/deriv_technical_analysis.dart'; -import 'package:deriv_technical_analysis/src/indicators/cached_indicator.dart'; import 'package:flutter_test/flutter_test.dart'; class MockOptions extends IndicatorOptions { diff --git a/test/deriv_chart/chart/helpers/functions/conversion_test.dart b/test/deriv_chart/chart/helpers/functions/conversion_test.dart index 2b310b3d5..18a388067 100644 --- a/test/deriv_chart/chart/helpers/functions/conversion_test.dart +++ b/test/deriv_chart/chart/helpers/functions/conversion_test.dart @@ -231,4 +231,68 @@ void main() { ); }); }); + + group('quoteFromCanvasY should return', () { + test('[topBoundQuote] when [y == topPadding]', () { + expect( + quoteFromCanvasY( + y: 0, + topBoundQuote: 1234.2345, + bottomBoundQuote: 123.439, + canvasHeight: 10033, + topPadding: 0, + bottomPadding: 133, + ), + equals(1234.2345), + ); + expect( + quoteFromCanvasY( + y: 1234.34, + topBoundQuote: 1234.2345, + bottomBoundQuote: 123.439, + canvasHeight: 10033, + topPadding: 1234.34, + bottomPadding: 133, + ), + equals(1234.2345), + ); + }); + test('[bottomBoundQuote] when [y == canvasHeight]', () { + expect( + quoteFromCanvasY( + y: 1024, + topBoundQuote: 102.2385, + bottomBoundQuote: 89.2345, + canvasHeight: 1024, + topPadding: 123, + bottomPadding: 0, + ), + equals(89.2345), + ); + expect( + quoteFromCanvasY( + y: 1024, + topBoundQuote: 102.2385, + bottomBoundQuote: 89.2345, + canvasHeight: 1024, + topPadding: 123, + bottomPadding: 0, + ), + equals(89.2345), + ); + }); + test('middle of drawing range', () { + expect( + quoteFromCanvasY( + y: 512, + topBoundQuote: 100, + bottomBoundQuote: 200, + canvasHeight: 1024, + topPadding: 12, + bottomPadding: 12, + ), + equals(150), + ); + }); + }); }