Posts by author:

Jozef Chka

Pixel Bender Explorer

by Jozef Chúťka on January 19, 2010

Pixel Bender Explorer is simple application that lets you dynamicly load .pbj files, apply it on image and edit shader values. Application uses two classes. ShaderLoader class loads external .pbj file and casts loaded binary data into Shader object. ShaderExplorer class inspects created Shader object and generates editable form based on Shader parameters. You can load your own external .pbj files (crossdomain.xml required near .pbj file) or feel free to download and use whole .swf application localy.

Application inspired by Enumerating Pixel Bender filter parameters and metadata dynamically in Flash and Introduction to Pixel Bender: Part 2 articles.

Pixel Bender Explorer application

sk.yoz.shader.ShaderLoader

package sk.yoz.shader
{
    import flash.display.Shader;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.filters.ShaderFilter;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;

    public class ShaderLoader extends EventDispatcher
    {
        protected var loader:URLLoader = new URLLoader();
        protected var _filters:Array = [];
        protected var _shader:Shader;
        protected var _shaderFilter:ShaderFilter;

        public function ShaderLoader()
        {
            super();

            loader.dataFormat = URLLoaderDataFormat.BINARY;
            loader.addEventListener(Event.COMPLETE, loadCompleteHandler);
            loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        }

        [Bindable(event="filtersChanged")]
        public function get filters():Array
        {
            return _filters;
        }

        protected function set filters(value:Array):void
        {
            _filters = value;
            dispatchEvent(new Event("filtersChanged"));
        }

        [Bindable(event="shaderChanged")]
        public function get shader():Shader
        {
            return _shader;
        }

        protected function set shader(value:Shader):void
        {
            _shader = value;
            dispatchEvent(new Event("shaderChanged"));
        }

        [Bindable(event="shaderFilterChanged")]
        public function get shaderFilter():ShaderFilter
        {
            return _shaderFilter;
        }

        protected function set shaderFilter(value:ShaderFilter):void
        {
            _shaderFilter = value;
            dispatchEvent(new Event("shaderFilterChanged"));
        }

        public function load(url:String):void
        {
            loader.load(new URLRequest(url));
        }

        protected function loadCompleteHandler(event:Event):void
        {
            try
            {
                protected::shader = new Shader(event.target.data);
                protected::shaderFilter = new ShaderFilter(public::shader);
                protected::filters = [public::shaderFilter];
            }
            catch(error:Error)
            {
                protected::shader = null;
                protected::shaderFilter = null;
                protected::filters = [];
            }
        }

        protected function ioErrorHandler(event:IOErrorEvent):void
        {
        }
    }
}

sk.yoz.shader.ShaderExplorer

