Tuesday, July 29, 2008

A quick look at Silverlight with IronRuby

This post presents a little Silverlight program written using IronRuby that exercises features such as graphics, dynamic language interaction and style separation.

This is the second of series of posts that explore features across different platforms such as JavaFX Script or Flex. The first post can be found here.

Code is this post is based on Silverlight 2 Beta 2.

The example

As described in the previous post:


A little program that calculates and plots a polynomial that touches four points selected by the user. The user can move any of the given points in the screen and the polynomial will be recalculated and replotted while moving it.


The Neville's algorithm is used in order to find the polynomial that touches all the given points.

Dynamic languages and Silverlight

The Silverlight web site provides documentation and examples to get you started using dynamic languages with Silverlight also the Silverlight Dynamic Languages SDK provides examples and useful code templates.

However, most of the documentation that talks about specific features of Silverlight focused in working with C#.

One of the thing that I wanted to explore was to use the data binding feature, however I didn't find a way to use it in conjunction with IronRuby classes.

John Lam's blog is a good place to find specific samples for working with IronRuby and Silverlight.

One of the benefits of working with dynamic languages and Silveright is that it's very easy to just start trying examples and experiment with it. You only need the Silverlight 2 SDK beta 2 and any text editor (no Visual Studio is required). The Chiron local web server is a very useful tool that let's you execute the Silverlight program and let's you see the changes just by just refreshing the page.


The program

The final program looks like this:



The running version of this program can be found here.

Polynomial class

The following program shows the implementation of the Polynomial class.

class Polynomial
def initialize(coefficients)
@coefficients = coefficients
end
def evaluate(x)
i = @coefficients.length - 1
result = 0.0
while (i >= 0)
result += (x**i)*@coefficients[i]
i = i - 1
end
return result
end

def coefficients
@coefficients
end


def add(p)

if @coefficients.length > p.coefficients.length
c1 = @coefficients
c2 = p.coefficients
else
c1 = p.coefficients
c2 = @coefficients
end
length1 = c1.length
length2 = c2.length

result = (0..length1-1).map do |i|
if i < length2
c1[i]+c2[i]
else
c1[i]
end
end

return Polynomial.new(result)
end

def add_destructive(p)

if @coefficients.length > p.coefficients.length
c1 = @coefficients
c2 = p.coefficients
else
c1 = p.coefficients
c2 = @coefficients
end

length2 = c2.length

(0..length2-1).each do |i|
c1[i] = c1[i]+c2[i]
end

return Polynomial.new(c1)
end


def multiply(p)
results = Array.new(p.coefficients.length+@coefficients.length - 1,0.0)
current_exponent = 0
p.coefficients.each do |coefficient|
if coefficient.abs > 0.000001
current_self_exponent = 0
@coefficients.each do |self_coefficient|
results[current_self_exponent+current_exponent] = (self_coefficient*coefficient) + results[current_self_exponent+current_exponent]
current_self_exponent = current_self_exponent + 1
end
end
current_exponent = current_exponent + 1
end

Polynomial.new(results)
end
end


This class contains a couple of methods for polynomial evaluation, addition and multiplication. These operations are required to implement the algorithm.

Algorithm

The following code shows the implementation of Neville's algorithm.


class NevilleCalculator
def initialize(converter)
@converter = converter
end
def create_polynomial(points)
number_of_points = points.length
matrix = (0..number_of_points-1).map {|i|(0..i).map{|j| Polynomial.new([])}}
(0..number_of_points-1).each do |i|
matrix[i][0] = Polynomial.new([@converter.convert_to_plane_y(points[i].y)])
end
xs = points.map {|point| @converter.convert_to_plane_x(point.x) }

(1..number_of_points-1).each do |i|
(1..i).each do |j|
q = xs[i]-xs[i-j]
p1 = Polynomial.new [-1.0*xs[i-j]/q,1.0/q]
p2 = Polynomial.new [xs[i]/q,-1.0/q]
matrix[i][j] = p1.multiply(matrix[i][j-1]).add_destructive(p2.multiply(matrix[i-1][j-1]))
end
end

