kandi background
kandi background
Explore Kits
kandi background
Explore Kits
kandi background
Explore Kits
kandi background
Explore Kits
Explore all Canvas open source software, libraries, packages, source code, cloud functions and APIs.

Popular New Releases in Canvas

Version 5.2.1

v2.9.0

v4.0.4

4.0.11

fabric.js

Version 5.2.1

node-canvas

v2.9.0

signature_pad

v4.0.4

F2

4.0.11

topology

Popular Libraries in Canvas

Trending New libraries in Canvas

Top Authors in Canvas

1

10 Libraries

301

2

10 Libraries

181

3

8 Libraries

167

4

7 Libraries

60

5

7 Libraries

277

6

6 Libraries

204

7

6 Libraries

148

8

6 Libraries

109

9

6 Libraries

4922

10

5 Libraries

18

1

10 Libraries

301

2

10 Libraries

181

3

8 Libraries

167

4

7 Libraries

60

5

7 Libraries

277

6

6 Libraries

204

7

6 Libraries

148

8

6 Libraries

109

9

6 Libraries

4922

10

5 Libraries

18

Trending Kits in Canvas

No Trending Kits are available at this moment for Canvas

Trending Discussions on Canvas

    Animate needle transition
    How To Scale The Contents Of A UIView To Fit A Destination Rectangle Whilst Maintaining The Aspect Ratio?
    Prevent y-axis labels from being cut off
    Javascript: frame precise video stop
    Opening PDFs in WebView2 based on selection in CheckBoxColumn
    Is it possible to manually update the value of a Behaviour? (Functional Reactive Programming, Threepenny)
    Problem resizing plot on tkinter figure canvas
    Efficient code for custom color formatting in tkinter python
    Android: Iterative queue-based flood fill algorithm 'expandToNeighborsWithMap()' function is unusually slow
    create a circle object and push them in array

QUESTION

Animate needle transition

Asked 2022-Mar-21 at 22:09

When I read data from GPS sensor, it comes with a slight delay. You are not getting values like 0,1 0,2 0,3 0,4 0,5 etc, but they are coming like 1 then suddenly 5 or 9 or 12. In this case needle is jumping back and forth. Anybody have an idea how to make needle moving smoothly? I guess some kind of delay is needed?

Something like, taken from another control:

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i < progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12

However I am a bit confused how to implement that.

Here is code for drawing a needle on canvas:

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i < progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12    private void OnDrawNeedle()
13    {
14        using (var needlePath = new SKPath())
15        {
16            //first set up needle pointing towards 0 degrees (or 6 o'clock)
17            var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
18            var needleOffset = ScaleToSize(NeedleOffset);
19            var needleStart = _center.Y - needleOffset;
20            var needleLength = ScaleToSize(NeedleLength);
21
22            needlePath.MoveTo(_center.X - widthOffset, needleStart);
23            needlePath.LineTo(_center.X + widthOffset, needleStart);
24            needlePath.LineTo(_center.X, needleStart + needleLength);
25            needlePath.LineTo(_center.X - widthOffset, needleStart);
26            needlePath.Close();
27
28            //then calculate needle position in degrees
29            var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
30
31            //finally rotate needle to actual value
32            needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
33
34            using (var needlePaint = new SKPaint())
35            {
36                needlePaint.IsAntialias = true;
37                needlePaint.Color = NeedleColor.ToSKColor();
38                needlePaint.Style = SKPaintStyle.Fill;
39                _canvas.DrawPath(needlePath, needlePaint);
40            }
41        }
42    }
43

EDIT:

Still having hard times to understand the process.

Let's say I don't want to apply this filter for control but have it in ViewModel to filter value. I have a Class from where I am getting data, for example GPSTracker. GPSTracker provides speed value, then I am subscribing to EventListener in my HomeViewModel and want to filter incoming value.

Based on Adams answer:

ANSWER

Answered 2022-Mar-21 at 22:09

Coming from a controls background, to mimic behavior of an analog device, you could use an exponential (aka low-pass) filter.

