|
custom drawing problem |
Posted by Thexder on Mar-10-2021 06:13 |
|
Hi Peter,
below is how I repeat the same sinarios in the financedemo program,
please see the upload images,
/////////////////////////////////////////////////////////////
for (int i = 1; i < 3; i++)
{
XYChart* c = (XYChart*)m.getChart(i);
PlotArea* p = c->getPlotArea();
//
DrawArea* d = c->makeChart();
int top = p->getTopY();
int bottom = p->getBottomY();
int left = p->getLeftX();
int right = p->getRightX();
d->setClipRect(left, top, right, bottom);
d->circle(right - 20, bottom - 20, 10, 10, 0x000000, 0xff0000);
}
////////////////////////////////////////////////////////////
// Set the chart to the viewer
viewer->setChart(&m);
it seems that if you want to draw something on other than the first chart,
you must draw something on the first chart frist, otherwise the rest of the charts
are incomplete.
I don't understand what's wrong with this, would you please help?
Rgds,
Thexder
|
Re: custom drawing problem |
Posted by Peter Kwan on Mar-10-2021 14:12 |
|
Hi Thexder,
In FinanceChart, the first chart that will determine the x-axis labels. If you add more charts, the other charts will copy the x-axis scale from the chart before.
If your code suddenly called "c->makeChart();" for a chart in the middle, it will try to get the x-axis scale from the previous chart. But the previous chart has not been drawn, so the x-axis scale becomes incorrect. That means you must start calling makeChart from the first chart.
The "Finance Chart Track Line" sample code is an example of using the DrawArea to draw things on a FinanceChart. The method we usually used is as follows:
// Make the entire FinanceChart.
DrawArea *d = m.makeChart();
for (int i = 1; i < 3; i++)
{
XYChart* c = (XYChart*)m.getChart(i);
PlotArea* p = c->getPlotArea();
int top = p->getTopY() + c->getAbsOffsetY();
int bottom = p->getBottomY() + c->getAbsOffsetY();
int left = p->getLeftX();
int right = p->getRightX();
d->setClipRect(left, top, right, bottom);
d->circle(right - 20, bottom - 20, 10, 10, 0x000000, 0xff0000);
}
In the above, the code draws on the FinanceChart surface directly, not an the XYChart.
If you must draw on the XYChart instead, please add another loop before your existing loop to call makeChart for all the XYChart. This is to ensure the charts are made in the correct order so that the x-axis scale can be propagated correctly.
Regards
Peter Kwan |
Re: custom drawing problem |
Posted by Thexder on Mar-11-2021 04:03 |
|
Hi Peter,
thanks for the quick reply,
by the way, I found I cannot get correct result from the custom drawing on XYChart,
since I need to draw something on each indicator, and sync with the viewport drawing
firstly I convert the mouse coordinate to data coordinate as below,
int mouse_x = m_Chartviewer->getChartMouseX();
int mouse_y = m_Chartviewer->getChartMouseY();
int offset_y = c->getPlotArea()->getTopY() + c->getAbsOffsetY();
CPoint(c->getNearestXVale() + startIndex, c->getYValue(mouse_y - offset_y));
and then convert it back to pixel coordinate
CPoint(c->getXCoor(pt.x- startIndex), c->getYCoor(pt.y));
Rgds,
Thexder |
Re: custom drawing problem |
Posted by Peter Kwan on Mar-11-2021 23:51 |
|
Hi Thexder,
From your code style, I assume your chart is a FinanceChart, and you are clicking on one of the XYChart inside the FinanceChart.
In this case, may be you can try:
// mouse coordinates relative to the XYChart "c"
int mouse_x = m_Chartviewer->getChartMouseX();
int mouse_y = m_Chartviewer->getChartMouseY() - c->getAbsOffsetY();
// Assume your code have determined that "c" is the XYChart under the mouse
// The extraPoint is the extraPoint parameter in the FinanceChart.setData line
pt = CPoint(c->getNearestXValue(mouse_x) + extraPoint + startIndex, c->getYValue(mouse_y));
Then your to draw something. Would you mind to clarify which object do you use to draw the things? I suggest to use the DrawArea of the FinanceChart (as opposed to the DrawArea of the XYChart).
// You must call makeChart first before using getXCoor and getYCoor. For the FinanceChart,
// you can simply call the makeChart after setting up the FinanceChart
DrawArea *d = m_ChartViewer->getChart()->makeChart();
// Assume c is the XYChart to draw the things. Assuming your code have determined
// that the point is still within the x-axis and y-axis range after scrolling/zooming.
int drawX = c->getXCoor(pt.x - extraPoint - startIndex);
int drawY = c->getYCoor(pt.y) + c->getAbsOffsetY();
.... now draw using the DrawArea *d ***
Regards
Peter Kwan |
Re: custom drawing problem |
Posted by Thexder on Mar-12-2021 09:27 |
|
Hi Peter,
thanks for the reply,
I can draw things on the first chart sucessfully , ie. financeChart mainChart, but I cannot draw anything on rest of the charts. yes I am using XYChart* c as the object to draw custom shapes.
I cannot use financeChart* m as the drawing object, since I need to zoom XYChart & sync custom drawing on it individually, obviously use entire financeChart as the object to draw is not workable, and those XYCharts position may changed in some application sinario, then it is not possible for you to retrieve the custom drawings after changing.
the problem here is I use same method to iterate all charts, why I cannot get same result from rest of the charts?
Previously I thought it may be cuased by the wrong pixel coordintate on those indicator charts, but now I followed your code....
Rgds,
Thexder |
Re: custom drawing problem |
Posted by Peter Kwan on Mar-12-2021 21:22 |
|
Hi Thexder,
I assume you have already successfully implemented the zoom function, and it works normally. You need to implement custom symbols so that they move to stay in the same "data position" after zooming and scrolling.
After zooming and scrolling, your code must have redraw the chart. Suppose you need to add 3 symbols at (dataX, dataY) = (60, 160), (80, 60) and (95, -10), in the 3 different XYCharts in your FinanceChart. So your data are like:
int chartIndex[] = { 0, 1, 2 };
double symbolX[] = { 60, 80, 95 };
double symbolY[] = { 160, 60, -10 };
The code to draw the symbols is:
// m is the FinanceChart pointer
DrawArea *d = m->makeChart();
for (int i = 0; i < 3; ++i)
{
XYChart* c = (XYChart *)m->getChart(i);
int drawX = c->getXCoor(symbolX[i] - extraDays - startIndex);
int drawY = c->getYCoor(symbolY[i]) + c->getAbsOffsetY();
d->circle(drawX, drawY, 10, 10, 0xff0000, 0xff0000);
}
In the above, note that it draws on the DrawArea of the FinanceChart. The code is simplest using this method. Drawing on the XYChart is the same except that you do not need the getAbsOffsetY. (The getAbsOffsetY is to convert the XYChart pixel coordinates to FinanceChart pixell coordinates.)
I have attached an example by modifying the "Finance Chart (1)" sample code.
For your case, may be you can try using some hard coded data first like in my code above, to test if the symbols are drawn correctly. If you scroll the chart, the symbols should scroll too. (Note: In your real code, you should check if the point has scrolled outside the plot area before drawing.)
If the hard coded data works, but your completed code do not work, the problem is probably in the data arrays (chartIndex, symbolX and symbolY). When the user clicks on a point in the chart, your code would need to do the followings:
(a) Determine which XYChart the user has clicked on. The mouse coordinates you obtain from getChartMouseX is for the chart in the CChartViewer, which is the FinanceChart. You can iterate the XYChart objects one by one, use getAbsOffsetY to convert the FinanceChart coordinates to XYChart coordinates, and check if the mouse is within the XYChart plot area. In this way, you can find the XYChart and then save its index in the chartIndex array.
(b) Using the converted XYChart coordinates, you can obtain the (dataX, dataY) from the pixel coordinates using the method is my previous message. You may want to print the dataX and dataY in the debugger window, so you can see it instantly to verify if they are what you expect.
Regards
Peter Kwan
finance_chart_symbols.cpp |
---|
BaseChart* finance(int /* chartIndex */, const char** /* imageMap */)
{
// Create a finance chart demo containing 100 days of data
int noOfDays = 100;
// To compute moving averages starting from the first day, we need to get extra data points
// before the first day
int extraDays = 30;
// In this exammple, we use a random number generator utility to simulate the data. We set up
// the random table to create 6 cols x (noOfDays + extraDays) rows, using 9 as the seed.
RanTable* rantable = new RanTable(9, 6, noOfDays + extraDays);
// Set the 1st col to be the timeStamp, starting from Sep 4, 2002, with each row representing
// one day, and counting week days only (jump over Sat and Sun)
rantable->setDateCol(0, Chart::chartTime(2002, 9, 4), 86400, true);
// Set the 2nd, 3rd, 4th and 5th columns to be high, low, open and close data. The open value
// starts from 100, and the daily change is random from -5 to 5.
rantable->setHLOCCols(1, 100, -5, 5);
// Set the 6th column as the vol data from 5 to 25 million
rantable->setCol(5, 50000000, 250000000);
// Now we read the data from the table into arrays
DoubleArray timeStamps = rantable->getCol(0);
DoubleArray highData = rantable->getCol(1);
DoubleArray lowData = rantable->getCol(2);
DoubleArray openData = rantable->getCol(3);
DoubleArray closeData = rantable->getCol(4);
DoubleArray volData = rantable->getCol(5);
// Create a FinanceChart object of width 640 pixels
FinanceChart* m = new FinanceChart(640);
// Add a title to the chart
m->addTitle("Finance Chart Demonstration");
// Set the data into the finance chart object
m->setData(timeStamps, highData, lowData, openData, closeData, volData, extraDays);
// Add the main chart with 240 pixels in height
m->addMainChart(240);
// Add a 5 period simple moving average to the main chart, using brown color
m->addSimpleMovingAvg(5, 0x663300);
// Add a 20 period simple moving average to the main chart, using purple color
m->addSimpleMovingAvg(20, 0x9900ff);
// Add HLOC symbols to the main chart, using green/red for up/down days
m->addHLOC(0x008000, 0xcc0000);
// Add 20 days bollinger band to the main chart, using light blue (9999ff) as the border and
// semi-transparent blue (c06666ff) as the fill color
m->addBollingerBand(20, 2, 0x9999ff, 0xc06666ff);
// Add a 75 pixels volume bars sub-chart to the bottom of the main chart, using green/red/grey
// for up/down/flat days
m->addVolBars(75, 0x99ff99, 0xff9999, 0x808080);
// Append a 14-days RSI indicator chart (75 pixels high) after the main chart. The main RSI line
// is purple (800080). Set threshold region to +/- 20 (that is, RSI = 50 +/- 25). The
// upper/lower threshold regions will be filled with red (ff0000)/blue (0000ff).
m->addRSI(75, 14, 0x800080, 20, 0xff0000, 0x0000ff);
// Append a 12-days momentum indicator chart (75 pixels high) using blue (0000ff) color.
m->addMomentum(75, 12, 0x0000ff);
// Output the chart
DrawArea *d = m->makeChart();
int chartIndex[] = { 0, 1, 2 };
double symbolX[] = { 60, 80, 95 };
double symbolY[] = { 160, 60, -10 };
for (int i = 0; i < 3; ++i)
{
XYChart* c = (XYChart *)m->getChart(i);
int drawX = c->getXCoor(symbolX[i] - extraDays - 0);
int drawY = c->getYCoor(symbolY[i]) + c->getAbsOffsetY();
d->circle(drawX, drawY, 10, 10, 0xff0000, 0xff0000);
}
//free up resources
delete rantable;
return m;
}
|
| |
Re: custom drawing problem |
Posted by Thexder on Mar-15-2021 16:52 |
|
Hi Peter,
It works now,
but I found the custom drawing has a viewport sync. problem on those indicator which has a dynamic yAxis scale, but for those has fixed YAxis scale are working good.
below are indicators have dynamic YAxis scale
Accumulator/Distribution
NVI
OBV
Performance
PVI
please check the attached snapshots, 1st one is before zoomscroll and 2nd one is after zoomscroll.
How can I fix this?
Rgds,
Thexder
|
Re: custom drawing problem |
Posted by Peter Kwan on Mar-15-2021 22:41 |
|
Hi Thexder,
From your image, I think everything is working correctly.
Are you aware many indicators can change values depending on the starting point of the plot? For example, suppose the Accumulator/Distribution value is 5.879 on Mar 1, 2021. After you scroll the chart, it may become 3.817 on Mar 1, 2021. This is the normal and correct behavior according to the definition of Accumulator/Distribution indicator.
It is because the Accumulator/Distribution indicator is about accumulation of some values. To accumulate, you must have a starting point. If you scroll or zoom, the starting point changes, so that value changes. The value is therefore meaningless. Only the trend of the value and the change of the value is meaningful.
When the user clicks on the chart, does your code store the y-axis value? If this is the case, when the chart scrolls/zooms, the code will accurately plot the symbol at the same (x, y) location. However, the indicator itself changes, so the distance between the point and the indicator can change.
So when the user clicks on the chart, your code must understand what is the intention of the user? Does the user want to click on a point on the indicator (so the point will follow the indicator if the indicator changes, or does the user want to click on a point at a specific (x, y) value? If the intention is to click on the indicator, since the user may not be able to exactly click on the indicator (there may be one or a few pixels or error), your code may need to snap the point to the indicator.
In the drawing code, if the intention is to follow the indicator, your code would need to obtain the updated indicator value when the chart is scroll/zoom.
Many indicators can change values, including:
- All indicators that use accumulation
- Indicators that uses exponential average can change slightly
- All indicator values would change if the chart changes resolution, such as from a daily chart (one candlestick per day) to a weekly chart (one candlestick per week).
Regards
Peter Kwan |
Re: custom drawing problem |
Posted by Thexder on Mar-16-2021 04:53 |
|
Hi Peter,
thanks for your information,
however I still confused with this problem, and don't know to to fix it.
please check the attached images for your better understanding of this problem
that I encountered here,
please let me know how to solve this by coding.
thanks!
Rgds,
Thexder
|
Re: custom drawing problem |
Posted by Peter Kwan on Mar-16-2021 17:36 |
|
Hi Thexder,
In both the candlestick and the accumulation/distribution, the line follows the y-axis accurately.
For the candlestick chart, the line is from approximately y = 1290 to 1265. After the y-axis changes, it is still at y = 1290 to 1265.
For the accumulation/distribution, the horizontal line is at approximately y = 4. After the y-axis changes, it is still at y = 4.
This shows that the custom line follows the y-axis correctly.
As explain in my previous email, for the accumulation/distribution, the indicator does not follow the y-axis. This is by definition of the accumulation/distribution indicator. So if you want to follow the y-axis, then the distance between the horizontal line and the accumulation/distribution indicator will change, because the horizontal line follows the y-axis accurately, while that indicator does not follow the y-axis.
If you want the original line to follow the indicator, that means the line must not follow the y-axis. So instead of storing the y value of the point, you need to store the point on the indicator you want to follow. When the indicator changes, your code can get the new value to plot the line, so that it follows the indicator.
So when the user clicks on the mouse, your code need to determine whether the user wants to follow the y-axis or to follow the indicator. Since it is very difficult to exactly click on an indicator (there may be a few pixels of mouse click error), you may need to guess what is the intention of the user and snap the point to follow the indicator.
Regards
Peter Kwan |
Re: custom drawing problem |
Posted by Thexder on Mar-17-2021 04:02 |
|
Hi Peter,
Thanks for your patient and helped me a lot to understand the reason completely.
so in this case, is it possible for me to make accumulation/distribution indicator to follow the y-axis?
since I found there are so many indicators have such nature in property.
I thought this would be much easier for me to implement and get the job done quickly!
Rgds,
Thexder |
Re: custom drawing problem |
Posted by Peter Kwan on Mar-17-2021 23:41 |
|
Hi Thexder,
The accumulation/distribution indicator is not invented by us. We cannot change its formula, otherwise it is not the accumulation/distribution indicator.
You can search google to find its formula. From its formula, you can see that it depends on the starting point of the data series. As you zoom/scroll, the starting point changes, so the indicator change values.
It you do not want the indicator to change values, you can keep the starting point the same for all your charts.
Suppose the full range of your chart has 10 years of data, and the user is viewing the last 30 days of data. The user can scroll back and forth or zoom in and out. The zooming and viewing code usually compute a "startIndex" and "endIndex" so that only the visible data (plus some extra points) are used to plot the chart.
If you want to always use the same starting point, the only method is to set the startIndex to 0, and then increase the extraPoints parameter to make the unnecessary data. In this way, the starting point is always the same point 0, and the indicator will always compute to the same value.
Note that this method may make the chart slower, because your code can pass much more data than necessary (something like 100 times more than necessary) to plot the chart, and all the data needs to be processed to compute the indicators. The resulting indicator values may also look strange because it is not how this indicator is typically computed.
Regards
Peter Kwan |
Re: custom drawing problem |
Posted by Thexder on Mar-18-2021 06:46 |
|
Hi Peter,
I do understand your point, but it’s so interesting for me to know how can trading view achieved this perfectly?
Any idea?
Please check attached images. You may also visit their website to do the test.
|
Re: custom drawing problem |
Posted by Peter Kwan on Mar-18-2021 23:55 |
|
Hi Thexder,
I have just tried trading view myself. I found that it does not achieve what you mentioned. It behaves the same as ChartDirector, that is, the indicator does not follow the axis.
To test, first draw a straight line when viewing using the "1Y" mode. Then press the "5Y" button or the "All" button. You can see the custom horizontal line is no longer following the indicator, and the indicator changes value, exactly as what ChartDirector does.
I notice for the "1Y" button, the chart cannot zoom out beyond 1Y, although you can scroll back. But if you scroll back too far, the line will scroll out of view. So even it does not follow the indicator, you cannot see it.
That means it only needs to keep the indicator stationary for 1 year. For the "1Y" chart display the year 2020 to 2021, may be it can set the starting point to the the first day 5 years (on Jan 1, 2016). In this way, the chart can remain stable when scrolling for a few years, and when the indicator does change value, you would not see the line. This avoids having to pass all the data to the chart (which would have 45 years of data for APPL).
So in summary, they limit the zoom out to "1Y", and they use a fix starting point enough to keep the indicator stable until you can no longer see it. If you press the "5Y" button, you can see that it is in fact the same as ChartDirector - that the indicator does not follow the axis.
Regards
Peter Kwan |
|