return matrix[number_of_points-1][number_of_points-1]
end
end


The create_polynomial method receives an array of user selected points in the screen. The converter object (described below) converts coordinates from the screen to the Cartesian coordinate system.

Screen to plane coordinate conversion

In order to convert the coordinates the Converter class was used.


class PlaneToScreenConverter
def initialize(screen_max_x,screen_min_x,screen_max_y,screen_min_y,
plane_max_x,plane_min_x,plane_max_y,plane_min_y)
...
@m_to_plane_x = ((@plane_max_x - @plane_min_x)/(@screen_max_x - @screen_min_x))
@b_to_plane_x = @plane_min_x- @screen_min_x*@m_to_plane_x

@m_to_plane_y = ((@plane_max_y - @plane_min_y)/(@screen_max_y - @screen_min_y))
@b_to_plane_y = @plane_min_y - @screen_min_y *@m_to_plane_y

end

...

def convert_to_screen_x(x)
m = ((@screen_max_x - @screen_min_x)/(@plane_max_x - @plane_min_x))
b = @screen_min_x - @plane_min_x*m
return (m*x + b)
end
def convert_to_screen_y(y)
m = ((@screen_max_y - @screen_min_y)/(@plane_max_y - @plane_min_y))
b = @screen_min_y - @plane_min_y*m
return (m*y + b)
end
def convert_to_plane_x(x)
return (@m_to_plane_x*x + @b_to_plane_x)

end
def convert_to_plane_y(y)
return (@m_to_plane_y*y + @b_to_plane_y)
end
end


Also a class to represent a user selected point was created:


class ScreenPoint
def x
@x
end

def x=(v)
@x = v
end

def y
@y
end

def y=(v)
@y = v
end
def initialize(x,y)
@x = x
@y = y
end
end


For some reason I couldn't use attr_accessor with the version of IronRuby shipped with the Silverlight SDK 2 Beta 2.

Plotting the polynomial

In order to plot the polynomial a Polyline object will be used. The FunctionPlotter class is used to create the list of points to create the list of Polyline points.


class FunctionPlotter
def initialize(converter,steps,polynomial)
@converter = converter
@steps = steps
@polynomial = polynomial
@points = []
end

def points
@points
end
def polynomial
@polynomial
end
def recalculate_points

increment = ((@converter.plane_max_x - @converter.plane_min_x)/@steps).abs
current_x = [@converter.plane_max_x,@converter.plane_min_x].min
@points = (0..@steps - 1).map do
x = current_x
y = @polynomial.evaluate(x)
current_x = current_x+increment
[x,y]
end
end
end


Dragging elements

Since the user can modify the points across the screen, a way to drag shapes is required. The Implementing Drag-and-Drop Functionality with Mouse Events and Capture (Silverlight 2) article provides a complete guide on how add dragging capabilities to a Silverlight shape.

Here's the implementation of a helper class that implements this functionality.


class ElementDraggingTracker
def is_captured
return @captured
end
def initialize
@captured = false
@last_position = ScreenPoint.new(0,0)
end

def handle_mouse_down(sender,e)
current_pos = e.GetPosition(nil)
@last_position.x = current_pos.X
@last_position.y = current_pos.Y
@captured = true
sender.CaptureMouse()
end
def handle_mouse_move(sender,e)
if @captured
new_position = e.GetPosition(nil)
deltav = new_position.Y - @last_position.y
deltah = new_position.X - @last_position.x
sender.Data.Center = Point.new(sender.Data.Center.X+deltah,sender.Data.Center.Y+deltav)
@last_position.x = new_position.X
@last_position.y = new_position.Y
end
end
def handle_mouse_up(sender,e)
sender.ReleaseMouseCapture()
@captured = false
end
end


This implementation was made to work only with Path objects that have an EllipseGeometry as the Data. That's why the Data and Center properties are used. This code could be improved to be independent of the object being dragged.

Presenting the polynomial

In order to show a textual representation of the calculated polynomial, a sequence of TextBlock objects was used. This was necessary since I wanted to show the exponent of each term of the polynomial. The only way I found to do that was to add an horizontal StackPanel and add TextBlock for each term and use a TranslateTransform for the exponents. Here's the code:


