Hack 94. Generate Flash Movies on the Fly

Use Ming to create dynamic Flash movies from PHP.
Have you ever wanted to make a web graphic have a little more pizzazz or zing? We all have at some point. One way to do this is to use the Macromedia Flash format (also known as the SWF format). But how do you do that with just open source tools? Well, there is a PHP module called Ming that saves the day (ironic, isn't it? Ming saves Flash?). It allows you to generate full-blown Flash files on the fly. This hack will show you how to pull that off with a Flash application that dynamically generates charts.
9.10.1. The Code
Save the code in Example 9-13 as data.php.
Example 9-13. Some XML to be rendered by Flash
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache"); // HTTP/1.0
header('Content-type: application/xml');
echo("<?xml version=\"1.0\" ?>\n");
?>
<GRAPH TYPE="BAR">
<TITLE>Revenues 2005</TITLE>
<YAXIS>Dollars
<RANGE MIN="0" MAX="50000" />
</YAXIS>
<XAXIS>Period
</XAXIS>
<DATA>
<?php
$colors = array( "0xFF0000", "0xFFFF00", "0xFF00FF", "0x00FFFF", "0x00FF00" );
srand((double)microtime()*1000000);
for ($i = 1; $i < 7; $i++)
{
$clr = $colors[ ($i - 1) % count($colors) ] ;
$val = rand(10000,45000);
echo("<D$i>$val<COLOR C=\"$clr\" /></D$i>\n");
}
/*
<D1>20000<COLOR C="0xFF0000" /></D1>
<D2>25000<COLOR C="0xFFFF00" /></D2>
<D3>27000<COLOR C="0xFF00FF" /></D3>
<D4>42000<COLOR C="0x00FFFF" /></D4>
<D5>48000<COLOR C="0x00FF00" /></D5>
*/
?>
</DATA>
</GRAPH>
graph.php, shown in Example 9-14, is the actual PHP script that does the work (with lots of help from Ming).
Example 9-14. Ming and PHP to the rescue
<?
ming_useswfversion(6); // Important!
$m = new SWFMovie();
$m->setBackground(0x80, 0x80, 0x80);
$m->setDimension(320, 240);
$m->setRate(30.0);
$s = new SWFShape();
$f = $s->addFill(0xff, 0xff, 0xff);
$s->setRightFill($f);
$s->movePenTo (-5, 0);
$s->drawLineTo( 5, 0);
$s->drawLineTo( 5, -10);
$s->drawLineTo(-5, -10);
$s->drawLineTo(-5, 0);
$p = new SWFSprite();
$i = $p->add($s);
$i->setDepth(1);
$p->nextFrame();
$i = $m->add($p);
$i->setDepth(1);
$i->moveTo(-10, -10);
$i->setName("box");
$m->add(new SWFAction("
var data = [];
var heights = [];
var actual = [];
var timerID;
// Animate the bars to their final position
function anim ()
{
var done = true;
for (var k = 0; k < data.length; k++)
{
var n = 'D' + k;
if (heights[k] != actual[k])
done = false;
else
continue;
var diff = (heights[k] - actual[k]) / 5;
actual[k] += diff;
_root[n]._height = actual[k];
if (diff < 0.1)
actual[k] = heights[k];
}
if (done)
{
clearInterval(timerID);
stop();
}
};
// Take the XML data transformed into a JavaScript/ActionScript
// object and display it
function doit(o)
{
// Take the data and draw
// Draw the axes
createEmptyMovieClip ('grp', 1);
with (grp)
{
lineStyle (1, 0xFFFFFF, 100);
// X - Axis
moveTo (40, 200);
lineTo (280, 200);
// Y - Axis
moveTo (40, 200);
lineTo (40, 20);
// Draw the Main Title
createTextField('title', 1, 100, 00, 100, 100);
var fmt = new TextFormat();
fmt.color = 0xffffff;
fmt.font = 'Arial';
title.text = o['TITLE']['_txt'];
title.setTextFormat(fmt);
// Draw the X Axis Title
createTextField('xtitle', 2, 120, 220, 100, 100);
var fmt = new TextFormat();
fmt.color = 0xffffff;
fmt.font = 'Arial';
fmt.size = '10';
xtitle.text = o['XAXIS']['_txt'];
xtitle.setTextFormat(fmt);
// Draw the Y Axis Title
createTextField('ytitle', 3, 0, 100, 100, 100);
var fmt = new TextFormat();
fmt.color = 0xffffff;
fmt.font = 'Arial';
fmt.size = '10';
ytitle.text = o['YAXIS']['_txt'];
ytitle.setTextFormat(fmt);
// Draw the Y Axis Labels
createTextField('ylabeltop', 5, 20, 16, 20, 20);
var fmt = new TextFormat();
fmt.color = 0xffffff;
fmt.font = 'Arial';
fmt.size = '6';
fmt.align = 'right';
ylabeltop.text = o['YAXIS']['RANGE']['1'];
ylabeltop.setTextFormat(fmt);
createTextField('ylabelbot', 6, 20, 193, 20, 20);
var fmt = new TextFormat();
fmt.color = 0xffffff;
fmt.font = 'Arial';
fmt.size = '6';
fmt.align = 'right';
ylabelbot.text = '0';
ylabelbot.setTextFormat(fmt);
}; // End with(grp)
// Draw the Data
// Determine how many data items we have
for (var k = 0; k < 10; k++)
{
if (typeof(o['DATA']['D'+k]) == 'object')
data.push(o['DATA']['D'+k]);
}
// Draw the Data
// Go through each data item and position a movie there
var increment = 180 / data.length;
var width = increment / 2;
var max = Number(o['YAXIS']['RANGE']['1']);
for (var k = 0; k < data.length; k++)
{
// dup the box into a new column
var n = 'D' + k;
duplicateMovieClip(box, n, 10+k);
// move to final position
_root[n]._x = 40 + ((k + 1) * increment);
_root[n]._y = 199.5;
_root[n]._width = width;
// Set heights initially to zero, and animate later
_root[n]._height = 0;
// Get the data value and transform to viewport
var n2 = 'D' + (k+1);
var h = 180 / max * Number(o['DATA'][n2]['_txt']);
heights.push(h);
actual.push(0);
var c = new Color(_root[n]);
c.setRGB(Number(o['DATA'][n2]['COLOR']['1']));
}
// We are done, setup an animation timer to refresh
// the movie clip sizes every 32 millis until they achieve
// their final height.
timerID = setInterval(anim, 32);
}
function convertToJS(nodes, o)
{
if (arguments.length == 1)
o = {};
for (var i = 0; i < nodes.length; i++)
{
if (nodes[i].nodeType == 1)
{
var tmp = convertToJS(nodes[i].childNodes);
// Add attributes
var attribs = nodes[i].attributes;
for (var j in attribs)
tmp[i] = attribs[j];
var name = nodes[i].nodeName;
o[name] = tmp;
}
else
{
var v = nodes[i].nodeValue;
o._txt = v;
}
}
return o;
}
var xml = new XML();
xml.ignoreWhite = true; // Otherwise lots of blank nodes created
xml.onLoad = function(success)
{
if (!success)
{
// Do a Javascript alert here through a url
return;
}
var o = convertToJS(this.childNodes[0].childNodes);
doit(o);
};
xml.load('data.php');
"));
$m->nextFrame();
header('Content-type: application/x-shockwave-flash');
$m->output();
?>
The process used here is illustrated in Figure 9-15. The file graph.php is called by the web browser to render. The script then uses the Ming library to generate a movie with some simple off-screen sprites. As the final action of the movie, it will call some ActionScript to load an XML file from a URL. This XML file will be dynamically generated by PHP in data.php. Currently, data.php generates random data for a bar chart. Once the data is loaded and parsed by the Flash player, the bars are animated to display their final values.
The Ming PHP library is object oriented (OO), which is nice, so it uses classes throughout the process. This makes it easy to see what methods each object supports. The most important class is the SWFMovie class. This is the class that will render the elements into the SWF file with an output( ) call. To see something, though, you need to add display objects to the movie. I created a simple white box sprite that will be placed off-screen and will be used as the prototype for the bars in the chart. Finally, an ActionScript object (SWFAction) is added.
The ActionScript code in the Flash object does the bulk of the work, and
|