package sk.yoz.shader
{
    import flash.display.Shader;
    import flash.display.ShaderInput;
    import flash.display.ShaderParameter;
    import flash.events.Event;
    import flash.filters.ShaderFilter;

    import mx.containers.VBox;
    import mx.controls.HRule;
    import mx.controls.HSlider;
    import mx.events.SliderEvent;
    import mx.controls.Spacer;
    import mx.controls.Text;

    public class ShaderExplorer extends VBox
    {
        protected var _shader:Shader;

        public function ShaderExplorer()
        {
            super();
        }

        public function get shader():Shader
        {
            return _shader;
        }

        public function set shader(value:Shader):void
        {
            _shader = value;
            createControls();
            dispatchEvent(new Event("shaderFiltersChanged"));
        }

        [Bindable(event="shaderFiltersChanged")]
        public function get shaderFilters():Array
        {
            if(!shader)
                return [];
            return [new ShaderFilter(shader)];
        }

        protected function createControls():void
        {
            var spacer:Spacer;

            removeAllChildren();
            createDescriptors();
            spacer = new Spacer();
            spacer.height = 20;
            addChild(spacer);
            createInputs();
            spacer = new Spacer();
            spacer.height = 20;
            addChild(spacer);
            createParameters();
        }

        protected function createDescriptors():void
        {
            var param:String;
            for(param in shader.data)
                if(shader.data[param] is String)
                    add_text(param + ": " + shader.data[param]);
        }

        protected function createInputs():void
        {
            var param:String;
            for(param in shader.data)
                if(shader.data[param] is ShaderInput)
                    add_text(param + ": " + shader.data[param]);
        }

        protected function sliderChangeHandler(event:SliderEvent):void
        {
            if(!event.target.name)
                return;
            var name:String = String(event.target.name).replace(/_[0-9]+$/, '');
            var shaderParameter:ShaderParameter = shader.data[name];
            var values:Array = [];
            var i:uint = 0;
            while(getChildByName(name + "_" + i))
                values.push(HSlider(getChildByName(name + "_" + i++)).value);
            shaderParameter.value = values;
            dispatchEvent(new Event("shaderFiltersChanged"));
        }

        protected function createParameters():void
        {
            var param:String;
            var shaderParameter:ShaderParameter;
            for(param in shader.data)
            {
                if(!(shader.data[param] is ShaderParameter))
                    continue;

                shaderParameter = shader.data[param] as ShaderParameter;
                try
                {
                    this["add_" + shaderParameter.type].apply(null, [shaderParameter]);
                }
                catch(error:Error)
                {
                    add_text("unknown type (" + shaderParameter.type + ")");
                }
            }
        }

        protected function add_text(text:String):void
        {
            var txt:Text = new Text();
            txt.text = text;
            txt.percentWidth = 100;
            addChild(txt);
        }

        protected function add_slider(name:String, value:Number, minimum:Number,
            maximum:Number, snapInterval:Number = 0):void
        {
            var slider:HSlider = new HSlider();
            slider.name = name;
            slider.minimum = minimum;
            slider.maximum = maximum;
            slider.value = value;
            slider.snapInterval = snapInterval;
            slider.percentWidth = 100;
            slider.addEventListener(SliderEvent.CHANGE, sliderChangeHandler);
            addChild(slider);
        }

        protected function add_float(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
        }

        protected function add_float2(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1]);
        }

        protected function add_float3(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1]);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2]);
        }

        protected function add_float4(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1]);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2]);
            add_slider(p.name + "_3", p.defaultValue[3], p.minValue[3], p.maxValue[3]);
        }

        protected function add_int(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
        }

        protected function add_int2(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1] ,1);
        }

        protected function add_int3(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1] ,1);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2] ,1);
        }

        protected function add_int4(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1] ,1);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2] ,1);
            add_slider(p.name + "_3", p.defaultValue[3], p.minValue[3], p.maxValue[3], 1);
        }

        // TODO, matrix, matrix4x4...
    }
}

Pixel Bender Explorer application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical" xmlns:shader="sk.yoz.shader.*"
    creationComplete="shadersService.send();" width="620" height="650"
    paddingTop="10" paddingRight="10" paddingBottom="10" paddingLeft="10"
    backgroundGradientColors="{[0xffffff, 0xffffff]}">
<mx:Script>
<![CDATA[
    import sk.yoz.shader.ShaderLoader;

    [Embed(source="../assets/img.jpg")]
    public static const IMAGE_CLASS:Class;

    [Bindable]
    private var loader:ShaderLoader = new ShaderLoader();
]]>
</mx:Script>
<mx:HTTPService id="shadersService" url="shaders.xml" />
<mx:HBox width="100%">
    <mx:CheckBox id="useFilter" selected="true"/>
    <mx:ComboBox id="combo" editable="true"
        close="{loader.load(String(combo.value))}"
        enter="{loader.load(String(combo.value))}"
        dataProvider="{shadersService.lastResult.data.shader}" width="510"/>
    <mx:Button label="Load" click="{loader.load(String(combo.value))}"/>
</mx:HBox>
<mx:HBox width="100%" height="100%">
    <mx:Image source="{IMAGE_CLASS}"
        filters="{useFilter.selected ? explorer.shaderFilters : []}"/>
    <mx:Canvas width="100%" height="100%" verticalScrollPolicy="auto">
        <shader:ShaderExplorer id="explorer" shader="{loader.shader}"
            height="100%" />
    </mx:Canvas>
</mx:HBox>
</mx:Application>

Embed Bitmap text into Flex

by Jozef Chúťka on January 12, 2010

pixelFont

This article helps you embed pixel sharp fonts (without antialias) into your flex apps. As far as I know you wouldn’t be able to do this just by direct embedding local font file into your flex application, but you gonna need Flash CS4 authoring tool. There are two ways you can make Bitmap text work in flex. You can use pure css method or actionscript method. The downside of both methods is, you have to define and export from flash exact font size you are going to use.

1. Embedding pixel fonts using CSS Style

  1. create new .fla file
  2. create Dynamic Text field:
    1. set font (Arial, Regular)
    2. set Anti-alias value to Bitmap text [no anti-alias]
    3. important: when embedding Bitmap text, only selected font size is created, select 12pt
    4. set characters to embed
  3. export .fla into .swf
  4. create flex project and insert the following code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Style>
    @font-face
    {
        src:url("font.swf");
        fontFamily: "Arial_12pt_st";
        fontAntialiasType: "normal";
    }

    Text
    {
        fontFamily: "Arial_12pt_st";
        fontSize: 12;
    }