There are two types of low-pass filters you can use, depending on what type of behavior you want to see: a first-order or second-order filter. To put it in a nutshell, if your reading was steady at 0 then suddenly changed to 10 and held steady at 10 (a step change), the first order would slowly go to 10, never passing it, then remain at 10 whereas the second order would speed up its progress towards 10, pass it, then oscillate in towards 10.

The function for an exponential filter is simple:

copy icondownload icon

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i < progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12    private void OnDrawNeedle()
13    {
14        using (var needlePath = new SKPath())
15        {
16            //first set up needle pointing towards 0 degrees (or 6 o'clock)
17            var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
18            var needleOffset = ScaleToSize(NeedleOffset);
19            var needleStart = _center.Y - needleOffset;
20            var needleLength = ScaleToSize(NeedleLength);
21
22            needlePath.MoveTo(_center.X - widthOffset, needleStart);
23            needlePath.LineTo(_center.X + widthOffset, needleStart);
24            needlePath.LineTo(_center.X, needleStart + needleLength);
25            needlePath.LineTo(_center.X - widthOffset, needleStart);
26            needlePath.Close();
27
28            //then calculate needle position in degrees
29            var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
30
31            //finally rotate needle to actual value
32            needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
33
34            using (var needlePaint = new SKPaint())
35            {
36                needlePaint.IsAntialias = true;
37                needlePaint.Color = NeedleColor.ToSKColor();
38                needlePaint.Style = SKPaintStyle.Fill;
39                _canvas.DrawPath(needlePath, needlePaint);
40            }
41        }
42    }
43public void Exp_Filt(ref double filtered_value, double source_value, double time_passed, double time_constant)
44{
45    if (time_passed > 0.0)
46    {
47        if (time_constant > 0.0)
48        {
49            source_value += (filtered_value - source_value) * Math.Exp(-time_passed / time_constant);
50        }
51        filtered_value = source_value;
52    }
53}
54

filtered_value is the filtered version of the source source_value, time_passed is how much time passed from the last time this function was called to filter filtered_value, and time_constant is the time constant of the filter (FYI, reacting to a step change, filtered_value will get 63% of the way towards source_value after time_constant time has passed and 99% when 5x have passed). The units of filtered_value will be the same as source_value. The units of time_passed and time_constant need to be the same, whether this be seconds, microseconds, or jiffy. Additionally, time_passed should be significantly smaller than time_constant at all times, otherwise the filter behavior will become non-deterministic. There are multiple ways to get the time_passed, such as Stopwatch, see How can I calculate how much time have been passed?

Before using the filter function, you would need to initialize the filtered_value and whatever you use to get time_passed. For this example, I will use stopwatch.

copy icondownload icon

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i < progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12    private void OnDrawNeedle()
13    {
14        using (var needlePath = new SKPath())
15        {
16            //first set up needle pointing towards 0 degrees (or 6 o'clock)
17            var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
18            var needleOffset = ScaleToSize(NeedleOffset);
19            var needleStart = _center.Y - needleOffset;
20            var needleLength = ScaleToSize(NeedleLength);
21
22            needlePath.MoveTo(_center.X - widthOffset, needleStart);
23            needlePath.LineTo(_center.X + widthOffset, needleStart);
24            needlePath.LineTo(_center.X, needleStart + needleLength);
25            needlePath.LineTo(_center.X - widthOffset, needleStart);
26            needlePath.Close();
27
28            //then calculate needle position in degrees
29            var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
30
31            //finally rotate needle to actual value
32            needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
33
34            using (var needlePaint = new SKPaint())
35            {
36                needlePaint.IsAntialias = true;
37                needlePaint.Color = NeedleColor.ToSKColor();
38                needlePaint.Style = SKPaintStyle.Fill;
39                _canvas.DrawPath(needlePath, needlePaint);
40            }
41        }
42    }
43public void Exp_Filt(ref double filtered_value, double source_value, double time_passed, double time_constant)
44{
45    if (time_passed > 0.0)
46    {
47        if (time_constant > 0.0)
48        {
49            source_value += (filtered_value - source_value) * Math.Exp(-time_passed / time_constant);
50        }
51        filtered_value = source_value;
52    }
53}
54var stopwatch = new System.Diagnostics.Stopwatch();
55double filtered_value, filtered_dot_value;
56...
57filtered_value = source_value;
58filtered_dot_value = 0.0;
59stopwatch.Start();
60