def create_polynomial_text_elements(p)
first_time = false
panel = polyTextPanel

panel.Children.Clear
length = p.coefficients.length
(0..length-1).each do |pos|
i = (length - 1) - pos
c = p.coefficients[i]
exponent = i

if c.abs > 0.000001 then
text = ""
text = text + ("%.4g" % c)
text = text + "x" if exponent > 0

b = create_text_block text,"FormulaTextStyle"
panel.Children.Add b
if exponent > 1
b = create_text_block exponent.to_s,"FormulaExponentTextStyle"
t = TranslateTransform.new
t.Y = -2
b.RenderTransform = t
panel.Children.Add b
end

if !first_time and exponent != 0
b = create_text_block " + " ,"FormulaTextStyle"
panel.Children.Add b
else
first_time = false
end
end

end
end
end


The main program

The XAML for this program is very simple:


<UserControl x:Class="System.Windows.Controls.UserControl"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="ucontrol">
<UserControl.Resources>

...

</UserControl.Resources>
<StackPanel>
<StackPanel x:Name="polyTextPanel" Orientation="Horizontal">
</StackPanel>
<Canvas x:Name="theCanvas" Width="480" Height="480" Background="White">
<Polyline x:Name="polynomialPolyline" Style="{StaticResource PolylineStyle}"/>
<Line Style="{StaticResource AxisLineStyle}" X1="0" Y1="240" X2="480" Y2="240"/>
<Line Style="{StaticResource AxisLineStyle}" X1="240" Y1="0" X2="240" Y2="480"/>
</Canvas>
</StackPanel>
</UserControl>


The App class implements the entry point of the program:


require "Silverlight"
require 'screen_point'
require 'element_dragging_tracker'
require 'plane_to_screen_converter'
require 'neville_calculator'
require 'function_plotter'
require 'polynomial'

include System::Windows
include System::Windows::Controls
include System::Windows::Media
include System::Windows::Shapes



class App < SilverlightApplication
use_xaml

...

end

App.new


The SilverlightApplication class and the referenced Silverlight.rb file are provided as part of a template for IronRuby/Silverlight project from the Silvelight Dynamic SDK.

The initialize method of the App class looks like this:


def initialize
(0..(480/24)).each do |i|
theCanvas.Children.Add create_line(i*24,0,i*24,480,"GridLineStyle")
end
(0..(480/24)).each do |i|
theCanvas.Children.Add create_line(0,i*24,480,i*24,"GridLineStyle")
end

@points = [ScreenPoint.new(100,300),
ScreenPoint.new(200,400),
ScreenPoint.new(350,300),
ScreenPoint.new(400,200)
]

@converter = PlaneToScreenConverter.new(480.0,0.0,0.0,480.0,20.0,-20.0,20.0,-20.0)

@tracker = ElementDraggingTracker.new

canvas = theCanvas

@points.each do |p|
c = create_circle p.x,p.y,10,"ControlPointStyle"
t = create_text_block "","PointTextStyle"
adjust_point_text_block(p,t)

c.mouse_left_button_down do |sender,args|
@tracker.handle_mouse_down(sender,args)
end
c.mouse_left_button_up do |sender,args|
@tracker.handle_mouse_up(sender,args)
p.x = sender.Data.Center.X
p.y = sender.Data.Center.Y
adjust_point_text_block(p,t)
replot
end
c.mouse_move do |sender,args|
@tracker.handle_mouse_move(sender,args)
if @tracker.is_captured
p.x = sender.Data.Center.X
p.y = sender.Data.Center.Y
adjust_point_text_block(p,t)

replot
end
end
canvas.Children.Add c
canvas.Children.Add t
end

replot
end


Two important things happen in this method. First the grid lines are added to the canvas and then the control points are added to the canvas and the dragging event handlers are also associated with each point. The event handlers also take care of synchronizing the update of each point's label and to recalculate and re-plot the polynomial.

The replot method executes the Neville's algorithm and create the new points for the Polyline.


def replot
@calc = NevilleCalculator.new(@converter)

