PDA

View Full Version : Time for some graphs


mondomjm
04-06-2005, 05:00 AM
OK friends.

I am going to appeal for some help. Not the usual kind (I don't have any current issues!), but some open-source spirit coding help. I am looking for some people who want to help and give input on a graphing class. After playing with 3.0b2 I have come up with the basics for a simple but dynamic pie charting class (I thought I would tackle the hardest one first...this code took about 1 hour to do last night...basically, if we can draw the elements, we can do the rest). The rudimentary "proof of concept" methods I post here will be refined in the next few weeks to include much of the functionality of the celebrated xmlcharts from maani.us. If this works, I would like to submit it to the pros here at laszlo systems for consideration of inclusion. Many of us feel this is a must have feature that can help propel this platform forward fast.

I don't know if there is a process for something being an official effort, or if there are other efforts going, but I will be doing this regardless of anyone "signing up to help", so I will be coordinating this, and I thought I would throw this out to see if there were any Laslovians who:
a: want to make this a reality faster
b: want to cut their teeth on some drawing api stuff
c: want to feel the satisfaction of giving back via open source

I could use :
A good javascript person or two
A good visual designer to help with look and feel
A tester

Here is the initial scope:

LGAPI (Laszlo Graphing API...let me know if I can't do that with the name...)

Initial Graph types: Pie, Column, Bar, Line, Scatter, Hi-Lo-Close, Stacked Bar, Stacked Column, Area, Polar, Bubble

Future Considerations: 3D versions (heck, it's just isometric drawing folks, which is just more math!)

Features: Binding to an XML dataset; Legend Control; Axis Control and Labels; Titles; Pop-Up tags; Drill Down, etc.

Here is some proof of concept code:

The class:

<class name="lgapi_pie" extends="drawview">
<method name="draw_pie" args="origin_x,origin_y,radius,pct_1,pct_2,pct_3">
<![CDATA[
//declare internal variables
var start_ang, incl_ang, slice_clr
start_ang = 0;
incl_ang = 0;

//all of the code below will be replaced with input arg array looping code

//start at horizontal
start_ang = start_ang + incl_ang;
incl_ang = pct_1 * 3.6;
slice_clr = 0x0000FF;

//draw slice
this.draw_slice(origin_x, origin_y, start_ang, incl_ang, radius, slice_clr);

//reset start angle for slice 2
start_ang = start_ang + incl_ang;
incl_ang = pct_2 * 3.6;
slice_clr = 0xFF00FF;

//draw slice
this.draw_slice(origin_x, origin_y, start_ang, incl_ang, radius, slice_clr);

//reset start angle for slice 3
start_ang = start_ang + incl_ang;
incl_ang = pct_3 * 3.6;
slice_clr = 0x00FFFF;

//draw slice
this.draw_slice(origin_x, origin_y, start_ang, incl_ang, radius, slice_clr);
]]>
</method>

<method name="draw_slice" args="origin_x,origin_y,start_ang,incl_ang,radius,slice_ clr">
<![CDATA[
//declare internal variables
var seg_ang, theta, ang, half_ang, incl_ang, segments
var start_x, start_y, end_x, end_y, ctrl_x, ctrl_y

//move to origin point
this.beginPath();
this.moveTo(origin_x, origin_y);

//limit arc to a full circle
if(Math.abs(incl_ang) > 360) {
incl_ang = 360;
}

//divide into a clean number of segments for good arc quality
segments = Math.ceil(Math.abs(incl_ang) / 45);
seg_ang = incl_ang / segments;

//convert degrees to radians
theta = -(seg_ang / 180) * Math.PI;
ang = -(start_ang / 180) * Math.PI;

//draw the arc segments (already!)
if(segments > 0) {
start_x = origin_x + Math.cos(start_ang / 180 * Math.PI) * radius;
start_y = origin_y + Math.sin(-start_ang / 180 * Math.PI) * radius;
this.lineTo(start_x, start_y);
for (var i = 0; i < segments; i++) {
ang += theta;
half_ang = ang - (theta / 2);
end_x = origin_x + Math.cos(ang) * radius;
end_y = origin_y + Math.sin(ang) * radius;
ctrl_x = origin_x + Math.cos(half_ang) * (radius / Math.cos(theta/2));
ctrl_y = origin_y + Math.sin(half_ang) * (radius / Math.cos(theta/2));
this.quadraticCurveTo(ctrl_x, ctrl_y, end_x, end_y);
}
//finish with the line to origin and fill
this.lineTo(origin_x, origin_y);
this.linewidth=2;
this.strokeStyle=0x000000;
this.stroke();
this.fillStyle=slice_clr;
this.fill();
}
]]>
</method>
</class>

Here is a test lzx:


<canvas bgcolor="#FFFFFF" width="600" height="400">
<include href="lgapi/lgapi_pie.lzx"/>
<view>
<lgapi_pie id="test">
<method event="oninit" >
test.draw_pie(200,200,75,75,20,5);
//test.draw_slice(200,200,330,30,100,0x0000ff);
</method>
</lgapi_pie>
</view>
</canvas>


Please respond to this post (or email mail me through the board) if you are interested in this...

Thanks....and thanks to the pros for including the Drawing API...

Bryan
04-06-2005, 09:12 AM
I'd love to help out where I can (not a huge amount of time)- a little scripting, a little design and testing. Having a charting component using the drawing api in Laszlo would be extremely helpful.

For a little inspiration you might want to check out the Blinex Charting Component > products > charting component (no direct link available as it's a Flash site).

http://blinex.com/

mondomjm
04-08-2005, 05:02 PM
hmm....48 reads, and one reply. perhaps I misjudged the necessity for a graphing class...oh well, here is where I am at, and I could use some help, because I have hit a wall of sorts.

current code that works (bound to an mxl dataset)
test script:
<canvas bgcolor="#FFFFFF" width="600" height="400">

<include href="lgapi/lgapi_pieold.lzx"/>
<dataset name="gtest_data" src="gtest_data.xml" request="true"/>

<view name="piecht">
<lgapi_pie id="test" start_ang="0" incl_ang="0" slice_clr="-2">
<view name="Datarow" datapath="gtest_data:/graph_data[1]/row">
<method event="ondata" >
test.setAttribute('slice_clr', test.getAttribute('slice_clr') + 1);
test.setAttribute('start_ang', test.getAttribute('start_ang') + test.getAttribute('incl_ang'));
test.setAttribute('incl_ang',this.datapath.xpathQu ery('gdata/text()') * 3.6);
test.draw_slice(200,200,test.getAttribute('start_a ng'),test.getAttribute('incl_ang'),100,test.getAtt ribute('slice_clr'));
debug.write(test.getAttribute('slice_clr'));
</method>
</view>
</lgapi_pie>
</view>

</canvas>

class file:
<class name="lgapi_pie" extends="drawview">
<attribute name="slice_clr" value="-2"/>
<attribute name="start_ang" value="0"/>
<attribute name="incl_ang" value="0"/>

<method name="draw_slice" args="origin_x,origin_y,start_ang,incl_ang,radius,slice_ clr">
<![CDATA[
//declare internal variables
var seg_ang, theta, ang, half_ang, incl_ang, segments
var start_x, start_y, end_x, end_y, ctrl_x, ctrl_y
var slice_clr_array = new Array("0x0000FF","0x00FF00","0xFF0000")

//move to origin point
this.beginPath();
this.moveTo(origin_x, origin_y);

//limit arc to a full circle
if(Math.abs(incl_ang) > 360) {
incl_ang = 360;
}

//divide into a clean number of segments for good arc quality
segments = Math.ceil(Math.abs(incl_ang) / 45);
seg_ang = incl_ang / segments;

//convert degrees to radians
theta = -(seg_ang / 180) * Math.PI;
ang = -(start_ang / 180) * Math.PI;

//draw the arc segments (already!)
if(segments > 0) {
start_x = origin_x + Math.cos(start_ang / 180 * Math.PI) * radius;
start_y = origin_y + Math.sin(-start_ang / 180 * Math.PI) * radius;
this.lineTo(start_x, start_y);
for (var i = 0; i < segments; i++) {
ang += theta;
half_ang = ang - (theta / 2);
end_x = origin_x + Math.cos(ang) * radius;
end_y = origin_y + Math.sin(ang) * radius;
ctrl_x = origin_x + Math.cos(half_ang) * (radius / Math.cos(theta/2));
ctrl_y = origin_y + Math.sin(half_ang) * (radius / Math.cos(theta/2));
this.quadraticCurveTo(ctrl_x, ctrl_y, end_x, end_y);
}
//finish with the line to origin and fill
this.lineTo(origin_x, origin_y);
this.linewidth=2;
this.strokeStyle=0x000000;
this.stroke();
this.fillStyle=slice_clr_array[slice_clr];
this.fill();
}
]]>
</method>
</class>

ok...here is the problem. I want to wrap this all up into one class that can be passed the datasorce name as an argument. Here is what I have:

Test Script:
<canvas bgcolor="#FFFFFF" width="600" height="400">

<include href="lgapi/lgapi_pie.lzx"/>
<view>
<lgapi_pie id="test"/>
</view>

</canvas>

class file:
<class name="lgapi_pie" extends="drawview">
<attribute name="slice_clr" value="-2"/>
<attribute name="start_ang" value="0"/>
<attribute name="incl_ang" value="0"/>

<method event="onconstruct">
var g_dset = new LzDataSet({name: 'graph_data',src: 'graphs/gtest_data.xml',request:'true'});
g_dset.doRequest();
</method>

<method name="draw_slice" args="origin_x,origin_y,start_ang,incl_ang,radius,slice_ clr">
<![CDATA[
//declare internal variables
var seg_ang, theta, ang, half_ang, incl_ang, segments
var start_x, start_y, end_x, end_y, ctrl_x, ctrl_y
var slice_clr_array = new Array("0x0000FF","0x00FF00","0xFF0000")

//move to origin point
this.beginPath();
this.moveTo(origin_x, origin_y);

//limit arc to a full circle
if(Math.abs(incl_ang) > 360) {
incl_ang = 360;
}

//divide into a clean number of segments for good arc quality
segments = Math.ceil(Math.abs(incl_ang) / 45);
seg_ang = incl_ang / segments;

//convert degrees to radians
theta = -(seg_ang / 180) * Math.PI;
ang = -(start_ang / 180) * Math.PI;

//draw the arc segments (already!)
if(segments > 0) {
start_x = origin_x + Math.cos(start_ang / 180 * Math.PI) * radius;
start_y = origin_y + Math.sin(-start_ang / 180 * Math.PI) * radius;
this.lineTo(start_x, start_y);
for (var i = 0; i < segments; i++) {
ang += theta;
half_ang = ang - (theta / 2);
end_x = origin_x + Math.cos(ang) * radius;
end_y = origin_y + Math.sin(ang) * radius;
ctrl_x = origin_x + Math.cos(half_ang) * (radius / Math.cos(theta/2));
ctrl_y = origin_y + Math.sin(half_ang) * (radius / Math.cos(theta/2));
this.quadraticCurveTo(ctrl_x, ctrl_y, end_x, end_y);
}
//finish with the line to origin and fill
this.lineTo(origin_x, origin_y);
this.linewidth=2;
this.strokeStyle=0x000000;
this.stroke();
this.fillStyle=slice_clr_array[slice_clr];
this.fill();
}
]]>
</method>

<view name="Datarow" datapath="graph_data:/graph_data[1]/row">
<method event="ondata" >
this.setAttribute('slice_clr', this.getAttribute('slice_clr') + 1);
this.setAttribute('start_ang', this.getAttribute('start_ang') + this.getAttribute('incl_ang'));
this.setAttribute('incl_ang',this.datapath.xpathQu ery('gdata/text()') * 3.6);
this.draw_slice(200,200,this.getAttribute('start_a ng'),this.getAttribute('incl_ang'),100,this.getAtt ribute('slice_clr'));
debug.write(test.getAttribute('slice_clr'));
</method>
</view>



</class>

this all works, except all I get is "couldn't find dataset for graph_data:/graph_data[1]/row
bad url: url is empty or null" from the debug window. Bear in mind, I am a DB programmer, not a JS programmer, and I am a noob to laszlo, so I am probably missing something obvious....any ideas?

Bryan
04-08-2005, 05:30 PM
FYI - just noticed this the other day.

http://openlaszlo.org/wiki/Charting_Components_API

mondomjm
04-08-2005, 08:35 PM
thanks bryan...i would have never found that (I am still really new here.)