To use this function for a first-order filter, you would loop the following using a timer or something similar

copy icondownload icon

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i < progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12    private void OnDrawNeedle()
13    {
14        using (var needlePath = new SKPath())
15        {
16            //first set up needle pointing towards 0 degrees (or 6 o'clock)
17            var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
18            var needleOffset = ScaleToSize(NeedleOffset);
19            var needleStart = _center.Y - needleOffset;
20            var needleLength = ScaleToSize(NeedleLength);
21
22            needlePath.MoveTo(_center.X - widthOffset, needleStart);
23            needlePath.LineTo(_center.X + widthOffset, needleStart);
24            needlePath.LineTo(_center.X, needleStart + needleLength);
25            needlePath.LineTo(_center.X - widthOffset, needleStart);
26            needlePath.Close();
27
28            //then calculate needle position in degrees
29            var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
30
31            //finally rotate needle to actual value
32            needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
33
34            using (var needlePaint = new SKPaint())
35            {
36                needlePaint.IsAntialias = true;
37                needlePaint.Color = NeedleColor.ToSKColor();
38                needlePaint.Style = SKPaintStyle.Fill;
39                _canvas.DrawPath(needlePath, needlePaint);
40            }
41        }
42    }
43public void Exp_Filt(ref double filtered_value, double source_value, double time_passed, double time_constant)
44{
45    if (time_passed > 0.0)
46    {
47        if (time_constant > 0.0)
48        {
49            source_value += (filtered_value - source_value) * Math.Exp(-time_passed / time_constant);
50        }
51        filtered_value = source_value;
52    }
53}
54var stopwatch = new System.Diagnostics.Stopwatch();
55double filtered_value, filtered_dot_value;
56...
57filtered_value = source_value;
58filtered_dot_value = 0.0;
59stopwatch.Start();
60double time_passed = stopwatch.ElapsedMilliseconds;
61stopwatch.Restart();
62Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
63

To use this function for a second-order filter, you would loop the following using a timer or something similar

copy icondownload icon

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i < progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12    private void OnDrawNeedle()
13    {
14        using (var needlePath = new SKPath())
15        {
16            //first set up needle pointing towards 0 degrees (or 6 o'clock)
17            var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
18            var needleOffset = ScaleToSize(NeedleOffset);
19            var needleStart = _center.Y - needleOffset;
20            var needleLength = ScaleToSize(NeedleLength);
21
22            needlePath.MoveTo(_center.X - widthOffset, needleStart);
23            needlePath.LineTo(_center.X + widthOffset, needleStart);
24            needlePath.LineTo(_center.X, needleStart + needleLength);
25            needlePath.LineTo(_center.X - widthOffset, needleStart);
26            needlePath.Close();
27
28            //then calculate needle position in degrees
29            var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
30
31            //finally rotate needle to actual value
32            needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
33
34            using (var needlePaint = new SKPaint())
35            {
36                needlePaint.IsAntialias = true;
37                needlePaint.Color = NeedleColor.ToSKColor();
38                needlePaint.Style = SKPaintStyle.Fill;
39                _canvas.DrawPath(needlePath, needlePaint);
40            }
41        }
42    }
43public void Exp_Filt(ref double filtered_value, double source_value, double time_passed, double time_constant)
44{
45    if (time_passed > 0.0)
46    {
47        if (time_constant > 0.0)
48        {
49            source_value += (filtered_value - source_value) * Math.Exp(-time_passed / time_constant);
50        }
51        filtered_value = source_value;
52    }
53}
54var stopwatch = new System.Diagnostics.Stopwatch();
55double filtered_value, filtered_dot_value;
56...
57filtered_value = source_value;
58filtered_dot_value = 0.0;
59stopwatch.Start();
60double time_passed = stopwatch.ElapsedMilliseconds;
61stopwatch.Restart();
62Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
63double time_passed = stopwatch.ElapsedMilliseconds;
64stopwatch.Restart();
65if (time_passed > 0.0)
66{
67    double last_value = filtered_value;
68    filtered_value += filtered_dot_value * time_passed;
69    Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
70    Exp_Filt(ref filtered_dot_value, (filtered_value - last_value) / time_passed, time_passed, dot_time_constant);
71}
72