</mx:Style>
<mx:Text text="Abc Def"/>
</mx:Application>

Notice font family name Arial_12pt_st, this is how flash exports Bitmap text at specific font size and font style. This pure css method seems to be cleaner and faster to me, it would even be easier to use it with large scale projects just by separating application and style files.

2. Embedding pixel fonts using ActionsScript

Method is described by Grant Skinner in Bitmap Fonts in Flex (via Flash) article:

  1. create new .fla file
  2. create Dynamic Text field:
    1. set font (Arial, Regular)
    2. set Anti-alias value to Bitmap text [no anti-alias]
    3. important: when embedding Bitmap text, only selected font size is created, select 12pt
    4. set characters to embed
  3. add text field into exportable MovieClip (select text field -> F8 -> MovieClip -> Linkage Class “font“)
  4. export .fla into .swf
  5. create flex project and insert the following code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
    [Embed(source="font.swf", symbol="font")]
    private var FontSourceClass:Class;
]]>
</mx:Script>
<mx:Text text="Abc Def"
    fontFamily="Arial_12pt_st" fontSize="12" fontAntiAliasType="normal"/>
</mx:Application>

Notice the step when you add text field into MovieClip and linkage, than in Application you embed directly the symbol!

Lets assume you are working on a bigger project and all-code-in-one-palce is not fine solution. I would suggest to separate logic and style this way:

Application file (notice important import Fonts + var fonts:Fonts definition):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
<mx:Script>
<![CDATA[
    import Fonts;
    private var fonts:Fonts;
]]>
</mx:Script>
<mx:Style source="style.css" />
<mx:Text text="Abc Def"/>
</mx:Application>

Fonts.as working as embed font database:

package
{
    import flash.text.Font;

    public class Fonts extends Object
    {
        [Embed(source="fonts.swf", symbol="Arial_12pt_st")]
        public static const Arial_12pt_st:Class;

        [Embed(source="fonts.swf", symbol="Verdana_11pt_st")]
        public static const Verdana_11pt_st:Class;

        [Embed(source="fonts.swf", symbol="Verdana_12pt_st")]
        public static const Verdana_12pt_st:Class;

        [Embed(source="fonts.swf", symbol="Verdana")]
        public static const Verdana:Class;

        public function Fonts()
        {
            super();
        }

        public static function traceAll():void
        {
            var fontList:Array = Font.enumerateFonts(false);
            for(var i:uint = 0; i < fontList.length; i++)
                trace("font: " + fontList[i].fontName);
        }
    }
}

And style.css file should look like this:

Text
{
   fontFamily: "Verdana_11pt_st";
   fontSize: 11;
   fontAntialiasType: "normal";
}

GeometryMath 3 points in line test

by Jozef Chúťka on January 11, 2010

inLine

GeometryMath.isLine() static function tests 3 Points and returns true if these points are in line. Optional 4th parameter defines if you require point2 to be in the middle between 1 and 3. Function is not based on vector algorithm, but on triangle equation. This simple function was developed in order to optimize number of points necessary to draw some paths. To draw a line, you do not need all the points, you can omit all the middle points that are on the same line and the result looks the same – optimizes performance or storage.

isLine() function is a part of sk.yoz.math. GeometryMath class

public static function isLine(point1:Point, point2:Point,
	point3:Point, orderSensitive:Boolean = true):Boolean
{
	var x1:Number = point1.x - point2.x;
	var x2:Number = point2.x - point3.x;
	var y1:Number = point1.y - point2.y;
	var y2:Number = point2.y - point3.y;

	if(orderSensitive && ((x1 > 0 && x2 < 0) || (x1 < 0 && x2 > 0)
		|| (y1 < 0 && y2 > 0) || (y1 < 0 && y2 > 0)))
		return false;
	else if(!y2)
		return !y1;
	else if(!x2)
		return !x1;
	else
		return x1 / x2 == y1 / y2;
}

Flex Size Report

by Jozef Chúťka on January 5, 2010

linkreport

This post is for everyone who is looking for clear and readeable size report from flex.

1. Define “-link-report” flag for compiler
2. Export / publish your project
3. Create static .xsl file
4. Link .xsl file into generated .xml size report

1. Define “-link-report” flag for compiler