@plotter = FunctionPlotter.new(@converter,200,@calc.create_polynomial(@points))

@plotter.recalculate_points

pc = PointCollection.new
@plotter.points.each do |aP|
pc.Add(Point.new(@converter.convert_to_screen_x(aP[0]),
@converter.convert_to_screen_y(aP[1])))
end

polynomialPolyline.Points = pc
create_polynomial_text_elements @plotter.polynomial
end


Additional methods for creating Silverlight shapes were required:


def create_line(x1,y1,x2,y2,style_key)
p = Path.new
p.Data = LineGeometry.new
p.Data.StartPoint = Point.new x1,y1
p.Data.EndPoint = Point.new x2,y2
p.Style = ucontrol.Resources.Item(style_key.ToString())
return p
end

def create_circle(x1,y1,radius,style_key)
p = Path.new
p.Data = EllipseGeometry.new
p.Data.Center = Point.new x1,y1
p.Data.RadiusX = radius
p.Data.RadiusY = radius
p.Style = ucontrol.Resources.Item(style_key.ToString())
return p
end

def create_text_block(text,style_key)
t = TextBlock.new
t.Text = text
t.Style = ucontrol.Resources.Item(style_key.ToString())
return t
end

def adjust_point_text_block(p,t)
t.Text = "("+("%.3f" % @converter.convert_to_plane_x(p.x)) +
","+("%.3f" % @converter.convert_to_plane_y(p.y)) + ")"
Canvas.SetTop(t,p.y+5)
Canvas.SetLeft(t,p.x-5)
end


Separating the styles

Silverlight style separationprovides a way to separate style definitions for UI elements. For this program, all the style definitions are defined in a separate section of the XAML file.


<UserControl.Resources>
<Style TargetType="TextBlock" x:Key="FormulaTextStyle">
<Setter Property="FontSize" Value="13"/>
</Style>
<Style TargetType="TextBlock" x:Key="FormulaExponentTextStyle">
<Setter Property="FontSize" Value="12"/>
</Style>
<Style TargetType="TextBlock" x:Key="PointTextStyle">
<Setter Property="FontSize" Value="10"/>
</Style>
<Style TargetType="Line" x:Key="AxisLineStyle">
<Setter Property="Stroke" Value="Black" />
<Setter Property="StrokeThickness" Value="3" />
</Style>
<Style TargetType="Polyline" x:Key="PolylineStyle">
<Setter Property="Stroke" Value="Black" />
</Style>
<Style TargetType="Path" x:Key="GridLineStyle">
<Setter Property="Stroke" Value="Cyan" />
</Style>
<Style TargetType="Path" x:Key="ControlPointStyle">
<Setter Property="Fill" Value="Blue" />
</Style>
</UserControl.Resources>


In order to assign styles to elements in IronRuby the following code was used:


p.Style = ucontrol.Resources.Item(style_key.ToString())


Given that style_key is an IronRuby variable with a string. Note that I had to use the ToString method. If the ToString method as not used I received a runtime error saying: ArgumentException: key must be a string.

Deploying the application

The creation of the XAP file to be deployed is VERY simple. The following command creates an app.xap file from a existing app folder.

Chiron.exe /z:app.xap /d:app


The /z parameter adds all the libraries required for executing IronRuby.

Final words

The only issue I see is the lack of documentation for using Silverlight features with scripting languages. The main thing missing is how to use data binding (if possible) which is a very nice feature which I enjoyed using in JavaFX Script for the previous post.

Because of the inclusion of the IronRuby support libraries, the final XAP size was be bigger than I expected (~700K in this case). However it was not as big as the one from the JavaFX example which included all the JavaFX runtime libraries.

For this example I had to take care of the performance. I stared using an almost direct port of the JavaFX script code of the previous post for the algorithm and the polynomial operations which lead to bad performance.

The integration of IronRuby with .NET is very nice, translating examples from C# was very easy, for example with the element dragging code described above. One thing that can be improved is to give more information on where an exception occurred in a program.

Overall the experience of working with IronRuby and Silverlight was very nice. I was very easy to get feedback of code changes just by hitting refresh in the browser.