The second-order filter works by taking the first derivative of the first-order filtered value into account. Also, I would recommend making time_constant < dot_time_constant - to start, I would set dot_time_constant = 2 * time_constant

Personally, I would call this filter in a background thread controlled by a threading timer and have time_passed a constant equal to the timer's period, but I will leave the implementation specifics up to you.

EDIT:

Below is example class to create first and second order filters. To operate the filter, I use a threading timer set to process every 100 milliseconds. Being that this timer is rather consistent, making time_passed constant, I optimized the filter equation by pre-calculating Math.Exp(-time_passed / time_constant) and not dividing/multiplying 'dot' term by time_passed.

For first-order filter, use var filter = new ExpFilter(initial_value, time_constant). For second-order filter, use var filter = new ExpFilter(initial_value, time_constant, dot_time_constant). Then, to read latest filtered value, call double value = filter.Value. To set value to filter towards, call filter.Value = value.

copy icondownload icon

1    async void animateProgress(int progress)
2    {
3        sweepAngle = 1;
4
5        // Looping at data interval of 5
6        for (int i = 0; i &lt; progress; i=i+5)
7        {
8            sweepAngle = i;
9            await Task.Delay(3);
10        }
11    }
12    private void OnDrawNeedle()
13    {
14        using (var needlePath = new SKPath())
15        {
16            //first set up needle pointing towards 0 degrees (or 6 o'clock)
17            var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
18            var needleOffset = ScaleToSize(NeedleOffset);
19            var needleStart = _center.Y - needleOffset;
20            var needleLength = ScaleToSize(NeedleLength);
21
22            needlePath.MoveTo(_center.X - widthOffset, needleStart);
23            needlePath.LineTo(_center.X + widthOffset, needleStart);
24            needlePath.LineTo(_center.X, needleStart + needleLength);
25            needlePath.LineTo(_center.X - widthOffset, needleStart);
26            needlePath.Close();
27
28            //then calculate needle position in degrees
29            var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
30
31            //finally rotate needle to actual value
32            needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
33
34            using (var needlePaint = new SKPaint())
35            {
36                needlePaint.IsAntialias = true;
37                needlePaint.Color = NeedleColor.ToSKColor();
38                needlePaint.Style = SKPaintStyle.Fill;
39                _canvas.DrawPath(needlePath, needlePaint);
40            }
41        }
42    }
43public void Exp_Filt(ref double filtered_value, double source_value, double time_passed, double time_constant)
44{
45    if (time_passed &gt; 0.0)
46    {
47        if (time_constant &gt; 0.0)
48        {
49            source_value += (filtered_value - source_value) * Math.Exp(-time_passed / time_constant);
50        }
51        filtered_value = source_value;
52    }
53}
54var stopwatch = new System.Diagnostics.Stopwatch();
55double filtered_value, filtered_dot_value;
56...
57filtered_value = source_value;
58filtered_dot_value = 0.0;
59stopwatch.Start();
60double time_passed = stopwatch.ElapsedMilliseconds;
61stopwatch.Restart();
62Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
63double time_passed = stopwatch.ElapsedMilliseconds;
64stopwatch.Restart();
65if (time_passed &gt; 0.0)
66{
67    double last_value = filtered_value;
68    filtered_value += filtered_dot_value * time_passed;
69    Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
70    Exp_Filt(ref filtered_dot_value, (filtered_value - last_value) / time_passed, time_passed, dot_time_constant);
71}
72    public class ExpFilter : IDisposable
73    {
74        private double _input, _output, _dot;
75        private readonly double _tc, _tc_dot;
76        private System.Threading.Timer _timer;
77
78        /// &lt;summary&gt;
79        /// Initializes first-order filter
80        /// &lt;/summary&gt;
81        /// &lt;param name=&quot;value&quot;&gt;initial value of filter&lt;/param&gt;
82        /// &lt;param name=&quot;time_constant&quot;&gt;time constant of filter, in seconds&lt;/param&gt;
83        /// &lt;exception cref=&quot;ArgumentOutOfRangeException&quot;&gt;&lt;paramref name=&quot;time_constant&quot;/&gt; must be positive&lt;/exception&gt;
84        public ExpFilter(double value, double time_constant)
85        {
86            // time constant must be positive
87            if (time_constant &lt;= 0.0) throw new ArgumentOutOfRangeException(nameof(time_constant));
88
89            // initialize filter
90            _output = _input = value;
91            _dot = 0.0;
92
93            // calculate gain from time constant
94            _tc = CalcTC(time_constant);
95
96            // disable second-order
97            _tc_dot = -1.0;
98
99            // start filter timer
100            StartTimer();
101        }
102
103        /// &lt;summary&gt;
104        /// Initializes second-order filter
105        /// &lt;/summary&gt;
106        /// &lt;param name=&quot;value&quot;&gt;initial value of filter&lt;/param&gt;
107        /// &lt;param name=&quot;time_constant&quot;&gt;time constant of primary filter, in seconds&lt;/param&gt;
108        /// &lt;param name=&quot;dot_time_constant&quot;&gt;time constant of secondary filter, in seconds&lt;/param&gt;
109        /// &lt;exception cref=&quot;ArgumentOutOfRangeException&quot;&gt;&lt;paramref name=&quot;time_constant&quot;/&gt; and &lt;paramref name=&quot;dot_time_constant&quot;/&gt; must be positive&lt;/exception&gt;
110        public ExpFilter(double value, double time_constant, double dot_time_constant)
111        {
112            // time constant must be positive
113            if (time_constant &lt;= 0.0) throw new ArgumentOutOfRangeException(nameof(time_constant));
114            if (dot_time_constant &lt;= 0.0) throw new ArgumentOutOfRangeException(nameof(dot_time_constant));
115
116            // initialize filter
117            _output = _input = value;
118            _dot = 0.0;
119
120            // calculate gains from time constants
121            _tc = CalcTC(time_constant);
122            _tc_dot = CalcTC(dot_time_constant);
123
124            // start filter timer
125            StartTimer();
126        }
127
128        // the following two functions must share the same time period
129        private double CalcTC(double time_constant)
130        {
131            // time period = 0.1 s (100 ms)
132            return Math.Exp(-0.1 / time_constant);
133        }
134        private void StartTimer()
135        {
136            // time period = 100 ms
137            _timer = new System.Threading.Timer(Filter_Timer, this, 100, 100);
138        }
139
140        ~ExpFilter()
141        {
142            Dispose(false);
143        }
144        public void Dispose()
145        {
146            Dispose(true);
147            GC.SuppressFinalize(this);
148        }
149        protected virtual void Dispose(bool disposing)
150        {
151            if (disposing)
152            {
153                _timer.Dispose();
154            }
155        }
156
157        /// &lt;summary&gt;
158        /// Get/Set filter value
159        /// &lt;/summary&gt;
160        public double Value
161        {
162            get =&gt; _output;
163            set =&gt; _input = value;
164        }
165
166        private static void Filter_Timer(object stateInfo)
167        {
168            var _filter = (ExpFilter)stateInfo;
169
170            // get values
171            double _input = _filter._input;
172            double _output = _filter._output;
173            double _dot = _filter._dot;
174
175            // if second-order, adjust _output (no change if first-order as _dot = 0)
176            // then use filter function to calculate new filter value
177            _input += (_output + _dot - _input) * _filter._tc;
178            _filter._output = _input;
179
180            if (_filter._tc_dot &gt;= 0.0)
181            {
182                // calculate second-order portion of filter
183                _output = _input - _output;
184                _output += (_dot - _output) * _filter._tc_dot;
185                _filter._dot = _output;
186            }
187        }
188    }
189

Source https://stackoverflow.com/questions/71493963