In order to get raw .xml size report from flex project you have to define “-link-report” flag. In flex project go to “File / Properties / Flex Compiler / Additional compiler arguments” text field and append neccesary “-link-report” flag. Your argument query now should look someting like this

-locale en_US -link-report=report.xml -optimize

2. Export / publish your project

Now you can release export or debug export your project to get your report. report.xml should appear in bin-debug directory.

3. Create static .xsl file

In same directory, create empty file named “flexSizeReport.xsl”. Put following content into this file and save:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:msxsl="urn:schemas-microsoft-com:xslt"
	exclude-result-prefixes="msxsl">
	<xsl:output method="html" indent="yes"/>
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>

	<xsl:template match="/">
		<html>
			<body>
				<h1>ActionScript Link Report</h1>

				<h2>Custom Classes</h2>
				<table border="1">
					<tr>
						<th align="left">Name</th>
						<th align="left">Size (B)</th>
						<th align="left">Optimized Size (B)</th>
					</tr>
					<xsl:for-each select="report/scripts/script">
						<xsl:sort select="@optimizedsize" data-type="number" order="descending"/>
						<xsl:if test="not(contains(@name, 'frameworks'))">
							<tr>
								<td><xsl:value-of select="@name"/></td>
								<td align="right"><xsl:value-of select="@size"/></td>
								<td align="right"><xsl:value-of select="@optimizedsize"/></td>
							</tr>
						</xsl:if>
					</xsl:for-each>
				</table>

				<h2>Framework Classes</h2>
				<table border="1">
					<tr>
						<th align="left">Name</th>
						<th align="left">Size (B)</th>
						<th align="left">Optimized Size (B)</th>
					</tr>
					<xsl:for-each select="report/scripts/script">
						<xsl:sort select="@optimizedsize" data-type="number" order="descending"/>
						<xsl:if test="contains(@name, 'frameworks')">
							<tr>
								<td><xsl:value-of select="@name"/></td>
								<td align="right"><xsl:value-of select="@size"/></td>
								<td align="right"><xsl:value-of select="@optimizedsize"/></td>
							</tr>
						</xsl:if>
					</xsl:for-each>
				</table>
			</body>
		</html>
	</xsl:template>
</xsl:stylesheet>

Now open report.xml file. It should contain some content like:

<report>
  <scripts>
     <script name="Drive:\Path\MyProject\src\accessibility\ComboBoxItemAccessibility.as" mod="1217893038671" size="1593" optimizedsize="875">
       <def id="accessibility:ComboBoxItemAccessibility" />
       <pre id="accessibility:ButtonAccessibility" />
       <dep id="flash.events:MouseEvent" />
       <dep id="accessibility:Constants" />
       <dep id="MenuItem" />
       <dep id="AS3" />
    </script>
  </scripts>
</report>

4. Link .xsl file into generated .xml size report

Prepend this content with two lines:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="flexSizeReport.xsl"?>
<report>
  <scripts>...

Save report.xml and open it in some modern browser, that can handle xslt (Chrome or Firefox work just fine). Now you see your .xml report parsed by .xsl file into two separated tables (custom classes, framework classes) ordered by optimized size descending.

Read more about “-link-report” flag here:

RayMath class

by Jozef Chúťka on December 16, 2009

raymath

While working on newest project I created a simple util class that simulates rays of spot light. It uses DisplayObject hitTestPoint() method to test for ray stream collisions so it does not go beyond the obstacles (corners etc.). Feel free to use RayMath class as you wish

RayMath application

RayMath class

package sk.yoz.math
{
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.geom.Point;

    public class RayMath extends Object
    {
        public function RayMath()
        {
            super();
        }

        public static function getRayPoints(lamp:DisplayObject,
            boundary:DisplayObject, rayCount:uint = 50, rayRange:uint = 50)
            :Array
        {
            var lp:Point, gp:Point, fp:Point;
            var points:Array = [];
            var angle:Number;
            for(var i:uint = 0; i < rayCount; i++)
            {
                fp = null;
                for(var j:uint = 0; j < rayRange; j++)
                {
                    angle = Math.PI / rayCount * i * 2;
                    lp = new Point(Math.cos(angle) * j, Math.sin(angle) * j);
                    gp = lamp.localToGlobal(lp);
                    if(!boundary.hitTestPoint(gp.x, gp.y, true))
                        break;
                    fp = lp;
                }
                if(fp)
                    points.push(fp);
            }
            return points;
        }

        public static function boundaryDistance(points:Array):Number
        {
            var shortest:Number = Number.MAX_VALUE;
            var zeroPoint:Point = new Point(0, 0);
            var distance:Number;
            for each(var point:Point in points)
            {
                distance = Point.distance(point, zeroPoint);
                if(distance < shortest)
                    shortest = distance;
            }
            return shortest;
        }

        public static function drawRays(lamp:Sprite, points:Array):void
        {
            if(!points.length)
                return;
            lamp.graphics.moveTo(points[0].x, points[0].y);
            for each(var point:Point in points)
                lamp.graphics.lineTo(point.x, point.y);
            lamp.graphics.lineTo(points[0].x, points[0].y);
        }
    }
}

