Hi Christophe,
Sorry for the late response, have been very busy lately cranking up the non-profitable algos an masse...
I have prototyped only some stuff with ChartDirector and do not have nice working example, but will try to help with some snippets.
That's right, the whole image is created at once, and it's really not the environment to deal with the interactive, real-time charts in common way. It is definitely geared toward the offline chart handling. Having said that, I am very impressed with the library speed and it is really coming very close to be a non-issue. One thing, you have to handle the image in memory, without saving it to file. It took me several attempts to figure out how to process the output from ChartDir in wxPython.
If you are using the fixed y_scale, then you already know your boundaries. X_scale is your time line (i.e. number of data points) and Y_scale is your price range. If you are using the auto scale, you have to pack the plot first to get the Y_scale min/max.
Then you get the coords of your trend line, add line to the chart and adjust z-order if needed.
When it comes to interaction, that's where it becomes awkward. I got something working by using the <getHTMLImageMap> function. It is meant for the web app, and the output is the html string, which gives you the info about all image objects, so you can have a clickable charts (there is a working example in docs). However, in wxPython you have to parse the output and put the info in dictionary and process it through the mouse and graphics events. Inside the Frame where you render the image, you have to do this:
This sample gets the info about the HLC bars, and the comment lines are output sample that is parsed. You have to read the docs and understand the <getHTMLImageMap> function and the output.
I am attaching the ImageCanvas class I have wrote, it is rather generic and you have to assign the instance to the canvas of your wxFrame. Inside the ImageCanvas, the on_mouse_motion() event is processing the info dictionary from the above as well as cross-hair, scale label, repainting, etc. By setting the status of <bitmap_status> var in your wxFrame, you are controlling the kickoff of the rendering logic, when graphic context is idle.
The whole thing will give you back the info about your trend line and you could simply show a dialog for the input of the new coords, or if you want mouse action, play with the mouse events and get the new coord via clicks. Lastly, you have to render the image again.
If you want to highlight the bar, you would have to have a "shadow" data series, with single bar in it, the one that you would get from the above described procedure. You would be rendering the image every time when mouse gives you the new location. Although I believe that, probably, you are not going to see any issues, it might start showing some flickering under the heavy load.
Instead of "highlighting" I was simply showing the bar info in the info box. That would be fast and efficient way for you comments as well. You put the info/comments in dictionary and do this:
You can make your info/comments persistent in many ways (pickle, file, dbs, etc).
Once again, it starts getting awkward at some point, but considering how quickly you can build variety of charts and being very fast, the library is great tool and can take you long way before you hit the wall with some super-short refresh time requirement.
Hope this help.
Dr. Sheldon Cooper
Sorry for the late response, have been very busy lately cranking up the non-profitable algos an masse...
I have prototyped only some stuff with ChartDirector and do not have nice working example, but will try to help with some snippets.
As ChartDir is creating the whole image at once, I'm not sure if this is do-able.
That's right, the whole image is created at once, and it's really not the environment to deal with the interactive, real-time charts in common way. It is definitely geared toward the offline chart handling. Having said that, I am very impressed with the library speed and it is really coming very close to be a non-issue. One thing, you have to handle the image in memory, without saving it to file. It took me several attempts to figure out how to process the output from ChartDir in wxPython.
Code:
stream = cStringIO.StringIO( c1.makeChart2(0) )
img = wx.ImageFromStream( stream )
stream.close()
self.canvas.bitmap = wx.BitmapFromImage( img )
Does your app allow you to draw trendline? That's something I'd like to do but not sure how to go around it yet. To be more precise, I'd like to draw a line, click on it, move it, etc...
If you are using the fixed y_scale, then you already know your boundaries. X_scale is your time line (i.e. number of data points) and Y_scale is your price range. If you are using the auto scale, you have to pack the plot first to get the Y_scale min/max.
Code:
#--- pack plot area so we can put some custom lines
c1.packPlotArea( x1, y1, w, h )
y_axis_min = c1.yAxis().getMinValue()
y_axis_max = c1.yAxis().getMaxValue()
Code:
x1_coor = c1.getXCoor( trendline_start_datapoint )
x2_coor = c1.getXCoor( trendline_end_datapoint )
y1_coor = c1.getYCoor( trendline_start_pricepoint )
y2_coor = c1.getYCoor( trendline_end_pricepoint )
cp_line = c1.addLine( x1_coor, y1_coor, x2_coor, y2_coor, c1.dashLineColor(0x40C020,pychartdir.DotLine), 2 )
cp_line.setZOrder( pychartdir.PlotAreaZ )
Code:
stream = cStringIO.StringIO( c1.makeChart2(0) )
#--------------------------------------------------------------------------------------------#
# create image maps
image_map = c1.getHTMLImageMap('a','x={x} T={xLabel} H={high} L={low} C={close}')
#<area shape="rect" coords="889,379,895,398" href="a?x=99 T=01:00 H=99232 L=99147 C=99177">
y_axis_map = c1.yAxis().getHTMLImageMap('b','{value}')
#<area shape="rect" coords="901,490,935,505" href="b?P=98700">
#<area shape="rect" coords="901,467,935,482" href="b?P=98800">
#<area shape="rect" coords="901,445,935,460" href="b?P=98900">
self.chart_data_info = [] #[ ((x1,y1,x2,y2),'x=99 T=01:00 H=99232 L=99147 C=99177'),...]
s = image_map.split('">')
for line in s:
a = line.partition('coords="')
b = a[2].partition('" href="a?')
if b[0].strip() == '': continue
self.chart_data_info.append( (tuple(b[0].split(',')), b[2]) )
self.y_axis_info = [] #[ ((x1,y1,x2,y2),'98700'),...]
s = y_axis_map.split('">')
for line in s:
a = line.partition('coords="')
b = a[2].partition('" href="b?')
if b[0].strip() == '': continue
self.y_axis_info.append( (tuple(b[0].split(',')), int(float(b[2]))) )
#--------------------------------------------------------------------------------------------#
img = wx.ImageFromStream( stream )
stream.close()
self.canvas.bitmap = wx.BitmapFromImage( img )
self.Refresh()
self.canvas.bitmap_status = 'DONE'
I am attaching the ImageCanvas class I have wrote, it is rather generic and you have to assign the instance to the canvas of your wxFrame. Inside the ImageCanvas, the on_mouse_motion() event is processing the info dictionary from the above as well as cross-hair, scale label, repainting, etc. By setting the status of <bitmap_status> var in your wxFrame, you are controlling the kickoff of the rendering logic, when graphic context is idle.
The whole thing will give you back the info about your trend line and you could simply show a dialog for the input of the new coords, or if you want mouse action, play with the mouse events and get the new coord via clicks. Lastly, you have to render the image again.
I'd like to add comment to the chart and store it somewhere, and display it again when I'm viewing the same timeframe at a later stage.
Another thing I'd like to do, is to highlight a bar when I mouse over it.
If you want to highlight the bar, you would have to have a "shadow" data series, with single bar in it, the one that you would get from the above described procedure. You would be rendering the image every time when mouse gives you the new location. Although I believe that, probably, you are not going to see any issues, it might start showing some flickering under the heavy load.
Instead of "highlighting" I was simply showing the bar info in the info box. That would be fast and efficient way for you comments as well. You put the info/comments in dictionary and do this:
Code:
#--- add some text in upper corner (e.g. legend) It is a dictionary {'title':'text',...}
if self.legend:
h_offset = 100; v_offset = 15; yy1 = y1 + v_offset
for legend_title,legend_txt in self.legend.items():
c1.addText( x1, yy1, legend_title, font, 10, 0x838d74 )
c1.addText( x1+h_offset,yy1, legend_txt, font, 10, 0x838d74 )
yy1 += v_offset
Once again, it starts getting awkward at some point, but considering how quickly you can build variety of charts and being very fast, the library is great tool and can take you long way before you hit the wall with some super-short refresh time requirement.
Hope this help.
Dr. Sheldon Cooper
