Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

Author Topic: Making a little chart app  (Read 1020 times)

phoenix27

• Jr. Member
• Posts: 65
Making a little chart app
« on: June 01, 2023, 03:28:04 pm »
I'm making a little chart app(candle chart to be precise and no , i don't wanty to use TAchart, i like to make things myself, well if it's not too complicated of course...).
This is the relevant code
Code: Pascal  [Select][+][-]
1. procedure Chart.drawChartToBmp;
2. var
3.   i:integer;
4.   d:array of double;
5.   x:double;
6.   offset:double;
7.   drawOnlylowhigh:boolean=false;
8.   cw:single;
9. begin
10.      cw:=fxmax / (fend-fstart);
11.      if cw < 2 then
12.          drawOnlylowhigh:=true;
13.      offset:= cw * 0.40;
14.    chartBmp.Canvas2D.clearRect(0,0,w,h);
15.    chartBmp.Canvas2D.lineWidth:=1;
16.    for i:=fstart to fend do begin
17.       chartBmp.Canvas2D.beginPath();
18.       d:= fdata[i];
19.       if d[4] > d[1] then begin //close > open
20.         chartBmp.Canvas2D.strokeStyle (green);
21.         chartBmp.Canvas2D.fillStyle (green);
22.       end
23.       else if d[4] < d[1] then begin
24.         chartBmp.Canvas2D.strokeStyle (red);
25.         chartBmp.Canvas2D.fillStyle (red);
26.       end
27.       else  begin
28.         chartBmp.Canvas2D.strokeStyle (white);
29.         chartBmp.Canvas2D.fillStyle (white);
30.       end;
31.       x:=q2x(d[5]);
32.       chartBmp.Canvas2D.moveTo(x, q2y(d[3])); //low
33.       chartBmp.Canvas2D.lineTo(x, q2y(d[2])); //high
34.       if not drawOnlylowhigh then begin
35.         chartBmp.Canvas2D.moveTo(x-offset, q2y(d[1]));//open
36.         chartBmp.Canvas2D.lineTo(x + offset, q2y(d[1]));
37.
38.         chartBmp.Canvas2D.lineTo(x + offset, q2y(d[4]));//close
39.         chartBmp.Canvas2D.lineTo(x - offset, q2y(d[4]));
40.       end;
41.       chartBmp.Canvas2D.closePath();
42.       chartBmp.Canvas2D.stroke();
43.       chartBmp.Canvas2D.fill();
44.    end;
45. end;
46.
Then i call invalidate and paint chartBmp on the screen.
Now, let's say i want add another feature(and i will!!) such as real time,so that everytime a new price comes in a draw another candle,  based on some timeframe. Of course there is the procedure
Canvas2D.clearRect() with which i can erase just the last candle and repaint it. Right? But what if i want to add an horizontal line going through all the chart, plus a price tag to show where the new  price is. Should i erase the whole chart and repaint it just to draw a line a nd a price tag? Isnt that unefficient?
I dont understand well this canvas thing. To change a detail(such as a line) i should repaint the whole chart?

phoenix27

• Jr. Member
• Posts: 65
Re: Making a little chart app
« Reply #1 on: June 01, 2023, 04:31:15 pm »
Also, imagine i want to add the ability for the user(me ahaha) to draw trendlines. I have to draw them on the chartBmp bitmap right? Together with the code needed to draw all the candles, which means everytime everything has to be redrawn. Is it possible? Am i missing something here? Or is it supposed to be this way?  In javascript, if i remember well, canvas seems to be persistent. I dont have to draw in a special method called by the OS or whatever. So if i want to add a trendline on the chart or something else i dont have to redraw everything. How can i do it in pascal efficiently? Anybody knows?

wp

• Hero Member
• Posts: 11222
Re: Making a little chart app
« Reply #2 on: June 01, 2023, 10:59:56 pm »
Canvas2D.clearRect() with which i can erase just the last candle and repaint it. Right? But what if i want to add an horizontal line going through all the chart, plus a price tag to show where the new  price is. Should i erase the whole chart and repaint it just to draw a line a nd a price tag? Isnt that unefficient?
It depends... Of course you can erase an individual candle by Canvas2D.ClearRect. This works fine when there is nothing else in the cleared rectangle. If there is, such as another line or the axis grid, it is easier to erase the entire chart and redraw everything. Unless you have millions of data points you will not notice any delay.