Code for this post can be found here.

The running version of the example can be found here.

Sunday, July 13, 2008

JavaFX Script Overview

Introduction

In this post I'll show a little program written in JavaFX Script that exercises some of its features such as graphics, data binding and programming language interaction.

This is the first of a series of posts exploring these features across different platforms such as Silverlight or Flex.

The example


A little program that calculates and plots a polynomial that touches four points selected by the user. The user can move any of the given points in the screen and the polynomial will be recalculated and replotted while moving it.

Neville's algorithm

The Neville's algorithm is used to calculate the polynomial that traverses the points selected by the user.



The user will select a series of points (x0,y0),(x1,y1),etc. that will be used as the input for this algorithm given that y0 = f(x0). Each entry of the Q matrix will stored as a Polynomial object, so the final result Qn,n will be the Polynomial to be plotted.

More information on this algorithm can be found in its Wikipedia page, its MathWorld page or a Numerial Analysis book such as the one by Burden & Faires.

JavaFX Script

Although JavaFX Script is a very interesting language, the biggest problem of working with it today its that is still on development, so there're many sites that contain tutorials and code snippets that references APIs or language constructs that are no longer valid. The James Weaver’s JavaFX Blog
and the JavaFX wiki were very useful resources to find out about the latest updates. Also the Converting JavaFX™ Script Programming Language Code from Interpreted to Compiled Syntax guide provides nice information on language changes.

I think this will be solved as soon a the first official release of the JavaFX SDK is published.

For now, the code snippets shown in this post were created using the build from July 11, 2008.

The program

The final program looks like this:



The applet can also be found here.


Implementing Neville's algorithm

In order to obtain the polynomial for the given selected points using Neville's algorithm we required a polynomial class with a couple of operations for multiplication and addition.


class Polynomial {
attribute coefficients : Number[];

function evaluate(x:Number):Number {
var result = 0.0;
for(i in [0..(sizeof coefficients) - 1]) {
result = result + coefficients[i]*Math.pow(x,i);
}
return result;
}

function multiplyByTerm(coefficient:Number,exponent:Integer):Polynomial {
var origExponent = sizeof coefficients;
if (coefficient == 0.0) {
return Polynomial{coefficients:[]}
} else {
return Polynomial {
coefficients: for (i in [0 .. (origExponent + exponent - 1) ])
if (i < exponent) 0
else coefficients[i - exponent]*coefficient
}
}
}

function multiply(p : Polynomial):Polynomial {
var parts = for (i in [0 .. (sizeof coefficients) - 1])
p.multiplyByTerm(coefficients[i],i);

var result = Polynomial{coefficients:[]};
for (part in parts) {
result = result.add(part);
}
return result;
}

function add(p : Polynomial):Polynomial {
var length1 = sizeof coefficients;
var length2 = sizeof p.coefficients;
var result:Number[];

if (length1 == length2) {
result = for(i in [0 .. length1 - 1])
coefficients[i] + p.coefficients[i];
} else {
if (length1 > length2) {
result = for(i in [0 .. length2 - 1])
coefficients[i] + p.coefficients[i];
for (i in [length2 .. length1 -1]) {
insert coefficients[i] into result
}
} else {
result = for(i in [0 .. length1 - 1])
coefficients[i] + p.coefficients[i];
for (i in [length1 .. length2 -1]) {
insert p.coefficients[i] into result
}
}
}
return Polynomial {
coefficients: result
};
}

function toString():String {
var result:java.lang.StringBuilder = new java.lang.StringBuilder();
var firstTime = true;
result.append("<html>");
for (i in [0 .. (sizeof coefficients) - 1]) {
var theIndex = (sizeof coefficients) - 1 - i;
if (Math.abs(coefficients[theIndex]) > 0.000001) {
if (not firstTime) {
result.append(" + ");
}
if (theIndex != 0) {
result.append("{%3.3f coefficients[theIndex]}x");
if (theIndex != 1) {
result.append("<sup>{theIndex}</sup>");
}
} else {
result.append("{%3.3f coefficients[theIndex]}");
}
firstTime = false;
}
}
result.append("</html>");
return result.toString();
}
}