Demo application

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    applicationComplete="init()" width="500" height="500"
    backgroundGradientAlphas="1,1" backgroundColor="0xffffff">
<mx:Script>
<![CDATA[
    import sk.yoz.math.RayMath;

    private function init():void
    {
        var bg:Graphics = boundary.graphics;
        bg.beginFill(0x000000, 1);

        bg.moveTo(50, 50);
        bg.lineTo(width - 50, 130);
        bg.lineTo(width - 150, height - 50);
        bg.lineTo(100, height - 100);
        bg.lineTo(50, 50);

        bg.moveTo(100, 200);
        bg.lineTo(300, 200);
        bg.lineTo(250, 300);
        bg.lineTo(100, 200);

        bg.moveTo(300, 250);
        bg.lineTo(350, 250);
        bg.lineTo(350, 300);
        bg.lineTo(300, 250);

        lamp.startDrag(true);
        addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }

    private function mouseMoveHandler(event:MouseEvent):void
    {
        var lg:Graphics = lamp.graphics;
        lg.clear();
        lg.beginFill(0xffff00, 0.5);
        lg.lineStyle(1, 0xffff00, 0.8);
        var points:Array = RayMath.getRayPoints(lamp, boundary, 80, 100);
        RayMath.drawRays(lamp, points);

        lg.endFill();
        for each(var point:Point in points)
        {
            lg.moveTo(0, 0);
            lg.lineTo(point.x, point.y);
        }
    }
]]>
</mx:Script>
<mx:UIComponent id="boundary" />
<mx:UIComponent id="lamp" />
</mx:Application>

Flex Charts – interpolating values in Series

by Jozef Chúťka on December 4, 2009

interpolateValues

Recently, I felt into problem with chart when using interpolateValues parameter set to true. You expect it to work as described in Language Reference. And guess what, it works :) , but make sure you provide correct numeric values (not strings – those were problematic in my case). Parameter interpolateValues is able to draw a continuous line by interpolating the missing value. Missing values means null (v1 in example) or when key is omited (v2).

If you want to use Charts in Flex, download data visualization components for your flex builder and copy content into sdk directory.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
    backgroundColor="#ffffff">
<mx:Script>
<![CDATA[
    import mx.collections.ArrayCollection;

    [Bindable]
    private var dp:ArrayCollection = new ArrayCollection([
        {Month: "Jan", v1: 1000, v2: 900, v3: "800", v4: "700"},
        {Month: "Feb", v1: null, v3: null},
        {Month: "Mar", v1: 2000, v2: 1900, v3: "1800", v4: "1700"}
    ]);
]]>
</mx:Script>

<mx:Stroke id = "s1" color="blue" weight="2"/>
<mx:Stroke id = "s2" color="red" weight="2"/>
<mx:Stroke id = "s3" color="green" weight="2"/>
<mx:Stroke id = "s4" color="yellow" weight="2"/>

<mx:LineChart id="lineChart" height="100%" width="100%" dataProvider="{dp}">
    <mx:horizontalAxis>
        <mx:CategoryAxis categoryField="Month"/>
    </mx:horizontalAxis>
    <mx:series>
        <mx:LineSeries yField="v1" form="curve" displayName="v1"
            lineStroke="{s1}" interpolateValues="true"/>
        <mx:LineSeries yField="v2" form="curve" displayName="v2"
            lineStroke="{s2}" interpolateValues="true"/>
        <mx:LineSeries yField="v3" form="curve" displayName="v3"
            lineStroke="{s3}" interpolateValues="true"/>
        <mx:LineSeries yField="v4" form="curve" displayName="v4"
            lineStroke="{s4}" interpolateValues="true"/>
    </mx:series>
</mx:LineChart>
<mx:Legend dataProvider="{lineChart}" direction="horizontal"/>
</mx:Application>

Notice numeric vs. string values in dataProvider. Result:

interpolateValues