I dont understand well this canvas thing.
Imagine you are a painter and have a canvas in front of you and paint on this canvas - it's the same here: the Canvas is surface on which you are painting using pens and brushes. BUT: There are several kinds of canvases. A Paintbox, for example, is a control which is designed for user painting. It provides you with a canvas but this canvas is not necessarily persistent. Imagine that it only "exists" during the painting cycle, i.e. in the OnPaint event of the Paintbox (similar with TForm or TPanel). So rather than painting in the OnClick event of a button
Code: Pascal  [Select][+][-]
1. procedure TForm1.Button1Click(Sender: TCanvas);
2. begin
3.   Paintbox1.Canvas.Line(0, 0, Paintbox1.Width, paintbox1.Height);
4. end;
you should always paint in the OnPaint event handler:
Code: Pascal  [Select][+][-]
1. procedure TForm1.Paintbox1Paint(Sender: TCanvas);
2. begin
3.   Paintbox1.Canvas.Line(0, 0, Paintbox1.Width, paintbox1.Height);
4. end;

Why? Because the canvas always must be able to redraw itself at any time requested. When you paint in the OnClick event the drawing probably does appear, but when the user temporarily drags another window over your drawing the Paintbox is forced to redraw itself - but the code of the OnClick event is no longer available, and your drawing will be erased.

In your special case, however, things are different since you seem to be painting on the canvas of a bitmap. This kind of canvas is not linked to a control and is always existent. You can draw on it at any time, and the pixels that you change this way will remain changed. But you must not forget to copy the bitmap onto the canvas of the control on which you want to see your drawing. And, of course, you do this on the OnPaint event of the control:
Code: Pascal  [Select][+][-]
1. procedure TForm1.PaintboxPaint(Sender: TObject);
2. begin
3.   Paintbox1.Canvas.Draw(0, 0, Your_Bitmap);
4. end;
And to trigger the OnPaint event you call the control's Invalidate method:
Code: Pascal  [Select][+][-]
1. procedure TForm1.Button1Click(Sender: TObject);
2. begin
3.   YourBitmap.Canvas.Line(0, 0, YourBitmap.Width, YourBitmap.Height);
4.   Paintbox1.Invalidate;
5. end;

phoenix27

• Jr. Member
• Posts: 65
Re: Making a little chart app
« Reply #3 on: June 05, 2023, 09:50:32 am »
Thank you for your answer. I thnk i understand it better now.

circular

• Hero Member
• Posts: 4048
Re: Making a little chart app
« Reply #4 on: June 08, 2023, 01:23:15 pm »
A little detail, the line chartBmp.Canvas2D.closePath(); would make more sense to be moved up one line, inside the begin/end. Indeed, closePath closes the current polygon. Note that you don't always need to call closePath for each beginPath. In fact, it would be better to call it closeSubPath but I've used the same name as in HTML5 canvas.
Conscience is the debugger of the mind

phoenix27

• Jr. Member
• Posts: 65
Re: Making a little chart app
« Reply #5 on: August 24, 2023, 05:03:18 am »
Right circular. Sorry i didn't read your reply before because i don't get notified so after a while i even forget my own posts. Anyway i can't get past the problem i talked about. If in the mousemove event i add, when the control key is down, an horizontal and vertical line which then follow the mouse pointer around, the drawing becomes slow because every time it has to make the chart again. But the only thing changing is the 2 lines, whereas the chart candles don't change. Imagine hundreds of little candles being created again and again while the mouse is moving, of course it will slow down the whole thing(see the image). So is there a way to separate the chart itself from the lines and other graphic annotations i could add later?

phoenix27

• Jr. Member
• Posts: 65
Re: Making a little chart app
« Reply #6 on: August 24, 2023, 02:35:03 pm »
I solved by putting the code regarding the lines in the paint event, ahahha, so simple, why before i put it in the chartBmp... mahh:
Code: Pascal  [Select][+][-]
1. procedure TChart.chartAreaPaint(Sender: TObject);
2. var
3.   cnvs: TCanvas;
4. begin
5.   cnvs := chartArea.Canvas;
6.   chartBmp.Draw(cnvs,0,0,True);
7.   volBmp.Draw(volArea.Canvas, 0, 0, true);
8.   cnvs.Pen.Color:=clwhite;
9.   cnvs.Line(0, cy, cnvs.Width, cy);
10.   cnvs.Line(cx, 0, cx, cnvs.Height);
11. end;
12.

circular

• Hero Member
• Posts: 4048
Re: Making a little chart app
« Reply #7 on: August 24, 2023, 09:03:51 pm »
That's indeed a way to do it efficiently. Congrats
Conscience is the debugger of the mind