Some elements of the JavaFX Script language that were really useful include the Sequence Comprehensions. Also the embedded expressions in string are an interesting feature.

An element to convert the coordinates between the screen and the Cartesian plane is also required to implement the algorithm.

The following code shows the implementation of this converter.


class PlaneToScreenConverter {
attribute screenMaxX : Number;
attribute screenMaxY : Number;
attribute screenMinX : Number;
attribute screenMinY : Number;
attribute planeMinX : Number;
attribute planeMinY : Number;
attribute planeMaxX : Number;
attribute planeMaxY : Number;


function convertToScreenX(x:Number):Integer {
var m = ((screenMaxX - screenMinX)/(planeMaxX - planeMinX));
var b = screenMinX - planeMinX*m ;
return (m*x + b).intValue();

}
function convertToScreenY(y:Number):Integer {
var m = ((screenMaxY - screenMinY)/(planeMaxY - planeMinY));
var b = screenMinY - planeMinY*m ;
return (m*y + b).intValue();
}
function convertToPlaneX(x:Integer):Number{
var m = ((planeMaxX - planeMinX)/(screenMaxX - screenMinX));
var b = planeMinX- screenMinX*m ;
return (m*x + b).intValue();

}
function convertToPlaneY(y:Integer):Number {
var m = ((planeMaxY - planeMinY)/(screenMaxY - screenMinY));
var b = planeMinY - screenMinY *m ;
return (m*y + b).intValue();
}
}


Finally we can implement the algorithm as follows:


class MRow {
attribute polynomials : Polynomial[];
}

class NevilleCalculator {
attribute converter : PlaneToScreenConverter;

function createPolynomial(points: FunctionPoint[]):Polynomial {
var numberOfPoints = sizeof points;
var matrix = for(i in [0..numberOfPoints - 1])
MRow {
polynomials:(for (j in [0..i])
Polynomial{coefficients:[]})} ;

for(i in [0..numberOfPoints - 1]) {
matrix[i].polynomials[0] =
Polynomial {
coefficients:[ converter.convertToPlaneY(points[i].y) ]
};
}

var xs = for(i in [0..numberOfPoints - 1]) converter.convertToPlaneX(points[i].x);
var ys = for(i in [0..numberOfPoints - 1]) converter.convertToPlaneY(points[i].y);

for(i in [1..numberOfPoints-1]) {
for(j in [1..i]) {
var q = xs[i]-xs[i-j];
var p1 = Polynomial { coefficients: [-1.0*xs[i-j]/q,1.0/q] };
var p2 = Polynomial { coefficients: [xs[i]/q,-1.0/q] };

matrix[i].polynomials[j] =
p1.multiply(matrix[i].polynomials[j-1]).add(p2.multiply(matrix[i-1].polynomials[j-1]));
}
}
return matrix[numberOfPoints-1].polynomials[numberOfPoints-1];
}
}


The MRow class was created because I was unable to create a sequence of sequences.


Plotting the function

In order to generate screen points for a given polynomial, a FunctionPlotter class was created as follows:


class FunctionPlotter {
attribute converter : PlaneToScreenConverter;
attribute steps = 250;
attribute initialX = -1.0;
attribute finalX = 1.0;

attribute poly = Polynomial {
coefficients:[0,0,1]
}
on replace {
points = getPolyPoints();
}

attribute points = getPolyPoints();

function getPolyPoints():Number[] {
var m = (finalX - initialX)/(250 - 0);
var b = finalX - m*250;
var increment = Math.abs(converter.planeMaxX - converter.planeMinX) / steps;
var currentX = Math.min(converter.planeMaxX , converter.planeMinX);
var i = 0;
var result = for (x in [0..steps*2 - 1]) 1.0;
while (i < steps*2) {
var currentY = poly.evaluate(currentX);
result[i] = converter.convertToScreenX(currentX)*1.0;
result[i+1] = converter.convertToScreenY(currentY)*1.0;

currentX = currentX + increment;
i = i + 2;
}
return result;
}
}


Connecting all the elements


