Putting It All Together
You'll notice that the chart's Draw()
method still requires a Control reference as a parameter that specifies the control into which the chart should render itself. I considered caching the control so you could call Transform()
with one less parameter; but that would tightly couple the chart to the dimensions and lifetime of that specific control. While that might be exactly what you want, most of the time, I couldn't see any compelling reasons to presume it always was. For the same reason, I've left the Chart class as a class library, rather than turning it into a formal control, so that you can integrate it loosely into your own projects.
Adding the Chart.Study class prompted me to add more support for user color schemes. You can individually specify the chart's background color, axis color and the color of any reference values displayed, in addition to the color and pen style of any studies you want to draw. You can also name the X and Y axes using the XLabel and YLabel properties respectively. Finally, the set of AtMinX()
, and AtMaxY()
methods keep track of the position of m_data_rect
with respect to m_limits_rect
. That lets users scroll around a study freely, but takes appropriate action when they reach the bounds of that study. For example, you can disable the "left scroll" button when AtMinX()
returns true. If users end up losing their view of the study completely, you can call Reset()
to restore m_data_rect
to its default position.
You can also subclass the Chart itself and override the two virtual methods, TranslateXValue() and TranslateYValue()to customize what kind of reference values get displayed. You might want to do that if you've had to perform some kind of type coercion to create your studies in the first place. For example, you might have converted a date or time object into a long or double, but you want to display a more user friendly string. If that's the case, this is the place to do your conversion. TranslateXValue() and TranslateYValue() take a float to convert, along with an int specifying the number of decimal places to use for the conversion. Using the methods, you can decide how accurately you want your reference values to be alignedfor example, you could refuse to supply any reference values until you get to at least three decimal places. You also have the option of refusing to supply any reference value at all, typically because you've either been supplied an out-of-range index, an appropriate conversion is unavailable, or a reference value at the specified precision would be ambiguous.
Download the sample project and examine the code in ChartTest.zip to get a feel for what the Chart class can do, or take a look at the screenshot in Figure 9.
|Figure 9. ChartTest in Action: The sample ChartTest project window displays charts along with reset, zoom, and scroll controls.|
ChartTest loads a year's worth of closing share price data for ticker symbol MSFT (Microsoft Corporation), and renders it in a PictureBox control with a nine-day simple moving average superimposed as a secondary study. It subclasses the Chart and overrides TranslateXValue()
to render dates as strings along the X axis. To see the effect that TranslateXValue()
has on the display, click the Dates/Values
button to switch between the overridden and the default implementations. The main form contains two zoom buttons and four scroll buttons at the bottom right of the screen, which you can use to move around the chart . The buttons become disabled when you reach the edges of the data rectangle. Finally, you can snap any point to the center of the chart by clicking the left or right mouse buttons.
Playing around with the Chart class, I realized that there are still a few "nice to have" features missing. One would be to annotate each study with a descriptive string, so that, for charts containing multiple studies, you could tell which study was which. Another would be to optimize the rendering code. Finally, studies should perhaps be allowed to implement their own drawing logic so that they can render candlesticks, point-and-figure diagrams, or other custom formations. However, all these are applicationspecific. You should be able to fulfil the majority of your basic graphing needs with the class as it stands. This article should illustrate a few pitfalls and give a few pointers in case you want to extend the sample to develop your own more specialized charts.