Now that we have all the pieces of the program we can connect them using data binding.

Data binding provides a very nice way for expressing connections between parts of the UI and the model of the application.

For our program we want to allow the user to move around the control points (the blue circles) and have the polynomial recalculated and replotted while moving them. In order to accomplish this we need to create the following databinding connections:



The code that represents the "model" section of the diagram is the following:


var userPoints =
Domain {
points: [
FunctionPoint{
x: 100
y: 300 },
FunctionPoint{
x: 200
y: 400 },
FunctionPoint{
x: 350
y: 300 },
FunctionPoint{
x: 400
y: 200 }

]
}

var converter = PlaneToScreenConverter {
screenMaxX: 480
screenMaxY: 0
screenMinX: 0
screenMinY: 480
planeMinX: -20
planeMinY: -20
planeMaxX: 20
planeMaxY: 20
}

var nevilleCalculator = NevilleCalculator {
converter: converter
}


var plotter = FunctionPlotter {
poly: bind nevilleCalculator.createPolynomial(
for(p in userPoints.points)
FunctionPoint{x:p.x,y:p.y})
converter: converter;
}


Notice that in the bind expression of the poly property of the FunctionPlotter we use a for comprehension instead of passing the complete collection. This is required to allow the update of the polynomial when moving just one point.

The UI part of the program looks like this:


Application {
stage:
Stage {
content:
ComponentView {
component:
BorderPanel {
background: Color.WHITE
top:
Label {
text: bind plotter.poly.toString()
}

center:
Canvas {
content: [
...
]
}
}
}
}
}


Notice that the label is also bound to the polynomial of the function plotter.

The plotted function line and the control points are located inside the canvas with the following code:


Polyline {
stroke:Color.BLACK
points: bind plotter.points
},
Group {
content: bind for (p in userPoints.points)
Group {
content: [
Circle {
centerX: p.x
centerY: p.y
radius:10
fill: Color.BLUE

var dragStartX:Number;
var dragStartY:Number;
onMousePressed:
function(event:MouseEvent) {
dragStartX = p.x;
dragStartY = p.y;
}
onMouseDragged:
function(event:MouseEvent):Void {
p.x = dragStartX + event.getDragX();
p.y = dragStartY + event.getDragY();
}
},
Text { x: p.x+4,y:p.y+10,
content: bind "({%3.2f converter.convertToPlaneX(p.x)},{%3.2f converter.convertToPlaneY(p.y)})" }
]}
}


The mouse pressed and mouse dragged handler are used to allow the user to move the control points.

Also inside the canvas is the code for the axis and the grid.


Group {
content: for(x in [0 .. Math.max(converter.screenMaxX,converter.screenMinX)/24])
Line {
stroke:Color.CYAN
startX: x*24, startY:converter.screenMinY
endX:x*24 , endY:converter.screenMaxY
}
},
Group {
content: for(y in [0 .. Math.max(converter.screenMinY,converter.screenMaxY)/24])
Line {
stroke:Color.CYAN
startX:converter.screenMinX,startY:y*24
endX:converter.screenMaxX, endY:y*24
}
},
Line {
startX: Math.abs((converter.screenMaxX - converter.screenMinX)/2)
startY: converter.screenMinY
endX: Math.abs((converter.screenMaxX - converter.screenMinX)/2)
endY: converter.screenMaxY
stroke:Color.BLACK
strokeWidth:2
},
Line {
startX: converter.screenMinX
startY: Math.abs((converter.screenMaxY - converter.screenMinY)/2)
endX: converter.screenMaxX
endY: Math.abs((converter.screenMaxY - converter.screenMinY)/2)
stroke:Color.BLACK
strokeWidth:2
},




Final words

JavaFX Script provides a nice programming language and powerful graphics and UI elements. One of the most interesting features is data binding. It will be very interesting to see how this feature is implemented in the other platforms.

The biggest problem right now is that JavaFX Script is on development so it's difficult to get documentation of the last changes. Also another problem is that right now in order to create an applet all the Jars of the distribution must be included adding a couple of MB to the size of final download.


Code for this post can be found here.

The applet can also be found here.