Commits

Anonymous committed a2349af

[svn r2] Final stage 2

  • Participants
  • Parent commits e57d565

Comments (0)

Files changed (122)

.actionScriptProperties

     <libraryPath defaultLinkType="1">
       <libraryPathEntry kind="4" path=""/>
       <libraryPathEntry kind="1" linkType="1" path="libs"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/Adobe corelib/bin/corelib.swc" useDefaultLinkType="false"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/AS3 data structures/bin/as3ds.swc" useDefaultLinkType="false"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/TweenMax/AS3/greensock.swc" useDefaultLinkType="false"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/PureMVC/bin/PureMVC_AS3_2_0_4.swc" useDefaultLinkType="false"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/Flare/build/flare.swc" useDefaultLinkType="false"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/SWFAddress/dist/swc/SWFAddress.swc" useDefaultLinkType="false"/>
-      <libraryPathEntry kind="3" linkType="1" path="${DOCUMENTS}/PureMVC Console/PureMVCConsole.swc" useDefaultLinkType="false"/>
     </libraryPath>
     <sourceAttachmentPath/>
   </compiler>
+
+Build information
+=================
+
+All 3rd party libraries are included in the package, nothing else needs
+to be downloaded.
+
+
+Flex Builder
+------------
+
+The code was developed using Flex Builder 3. Simply import the entire archive
+as a project.
+
+The base Flex SDK version is 3.2, but it should work with any later 3.x Flex release.
+
+
+Flex Open Source SDK
+--------------------
+
+The build has been tested with version 3.5a of the Open Source Flex SDK. It
+can be downloaded here: http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3
+
+Download the appropriate zipfile and unpack it. You may need to change
+the protections on the binaries so that they are executable:
+
+- cd <sdkdir>/bin
+- chmod +x *
+
+You will also need a recent version of Apache Ant, and a Java runtime environment.
+
+To build a debug version:
+
+- ant debug
+
+Output is placed in the output.debug directory.
+
+To build for release (optimised, no debug):
+
+- ant release
+
+Output is placed in the output.release directory.
+
+
+Execution
+---------
+
+The client must be run from within a webserver, both for testing and for deployment.
+
+Mac OS X contains a built-in webserver. Simply enable it from System Preferences/Sharing,
+and copy the output.debug/output.release files into the your Sites directory.
+
+If you are using Linux, please see your local documentation (or use Google) for information
+on how to enable the Apache webserver.
+
+Windows users should use Google to find out how to install a local webserver.
+
+
+Build dependencies
+------------------
+
+Adobe corelib: https://github.com/mikechambers/as3corelib
+AS3 data structures: http://lab.polygonal.de/ds/
+PureMVC: http://trac.puremvc.org/PureMVC_AS3/
+TweenMax: https://www.greensock.com/tweenmax/
+Flare: http://flare.prefuse.org/
+SWFAddress: http://www.asual.com/swfaddress/
+
+In addition, the HTML/Javascript environment makes use of swfobject
+(http://code.google.com/p/swfobject/).
+
+The current versions of some of the above packages may be more recent
+than those included in the package.
+
+
+Template files
+--------------
+
+Template files for the installation live in the toplevel html-template
+directory.
+
+
+
+PureMVC
+=======
+
+PureMVC is the glue that holds the application together.
+
+PureMVC nomenclature is a little different from standard MVC.
+Views are still the actual components that draw on the glass.
+However, in PureMVC, models are called Proxies, and controllers
+are called Mediators.
+
+User events are caught within views, and these generate
+application events which are picked up by mediators.
+Mediators then call proxies to change/retrieve state.
+When proxy states change, they send notifications which
+are picked up by mediators, which then update views
+accordingly.
+
+Mediators need to know about their proxies and views,
+but views and proxies don't need to know about each
+other.
+
+State changes are often communicated through the use of Notifications.
+These are named events which can carry data. Mediators typically
+register their interest in a number of different notifications, which
+can come from proxies or other mediators.
+
+The Command class is a way to package up application logic
+into a self-contained way that coordinates the work of other
+objects. Commands can also be linked to notifications, ie,
+a notification can cause a Command to be executed.
+
+If a state change in a proxy needs to be picked by multiple
+mediators, then it's generally better to use a Command: the
+Command is able to synchronise changes in a controlled way.
+
+The Facade is responsible for managing all these classes,
+and for routing notifications around the system. This is the
+first object created.
+
+All proxies and mediators are effectively singletons, ie,
+we don't create multiple copies of them.
+
+
+Source tree
+===========
+
+charts				- charting code
+charts/bubblechart	- bubble chart
+charts/include		- AS3 source for MXML components
+commands			- application Commands
+components			- various UI components
+components/include	- AS3 source for MXML components
+data				- data handling
+events				- application event classes
+include				- AS3 source for WDMMG component
+mediators			- application Mediators
+proxies				- application Proxies
+uk					- legacy UK/COFOG definitions
+views				- individual views
+views/commands		- view commands
+views/components	- view subcomponents
+views/events		- view event classes
+views/include		- AS3 source for view components
+views/mediators		- view Mediators
+views/proxies		- view Proxies
+
+
+Startup
+=======
+
+The toplevel main.mxml simply loads the WDMMG component from
+com/iconomical/wdmmg. The main role of WDMMG is to provide
+an overall screen layout, and to start up the application
+by creating the facade, WDMMGFacade. Most of the facade's
+job is done in the Facade superclass. The WDMMGFacade itself is
+used to register and call the initial application-specific
+StartupCommand.
+
+The surrounding HTML must pass an 'assets' parameter to the
+application on startup. This is read by StartupCommand,
+and tells the application where to load all other asset
+files, including the main dashboard-config.json file.
+If this is not supplied, the 'assets/' directory is used.
+
+StartupCommand then creates a number of startup proxies,
+followed by the application mediator.
+
+The main application mediator is WDMMGAppMediator. Its primary
+job is to organise the startup sequence, and initialise
+the interface once all config files and assets have been
+loaded. It waits until it has received notifications from
+the startup proxies, then executes InitialiseInterface.
+
+
+Initialisation sequence:
+- WDMMG.mxml
+- include/WDMMG_inc.as
+- WDMMGFacade.as
+- StartupCommand.as
+- various proxies
+- WDMMGAppMediator.as
+- commands/InitialiseInterface.as
+
+
+Config
+======
+
+ConfigProxy loads the dashboard-config file, and provides
+access to the config data.
+
+The config file format is described later in this document.
+
+
+
+Data
+====
+
+Apart from the long-term trends view, all data comes from
+the data store.
+
+Data query specification is largely parameterised through the
+config file.
+
+All numbers are in millions.
+
+
+Config
+------
+
+Data requests are made to a dataStore. Each data request
+is identified by a name, and must specify a slice, and
+the aggregation data keys to be retrieved.
+
+In addition, the classification key names must be specified.
+These allow the engine to retrieve more detailed information
+about classification key values.
+
+Currently, COFOG per-capita data by region is hardwired.
+This is the only request where customised post-processing
+is done, and until we have further examples, we can't
+decide how to parameterise this.
+
+
+Data processing
+---------------
+
+DataStore is the class that deals directly with the
+data store. DataStoreProxy provides the interface to the rest
+of the application.
+
+Data is received from the aggregator in table format. This is converted
+into a more hierarchical structure for use elsewhere in the client.
+
+Inside the client, data is collected into objects indexed by year.
+Each year contains breakdowns by classification hierarchy.
+Each classification level can contain further levels.
+
+Non-data store requests are done through the DataManager class.
+These are legacy requests for long-term time series data, and only
+used by the long-term trends view.
+
+
+
+User interface assets
+---------------------
+
+Graphical assets are loaded through WDMMGAssetsProxy and
+WDMMGAssetsProxy2, which are wrappers for WDMMGAssets
+and WDMMGAssets2. Some older code might bypass the proxies.
+
+Source files for the assets are in the toplevel assets directory.
+These are .fla files, and have to be opened using Adobe Flash.
+
+
+Views
+=====
+
+Each separate screen is a view, and they all live in the views
+subdirectory.
+
+DailyBreadView: daily bread
+LongTermFunctionalView: long term trends
+LongTermSubfunctionalView: uk-wide bubble chart
+NationalFunctionalView: nations stacked chart
+NationalSubfunctionalView: nations bubble chart
+RegionsComparisonView: comparatron
+RegionsFunctionalView: regional overview
+RegionsSubfunctionsView: regional subfunctions
+
+Each view has an MXML file, a proxy, and a mediator. MXML files use
+source files from the include subdirectory.
+
+
+Views are brought onto the screen by sending the appropriate
+notification. This results in a Command being run - these are
+all in the views/commands directory.
+
+CurrentViewProxy keeps track which view is current. Each change-view
+command works by changing the CurrentViewProxy state. CurrentViewProxy
+then issues a notification, which is picked up by WDMMGViewsMediator.
+
+WDMMGViewsMediator does the actual work of removing the old view
+and organising the new one.
+
+
+View initialisation
+-------------------
+
+Each view is defined using an MXML file, which defines the overall layout.
+Each MXML file includes an actionscript file, which contains the code for
+the view.
+
+View components are not available until its children are constructed. Each view
+has a listener for the childrenCreated event. This listener notifies the mediator
+that the view is now available, ie, that the mediator can invoke operations on
+the view.
+
+The mediator can only display data once the data itself is available, ie, when
+the data has been received from the server. PESAViewMediator controls the
+overall mediator startup through initialiseView(), and it invokes a method
+called canInitialise(), which needs to be overridden in subclasses. If canInitialise()
+returns true, then initialiseView() can draw the view.
+
+The proxy for each view is responsible for obtaining the data. Each proxy has an
+isDataReady() method, and this is normally called by each mediator's canInitialise()
+method. If all the necessary data components are loaded, then isDataReady()
+returns true. If the data is not yet available, then the data request calls have
+the side effect of initialising the data loads.
+
+
+Charting
+========
+
+Most of the plots are produced using an internal plotting framework.
+
+Flare is used on only one view: long-term trends. We found that while Flare
+is an excellent library for standalone visualisations, it wasn't best suited
+for use in an embedded framework.
+
+
+HOWTOs
+======
+
+How to add another view
+-----------------------
+
+You need to create a number of new classes/files in the views directory:
+
+	- NewView.mxml (subclass of PESAView)
+	- include/NewView_inc.as
+	- mediators/NewViewMediator.as (subclass of PESAViewMediator)
+	- proxies/NewViewProxy.as (subclass of PESAViewProxy)
+	- views/NewViewCommand.as (subclass of ShowNewViewCommand)
+
+Construct the user interface inside the NewView.mxml.
+
+Define the communication between the view and the mediator. The view catches
+the user events themselves, so define application-specific events which are
+generated by the view, and caught by the mediator. The mediator drives the
+view, so the view needs to provide access functions.
+
+Define the communication between the mediator and the proxy. The proxy provides
+the mediator with all of its data, so the proxy generally has to implement
+get/set routines. Since this is largely an event-driven system and data might
+not be immediately available, the proxy has to define notifications to indicate
+data availability. The mediator has to register its interest in these notifications.
+
+NewViewCommand must create the appropriate mediator type in the makeViewMediator
+factory method.
+
+Register the NewViewCommand in mediators/WDMMGViewsMediator.
+
+To hook the view into the legacy user interface, add a new button to any
+of the ViewButtons components in views/components.
+
+To allow the view to be opened from Javascript, update the urlMapping table
+in SWFAddressProxy.
+
+
+Adding more Javascript interfaces
+---------------------------------
+
+All Javascript interfacing is done in the JavascriptProxy.
+
+Functions called from Javascript need to be registered as callbacks. This is
+done by creating a function, and registering the function in registerCallbacks().
+
+If the client needs to call back to Javascript, create a public function which
+provides the interface to the rest of the application. Within this function,
+use the ExternalInterface.call() routine to call back to the Javascript.
+
+There are numerous examples of both mechanisms in the class.
+
+
+
+Config file
+===========
+
+This is a JSON file. It describes a single dictionary of key/value pairs.
+Values are strings unless otherwise noted.
+
+
+dataStore
+---------
+
+This is the base URL for data store requests.
+The default is http://data.wheredoesmymoneygo.org/
+
+
+dataRequests
+------------
+
+This is a more complicated structure. There are two required fields:
+
+- classification;
+- queries.
+
+'classification' is an array of classification types, eg, cofog1. This is used
+to obtain further data store information on the classifications.
+
+'queries' is a map of query names to query specifications. Each specification is a
+dictionary containing the following key/value pairs:
+
+- slice (the name of the slice);
+- dataKeys (ordered array of classification names);
+- regionKey (optional classification key for regions);
+- params (additional data store query parameters).
+
+
+themeData
+---------
+
+This is a largely ad-hoc structure, which is dictated by the varying
+natures of the different views. Please see the toplevel flash/dashboard-config.json
+for a complete example.
+
+
+iconMapping
+-----------
+
+This is a dictionary of key/value strings pairs. The keys are classification identifiers,
+eg, COFOG_1. The values are the names of icon assets in the WDMMGassets2.fla file.
+This allows the developer to map arbitrary keys to reasonable icons throughout
+the application.
+
+
+infoboxDisabled
+---------------
+
+If set to true, this disables the display of help text for spending classifications
+within the application. Developers should handle this through the wdmmgInfobox call
+in Javascript.
+
+
+dataColours
+-----------
+
+This is a dictionary of classification key identifiers. Each identifier has
+an array of strings denoting hex-encoded RGB colours, eg, 0x123456.
+
+Classifications are considered to be hierarchical (eg, COFOG 1, 1.1, 1.1.1).
+
+
+currencyPrefix
+--------------
+
+This string is displayed before currency values. The default is "£"
+
+
+currencySuffix
+--------------
+
+This string is displayed after currency values. The default is "".
+
+
+Javascript interface
+====================
+
+The application provides an API to allow it to be
+controlled through Javascript.
+
+changeView(viewName, params)
+----------------------------
+
+This allows the current view to be changed. Parameters can
+also be provided.
+
+long-term:
+	functionSpending (actual/real/gdp)
+	longTermSpending (actual/real/gdp)
+
+uk-bubble-chart:
+	focus (spending code, eg, 01.1 in cofog)
+	year (year text, eg, 2007-2008)
+
+national-trends:
+	region (country names, spaces replaced with '-', entire uk is 'uk')
+	focus (spending code)
+
+national-bubble-chart:
+	focus (spending code)
+	region (country names, spaces replaced with '-', entire uk is 'uk')
+	year
+
+regional-overview:
+	spending (indexed/per_capita/actual)
+	region (cofog region names, spaces replaced with '-')
+	year
+	filter (comma separated list of top-level spending codes)
+
+regional-drilldown:
+	spending (actual/per_capita)
+	focus (spending code)
+	year
+
+daily-bread:
+	income
+	code (toplevel classification code)
+	interesting (array of classification codes)
+
+comparatron-a:
+	region1
+	region2
+	year
+
+
+removeHeader()
+--------------
+
+Remove the legacy header components.
+
+
+removeFooter()
+--------------
+
+Remove the legacy footer components.
+
+
+disableUrls()
+-------------
+
+Prevent the application from rewriting URLs as the
+view is changed.
+
+
+wdmmgInit()
+-----------
+
+Called by the application when it is starting up.
+removeHeader(), removeFooter() and disableUrls()
+can be called here, but not changeView().
+
+
+wdmmgReady()
+------------
+
+Called by the application when it is fully initialised,
+ie, when the interface can be modified. changeView()
+should not be called before wdmmgReady() is received.
+
+
+wdmmgInfobox(code, classificationName, link)
+--------------------------------------------
+
+Called by the application when the user wishes to see
+more information on an item, and 'infoboxDisabled' has
+been set to true in the config file.
+
+
+wdmmgCallback(viewName, params)
+-------------------------------
+
+Called by the application when a view changes. viewName
+and params correspond to the changeView parameters.
+
+
+wdmmgHelp(viewName, params)
+---------------------------
+
+Called by the application when the user clicks on a help button.
+This is only enabled in the daily bread and long-term trends views.
+
+The view names are 'daily-bread' and 'long-term'. For the long-term
+view, the params contain an object specifying the view type, which
+is either 'functions' or 'tme'.
+
+
+
+
+Caveats
+=======
+
+Many things are not ideal. The project started as a very quick
+and dirty prototype based around a non-homogeneous data set,
+and there has been little opportunity to revisit or
+re-architecture.

assets/WDMMGassets2.fla

Binary file added.
+
+# Change this to the location of your Flex SDK installation
+FLEX_HOME = ../flex_sdk_3/
+
+debug.outdir	=	debug.output
+release.outdir	=	release.output
+template.dir = html-template
+template.index.html = index.template.html
+
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="WDMMG" basedir=".">
+	<property file="./build.properties"/>
+	<taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar/"/>
+	<description>Build Script</description>
+	
+	<target name="debug" depends="cleanDebug,buildDebugDir">
+		<mxmlc debug="true" optimize="false" file="${basedir}/src/main.mxml" output="${debug.outdir}/main.swf" use-network="false">
+			<load-config filename="${basedir}/wdmmg_build.xml"/>
+			<compiler.source-path path-element="${basedir}/src/"/>
+			<source-path path-element="${FLEX_HOME}/frameworks/"/>
+		</mxmlc>
+	</target>
+	
+	<target name="release" depends="cleanRelease,buildReleaseDir">
+		<mxmlc debug="false" optimize="true" file="${basedir}/src/main.mxml" output="${release.outdir}/main.swf" use-network="false">
+			<load-config filename="${basedir}/wdmmg_build.xml"/>
+			<compiler.source-path path-element="${basedir}/src/"/>
+			<source-path path-element="${FLEX_HOME}/frameworks/"/>
+		</mxmlc>
+	</target>
+	
+	<target name="cleanRelease">
+		<delete dir="${release.outdir}"/>
+		<mkdir dir="${release.outdir}"/>
+	</target>
+	
+	<target name="cleanDebug">
+		<delete dir="${debug.outdir}"/>
+		<mkdir dir="${debug.outdir}"/>
+	</target>
+	
+	<target name="buildDebugDir">
+		<copy todir="${debug.outdir}" overwrite="true">
+			<fileset dir="${template.dir}"/>
+		</copy>
+		<move file="${debug.outdir}/${template.index.html}" tofile="${debug.outdir}/index.html"/>
+	</target>
+	
+	<target name="buildReleaseDir">
+		<copy todir="${release.outdir}" overwrite="true">
+			<fileset dir="${template.dir}"/>
+		</copy>
+		<move file="${release.outdir}/${template.index.html}" tofile="${release.outdir}/index.html"/>
+	</target>
+</project>

html-template/barnet-config.json

+{
+	"dataStore":"http://data.staging.wheredoesmymoneygo.org/",
+	"mainSite":"http://www.wheredoesmymoneygo.org/",
+	"commentsLocation":"http://www.wheredoesmymoneygo.org/comments/dashboard/?page=",
+	"dataRequests":{
+		"classification":["key_barnet_level_1", "key_barnet_level_2"],
+		"queries":{
+			"ukCofog":{
+				"slice":"barnet",
+				"dataKeys":["key_barnet_level_1", "key_barnet_level_2"],
+				"regionKey":"",
+				"params":""
+			}
+		}
+	},
+	"dataColours":{
+        "01":["0x8c5d92"],
+        "02":["0x916596"],
+        "03":["0x00b0d6"],
+        "04":["0x7db67f"],
+        "05":["0xdd6f9b"],
+        "06":["0x96493d"],
+        "07":["0xc9bf62"],
+        "08":["0x8c3932"],
+        "09":["0x787894"],
+        "10":["0x548957"],
+        "11":["0xc9c069"]
+	}
+}

html-template/callbacks.js

+
+// Called by the dashboard after basic initialisation is done.
+// 
+function wdmmgInit() {
+	var m = swfobject.getObjectById("wdmmg");
+	//m.removeHeader();
+	//m.removeFooter();
+	//m.disableUrls();
+}
+
+
+// Called by the dashboard when it's fully initialised
+//
+function wdmmgReady() {
+	//changeView("uk-bubble-chart", {'focus':'01'});
+	//changeView("daily-bread", {'income':'25000', 'code':'10', 'interesting':['01', '02', '03.x', '06.1', '08.2', 'foo']});
+	//changeView("comparatron-a", {'region1':'Scotland', 'region2':'London', 'spending':'per_capita', 'year':'2008-2009'});
+}
+
+
+// Called when the user wants to show more information, but the
+// visualisation infobox popup is disabled
+//
+function wdmmgInfobox(code, classificationName, link) {
+	//alert(code+":"+classificationName+":"+link);
+}
+
+
+// Called by the dashboard for extra help information
+//
+function wdmmgHelp(id, params) {
+	//alert(id);
+}
+
+
+// Called by the dashboard when the user makes changes within the visualisation
+// 
+function wdmmgCallback(page, params) {
+	var txt = 'Page:' + page + "\n";
+	var p;
+	for (p in params) {
+		txt = txt + "param:" + p + ", " + params[p] + "\n";
+	}
+	//alert(txt);
+}
+
+
+// Changes the current dashboard view
+//
+function changeView(viewName, params) {
+	var m = swfobject.getObjectById("wdmmg");
+	m.changeView(viewName, params)
+}

html-template/cra_config.json

+{
+	"dataStore":"http://data.wheredoesmymoneygo.org/",
+	"mainSite":"http://www.wheredoesmymoneygo.org/",
+	"commentsLocation":"http://www.wheredoesmymoneygo.org/comments/dashboard/?page=",
+	"dataRequests":{
+		"classification":["cofog1", "cofog2", "cofog3", "region"],
+		"queries":{
+			"ukCofog":{
+				"slice":"cra",
+				"dataKeys":["cofog1", "cofog2", "cofog3"],
+				"regionKey":"",
+				"params":""
+			},
+			"ukActualRegion":{
+				"slice":"cra",
+				"dataKeys":["cofog1", "cofog2", "cofog3"],
+				"regionKey":"region",
+				"params":""
+			}
+		}
+	},
+	"dataColours":{
+        "01":["0x9900cc", "0x9933ff", "0xcc66ff"],
+        "02":["0x999933", "0x999966", "0xcccc66"],
+        "03":["0x0099cc", "0x33ccff", "0x66ddff"],
+        "04":["0x33cc33", "0x66cc66", "0x99cc99"],
+        "05":["0x006633", "0x339966", "0x33cc99"],
+        "06":["0xcc6666", "0xFF6666", "0xFF9999"],
+        "07":["0xcc0066", "0xff0099", "0xff00cc"],
+        "08":["0xCCCC00", "0xCCCC33", "0xCCCC66"],
+        "09":["0x3333cc", "0x6666cc", "0x9999cc"],
+        "10":["0xff3300", "0xff6633", "0xff9966"]
+	},
+	"theme":{
+		"background":"0x333333",
+		"font":{"color":"0xffffff"},
+		"comment-button":{"font":{"color":"0xffffff"}},
+		"title":{"font":{"name":"Gill Sans Bold", "embedded":"false", "bold":true, "size":26}},
+		"graphs":{"title":{"font":{"color":"0xffffff"}}, "axes":{"color":"0xffffff", "labels":{"font":{"color":"0xffffff"}}}},
+		"slider":{"font":{"color":"0xffffff"}},
+		"view-buttons":{"font":{"color":"0xffffff", "name":"Gill Sans"}, "selected-color":"0x333333", "deselected-color":"0x666666"},
+		"uk-bubble-chart":{"background":"0x333333", "font":{"color":"0xffffff"}},
+		"long-term":{"long-term-label":{"font":{"size":15}}, "tme-label":{"font":{"size":15}}},
+		"national-trends":{"title":{"font":{"size":18}}},
+		"regional-drilldown":{"background":"0x333333", "title":{"font":{"color":"0xffffff", "size":18}},
+							  "subfunctions":{"font":{"color":"0xffffff"}},
+							  "next-button":{"color":"0xffffff", "font":{"color":"0xffffff"}},
+							  "prev-button":{"color":"0xffffff", "font":{"color":"0xffffff"}}},
+		"daily-bread":{"background":"0xffffff", "font":{"color":"0x000000"}, "okfn-label":{}, "slider":{"font":{"color":"0x000000"}}, "select":{}, "title":{}, "salary":{"title":{}, "value":{}}, "tax":{"title":{}, "value":{}}}
+	}
+}

html-template/dfid-config.json

+{
+	"dataStore":"http://data.staging.wheredoesmymoneygo.org/",
+	"mainSite":"http://www.wheredoesmymoneygo.org/",
+	"commentsLocation":"http://www.wheredoesmymoneygo.org/comments/dashboard/?page=",
+	"dataRequests":{
+		"classification":["dfid_continent_or_agency", "dfid_continent_or_agency_sub", "dfid_country_or_subagency"],
+		"queries":{
+			"ukCofog":{
+				"slice":"dfid",
+				"dataKeys":["dfid_continent_or_agency", "dfid_continent_or_agency_sub", "dfid_country_or_subagency"],
+				"regionKey":"",
+				"params":""
+			}
+		}
+	},
+	"dataColours":{
+        "01":["0x8c5d92"],
+        "02":["0x916596"],
+        "03":["0x00b0d6"],
+        "04":["0x7db67f"],
+        "05":["0xdd6f9b"],
+        "06":["0x96493d"],
+        "07":["0xc9bf62"],
+        "08":["0x8c3932"],
+        "09":["0x787894"],
+        "10":["0x548957"],
+        "11":["0xc9c069"]
+	},
+  "theme": {
+      "background":"0xCDCDCD"
+  }
+}

html-template/flash/WDMMGassets.swf

Binary file added.

html-template/flash/WDMMGassets2.swf

Binary file added.

html-template/flash/dashboard-config.json

+{
+	"dataStore":"http://data.wheredoesmymoneygo.org/",
+	"mainSite":"http://www.wheredoesmymoneygo.org/",
+	"commentsLocation":"http://www.wheredoesmymoneygo.org/comments/dashboard/?page=",
+	"infoboxDisabled":false,
+	"dataRequests":{
+		"classification":["cofog1", "cofog2", "cofog3", "region"],
+		"queries":{
+			"ukCofog":{
+				"slice":"cra",
+				"dataKeys":["cofog1", "cofog2", "cofog3"],
+				"regionKey":"",
+				"params":""
+			},
+			"ukActualRegion":{
+				"slice":"cra",
+				"dataKeys":["cofog1", "cofog2", "cofog3"],
+				"regionKey":"region",
+				"params":""
+			}
+		}
+	},
+	"dataColours":{
+        "01":["0x9900cc", "0x9933ff", "0xcc66ff"],
+        "02":["0x999933", "0x999966", "0xcccc66"],
+        "03":["0x0099cc", "0x33ccff", "0x66ddff"],
+        "04":["0x33cc33", "0x66cc66", "0x99cc99"],
+        "05":["0x006633", "0x339966", "0x33cc99"],
+        "06":["0xcc6666", "0xFF6666", "0xFF9999"],
+        "07":["0xcc0066", "0xff0099", "0xff00cc"],
+        "08":["0xCCCC00", "0xCCCC33", "0xCCCC66"],
+        "09":["0x3333cc", "0x6666cc", "0x9999cc"],
+        "10":["0xff3300", "0xff6633", "0xff9966"]
+	},
+	"theme":{
+		"background":"0x333333",
+		"font":{"color":"0xffffff"},
+		"comment-button":{"font":{"color":"0xffffff"}},
+		"title":{"font":{"name":"Gill Sans Bold", "embedded":"false", "bold":true, "size":26}},
+		"graphs":{"title":{"font":{"color":"0xffffff"}}, "axes":{"color":"0xffffff", "labels":{"font":{"color":"0xffffff"}}}},
+		"bubble-chart":"circular",
+		"slider":{"font":{"color":"0xffffff"}},
+		"view-buttons":{"font":{"color":"0xffffff", "name":"Gill Sans"}, "selected-color":"0x333333", "deselected-color":"0x666666"},
+		"uk-bubble-chart":{"background":"0x333333", "innerfont":{"color":"0xffffff"}, "outerfont":{"color":"0xffffff"}},
+		"national-bubble-chart":{"background":"0x333333",  "innerfont":{"color":"0xffffff"}, "outerfont":{"color":"0xffffff"}},
+		"long-term":{"long-term-label":{"font":{"size":15}}, "tme-label":{"font":{"size":15}}},
+		"national-trends":{"title":{"font":{"size":18}}},
+		"regional-drilldown":{"background":"0x333333", "title":{"font":{"color":"0xffffff", "size":18}},
+							  "subfunctions":{"font":{"color":"0xffffff"}},
+							  "next-button":{"color":"0xffffff", "font":{"color":"0xffffff"}},
+							  "prev-button":{"color":"0xffffff", "font":{"color":"0xffffff"}}},
+		"daily-bread":{"background":"0xffffff", "font":{"color":"0x000000"}, "slider":{"font":{"color":"0x000000"}}, "select":{}, "salary":{"title":{}, "value":{"font":{"color":"0x8d1d4a"}}}, "tax":{"title":{}, "value":{"font":{"color":"0x8d1d4a"}}}},
+		"comparatron-a":{"background":"0x333333", "title":{"font":{"color":"0xffffff", "size":"18"}}}
+	},
+	"currencyPrefix":"£",
+	"currencySuffix":""
+}

html-template/flash/swfaddress.js

+/**
 * SWFAddress 2.4: Deep linking for Flash and Ajax <http://www.asual.com/swfaddress/>
 *
 * SWFAddress is (c) 2006-2009 Rostislav Hristov and contributors
 * This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
 *
 */

html-template/flash/swfobject.js

+/*	SWFObject v2.2 <http://code.google.com/p/swfobject/> 
+	is released under the MIT License <http://www.opensource.org/licenses/mit-license.php> 
+*/
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();

html-template/flash/table4.1.json.zlib

Binary file added.

html-template/flash/table4.2.json.zlib

Binary file added.

html-template/index.template.html

 	</style>
 		<title>Where Does My Money Go?</title>
 		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
-		<script type="text/javascript" src="swfobject.js"></script>
-		<script type="text/javascript" src="swfaddress.js"></script>
+		<script type="text/javascript" src="flash/swfobject.js"></script>
+		<script type="text/javascript" src="flash/swfaddress.js"></script>
 		<script type="text/javascript">
 			swfobject.registerObject("wdmmg", "9.0.0");
 		</script>
+		<script type="text/javascript" src="callbacks.js"></script>
 		<style type="text/css" media="screen">
 			html, body {
 				background: #fdba13;
 		<div id="wrapper">
 			<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%" id="wdmmg">
 				<param name="movie" value="main.swf" />
+				<param name="flashvars" value="assets=flash/"/>
 				<!--[if !IE]>-->
 				<object type="application/x-shockwave-flash" data="main.swf" width="100%" height="100%">
+				<param name="flashvars" value="assets=flash/"/>
 				<!--<![endif]-->
 					<a href="http://www.adobe.com/go/getflashplayer">
 						<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />

libs/PureMVC_AS3_2_0_4.swc

Binary file added.

libs/SWFAddress.swc

Binary file added.

libs/as3ds.swc

Binary file added.

libs/corelib.swc

Binary file added.

libs/flare.swc

Binary file added.

libs/greensock.swc

Binary file added.

src/com/iconomical/wdmmg/StartupCommand.as

 package com.iconomical.wdmmg
 {
 	import com.iconomical.wdmmg.proxies.ConfigProxy;
-	import com.iconomical.wdmmg.proxies.DataStoreProxy;
+	import com.iconomical.wdmmg.proxies.JavascriptProxy;
 	import com.iconomical.wdmmg.proxies.PESADataProxy;
 	import com.iconomical.wdmmg.proxies.SWFAddressProxy;
+	import com.iconomical.wdmmg.proxies.WDMMGAssets2Proxy;
 	import com.iconomical.wdmmg.proxies.WDMMGAssetsProxy;
 	
+	import flash.display.LoaderInfo;
+	import flash.events.Event;
+	
 	import org.puremvc.as3.interfaces.INotification;
 	import org.puremvc.as3.patterns.command.SimpleCommand;
 
 	public class StartupCommand extends SimpleCommand
 	{
+		private var wdmmg:WDMMG;
+		
 		override public function execute(notification:INotification):void {
-			facade.registerProxy(new ConfigProxy(ConfigProxy.NAME));
+			wdmmg = notification.getBody() as WDMMG;
+			
+			if (wdmmg.stage) {
+				init();
+			} else {
+				wdmmg.addEventListener(Event.ADDED_TO_STAGE, stageListener);
+			}
+		}
+		
+		private function init():void {
+			var paramObj:Object = LoaderInfo(wdmmg.stage.root.loaderInfo).parameters;
+			facade.registerProxy(new ConfigProxy(ConfigProxy.NAME, paramObj['assets']));
 			facade.registerProxy(new PESADataProxy(PESADataProxy.NAME));
 			facade.registerProxy(new WDMMGAssetsProxy(WDMMGAssetsProxy.NAME));
+			facade.registerProxy(new WDMMGAssets2Proxy(WDMMGAssets2Proxy.NAME));
 			facade.registerProxy(new SWFAddressProxy(SWFAddressProxy.NAME));
 			
-			facade.registerMediator(new WDMMGAppMediator(WDMMGAppMediator.NAME, notification.getBody()));
+			var jp:JavascriptProxy = new JavascriptProxy(JavascriptProxy.NAME);
+			facade.registerProxy(jp);
+			
+			facade.registerMediator(new WDMMGAppMediator(WDMMGAppMediator.NAME, wdmmg));
+
+			// Allow Javascript to do last-minute config
+			jp.initNotification();			
+		}
+		
+		private function stageListener(e:Event):void {
+			init();
 		}
 	}
 }

src/com/iconomical/wdmmg/WDMMG.mxml

             			fontFamily="Verdana" fontWeight="bold" fontSize="9" 
             			color="#ffffff"/>
             	</mx:HBox>
-            	<mx:HBox width="100%" backgroundColor="#333333">
-	                <mx:Text htmlText="{titleLabel}" textAlign="center"
+            	<mx:HBox width="100%">
+	                <mx:Text id="wdmmgTitle" htmlText="{titleLabel}" textAlign="center"
 	                    paddingLeft="15" paddingTop="10" paddingRight="5" paddingBottom="5" width="100%"
 	                    fontFamily="Gill Sans Bold" fontSize="26" color="#ffffff" />            		
             	</mx:HBox>
     </mx:VBox>
     
     <!-- Bottom line contains comments link and colophon -->
-    <mx:HBox width="100%" paddingBottom="5">
+    <mx:HBox width="100%" paddingBottom="5" id="footer">
         <mx:Spacer width="5"/>
         <ns:CommentButton id="comments"/>
         <mx:Spacer width="100%"/>

src/com/iconomical/wdmmg/WDMMGAppMediator.as

 
 package com.iconomical.wdmmg
 {
+	import com.iconomical.wdmmg.charts.bubblechart.BubbleChart;
 	import com.iconomical.wdmmg.commands.InitialiseInterface;
 	import com.iconomical.wdmmg.proxies.ConfigProxy;
+	import com.iconomical.wdmmg.proxies.JavascriptProxy;
 	import com.iconomical.wdmmg.proxies.PESADataProxy;
+	import com.iconomical.wdmmg.proxies.WDMMGAssets2Proxy;
 	import com.iconomical.wdmmg.proxies.WDMMGAssetsProxy;
+	import com.iconomical.wdmmg.views.mediators.ViewMediator;
 	
 	import org.puremvc.as3.interfaces.INotification;
-	import org.puremvc.as3.patterns.mediator.Mediator;
 
-	public class WDMMGAppMediator extends Mediator
+	public class WDMMGAppMediator extends ViewMediator
 	{
 		public static const NAME:String = "WDMMGAppMediator";
 		public static const INIT_INTERFACE:String = NAME+"InitInterface";
 		
 		private var haveConfig:Boolean = false;
-		private var havePesaData:Boolean = false;
+		private var havePesaData:Boolean = true;
+		private var haveAssets:Boolean = false;
+		private var haveAssets2:Boolean = false;
 		
 		public function WDMMGAppMediator(mediatorName:String=null, viewComponent:Object=null) {
 			super(mediatorName, viewComponent);
 		
 		override public function onRegister():void {
 			sendNotification(WDMMGAssetsProxy.LOAD_ASSETS);
+			sendNotification(WDMMGAssets2Proxy.LOAD_ASSETS);
+			sendNotification(PESADataProxy.LOAD_DATA);
 		}
 		
 		override public function listNotificationInterests():Array {
 			return [
 				WDMMGAssetsProxy.ASSETS_LOADED,
+				WDMMGAssets2Proxy.ASSETS_LOADED,
 				PESADataProxy.DATA_LOADED,
-				ConfigProxy.CONFIG_LOADED
+				ConfigProxy.CONFIG_LOADED,
+				JavascriptProxy.REMOVE_HEADER,
+				JavascriptProxy.REMOVE_FOOTER
 			];
 		}
 		
 			switch (notification.getName()) {
 			case PESADataProxy.DATA_LOADED:
 				havePesaData = true;
-				if (havePesaData && haveConfig) sendNotification(INIT_INTERFACE, wdmmg);
+				tryStartup();
 				break;
 				
 			case ConfigProxy.CONFIG_LOADED:
 				haveConfig = true;
-				if (havePesaData && haveConfig) sendNotification(INIT_INTERFACE, wdmmg);
+				tryStartup();
 				break;
-				
-			case WDMMGAssetsProxy.ASSETS_LOADED:
-				sendNotification(PESADataProxy.LOAD_DATA);
-				break;
+                                
+            case WDMMGAssetsProxy.ASSETS_LOADED:
+                haveAssets = true;
+                tryStartup();
+                break;
+                                
+            case WDMMGAssets2Proxy.ASSETS_LOADED:
+                haveAssets2 = true;
+                tryStartup();
+                break;
+                
+            case JavascriptProxy.REMOVE_HEADER:
+            	removeHeader();
+            	break;
+            
+            case JavascriptProxy.REMOVE_FOOTER:
+            	removeFooter();
+            	break;
+   			}
+		}
+		
+		private function tryStartup():void {
+            if (haveAssets && haveAssets2 && haveConfig && havePesaData) {
+            	theme();
+            	sendNotification(INIT_INTERFACE, wdmmg);
+            }
+		}
+		
+		private function theme():void {
+			applyBackground("background", wdmmg);
+			applyFontTheme("title.font", wdmmg.wdmmgTitle);
+			applyFontTheme("comment-button.font", wdmmg.comments.label_widget);
+			var bubbleChartType:String = configProxy.getThemeInfo("bubble-chart") as String;
+			BubbleChart.chartType = bubbleChartType;
+		}
+		
+		private function removeHeader():void {
+			if (wdmmg.contains(wdmmg.header)) {
+				wdmmg.removeChild(wdmmg.header);
+			}
+		}
+		
+		private function removeFooter():void {
+			if (wdmmg.contains(wdmmg.footer)) {
+				wdmmg.removeChild(wdmmg.footer);
 			}
 		}
 		

src/com/iconomical/wdmmg/charts/AxesChart.as

 {
     import com.iconomical.wdmmg.data.WDMMGAssets;
     
+    import flash.display.DisplayObject;
     import flash.display.Graphics;
     import flash.display.Sprite;
     import flash.geom.Rectangle;
         private static const MIN_WIDTH:Number = 160;
         private static const DEF_HEIGHT:Number = 120;
         private static const DEF_WIDTH:Number = 220;
-        private static const INTERVALS:Array = [1, 2.5, 5, 10, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000, 7500,
-                                        10000, 20000, 25000, 50000, 75000, 100000];
+        private static const BASE_INTERVALS:Array = [1, 2.5, 5, 7.5];
         private static const MAX_YAXIS_LABELS:int = 6;
         private static const XAXIS_LABEL_SIZE:int = 9;
         private static const YAXIS_LABEL_SIZE:int = 10;
+        private static const BAR_LABEL_SIZE:int = 11;
         
         private static var defXAxisFormat:TextFormat = null;
         private static var defYAxisFormat:TextFormat = null;
+        private static var defBarFormat:TextFormat = null;
         
 		protected var minDataValue:Number;
 		protected var maxDataValue:Number;
 		protected var keyLabels:Array;
         protected var maxValueLabelSize:Rectangle = new Rectangle(0, 0, 0, 0);
         protected var maxKeyLabelSize:Rectangle = new Rectangle(0, 0, 0, 0);
+        protected var absValueLabels:Boolean = false;
         
         protected var curHeight:Number = MIN_HEIGHT;
         protected var curWidth:Number = MIN_WIDTH;
         protected var keyLabelFormat:TextFormat;
         protected var valueLabelFormat:TextFormat;
+        protected var barLabelFormat:TextFormat;
+        
+        protected var axesColor:uint = 0xffffff;
+        protected var axesLabelsColor:uint = 0xffffff;
         
         // Clip which contains text labels
         protected var labelsContainer:Sprite = null;
                 defXAxisFormat = new TextFormat;
                 defXAxisFormat.font = font.fontName;
                 defXAxisFormat.size = XAXIS_LABEL_SIZE;
-                defXAxisFormat.color = 0xffffff;
+                defXAxisFormat.color = axesLabelsColor;
                 
                 defYAxisFormat = new TextFormat;
                 defYAxisFormat.font = font.fontName;
                 defYAxisFormat.size = YAXIS_LABEL_SIZE;
-                defYAxisFormat.color = 0xffffff;
+                defYAxisFormat.color = axesLabelsColor;
+                
+                defBarFormat = new TextFormat;
+                defBarFormat.font = font.fontName;
+                defBarFormat.bold = true;
+                defBarFormat.size = BAR_LABEL_SIZE;
+                defBarFormat.color = axesLabelsColor;
             }
             keyLabelFormat = defXAxisFormat;
             valueLabelFormat = defYAxisFormat;
+            barLabelFormat = defBarFormat;
             
             labelsContainer = this;
 		}
          */		
         public function setData(info:Object):void {
             this.plotSettings = info;
+            applyTheme();
             redraw();
         }
         
             var g:Graphics = this.graphics;
             g.clear();
             
-            for each (var tf:TextField in keyLabels) labelsContainer.removeChild(tf);
-            for each (tf in valueLabels) labelsContainer.removeChild(tf);
+            deleteAxesLabels();
             
             keyLabels = new Array;
             valueLabels = new Array;
             positionAxisLabels();
 		}
 		
+		protected function deleteAxesLabels():void {
+            for each (var o:DisplayObject in keyLabels) labelsContainer.removeChild(o);
+            for each (o in valueLabels) labelsContainer.removeChild(o);
+		}
+		
 		
 		/**
 		 * Find maximum and minimum data values.
         protected function determinePlotArea():Rectangle { return null; }
         
         
+        override protected function applyTheme():void {
+        	var themeInfo:Object = plotSettings['options']['theme'];
+        	if (themeInfo) {
+        		if (themeInfo['axes']) {
+        			if (themeInfo['axes']['color']) {
+        				axesColor = uint(themeInfo['axes']['color']);
+        			}
+        			if (themeInfo['axes']['labels']['font']['color']) {
+        				axesLabelsColor = uint(themeInfo['axes']['labels']['font']['color']);
+        				keyLabelFormat.color = axesLabelsColor;
+        				valueLabelFormat.color = axesLabelsColor;
+        				barLabelFormat.color = axesLabelsColor;
+        			}
+        		}
+        	}
+        }
+        
         /**
          * Work out plotting data range.
          * 
          * 
          */        
         private function evaluateInterval(range:Number):int {
-            for each (var i:int in INTERVALS) {
-                var n:int = Math.ceil(range/i);
-                if (n < MAX_YAXIS_LABELS) {
-                    return i;
-                }
+            var m:Number = range/MAX_YAXIS_LABELS;
+            var p:Number = getPower10(m);
+            var e:Number = Math.pow(10, Math.round(p));
+            
+            var newInterval:int = 1;
+            for each (var i:int in BASE_INTERVALS) {
+            	var n:Number = e*i;
+            	if (n > m) {
+            		newInterval = n;
+            		break;
+            	}
             }
-            return -1;
+            
+            return newInterval;
         }
         
+		
+		private function getPower10(n:Number):Number {
+			var p:Number = Math.log(n)/Math.log(10);
+			return p;
+		}
         
         /**
          * Generate labels for values. 
                 tf.embedFonts = true;
                 tf.selectable = false;
                 tf.autoSize = TextFieldAutoSize.LEFT;
-                tf.text = i.toString();
+                tf.text = absValueLabels ? Math.abs(i).toString() : i.toString();
                 labelsContainer.addChild(tf);
                 valueLabels.push(tf);
                 maxValueLabelSize = maxValueLabelSize.union(new Rectangle(0, 0, tf.width, tf.height));
          * Generate labels for keys 
          * 
          */        
-        private function makeKeyLabels():void {
+        protected function makeKeyLabels():void {
             if (plotSettings.options.xAxisLabels) {
                 for each (var label:String in plotSettings.options.xAxisLabels) {
                     var tf:TextField = new TextField;

src/com/iconomical/wdmmg/charts/BaseChart.as

 		}
 		
 		public function dispose():void {
-			if (curPopup) {
+			if (curPopup && curPopup.parent) {
 				curPopup.parent.removeChild(curPopup);
 			}
 			deleteBars();
 		
 		public function filterOnFunction(filter:Object):void {}
 		
+		protected function applyTheme():void {}
+		
         protected function makeBar(label:String, value:String, data:Object = null):ChartComponent {
             var bar:ChartComponent = new ChartComponent(label, value, data);
             addComponent(bar);

src/com/iconomical/wdmmg/charts/ChartContainer.mxml

     		[Bindable]
     		public var chart:UIComponent = null;
     		
+    		[Bindable]
+    		public var titleColor:uint = 0xffffff;
+    		
     		public function dispose():void {
     			if (chart is Disposable)
     				(chart as Disposable).dispose();
     	]]>
     </mx:Script>
     <mx:VBox width="100%" height="100%" verticalGap="0">
-		<ns:ChartLabel id="chartLabel" text="{labelText}"/>
+		<ns:ChartLabel id="chartLabel" text="{labelText}" color="{titleColor}"/>
 		<mx:VBox id="box" width="100%" height="100%"/>
 		<mx:Spacer height="10" width="100%"/>
     </mx:VBox>

src/com/iconomical/wdmmg/charts/ComparatronAChart.as

+//
+// Copyright (c) 2009,2010 Iconomical.
+// All Rights Reserved.
+//
+// The copyright above and this notice must be preserved in all
+// copies of this source code.
+//
+// See the file LICENSE.txt for license details.
+//
+
+package com.iconomical.wdmmg.charts
+{
+	import com.iconomical.wdmmg.data.WDMMGAssets;
+	import com.iconomical.wdmmg.views.events.ViewUserEvent;
+	
+	import flash.display.BitmapData;
+	import flash.display.DisplayObject;
+	import flash.display.Graphics;
+	import flash.events.MouseEvent;
+	import flash.geom.Rectangle;
+	import flash.text.Font;
+	import flash.text.TextField;
+	import flash.text.TextFieldAutoSize;
+	import flash.text.TextFormat;
+	
+	import mx.core.UIComponent;
+	
+	public class ComparatronAChart extends AxesChart
+	{
+		public static const CODE_CLICK_EVENT:String = "CodeClickEvent";
+		
+        private static const KEY_LABEL_SIZE:int = 11;
+		private static const GUTTER:Number = 4;
+		private static const MAX_BAR_HEIGHT:Number = 18;
+		private static const GRIDLINE_COLOR:uint = 0xada095;
+		
+		private static var defKeyLabelFormat:TextFormat = null;
+		
+		private var barLabels:Array = new Array;
+		private var grid:UIComponent = new UIComponent;
+		
+		
+		public function ComparatronAChart() {
+            if (!defKeyLabelFormat) {
+                var assets:WDMMGAssets = WDMMGAssets.instance;
+                var font:Font = assets.getFont(WDMMGAssets.defaultFontName);
+                
+                defKeyLabelFormat = new TextFormat;
+                defKeyLabelFormat.font = font.fontName;
+                defKeyLabelFormat.size = KEY_LABEL_SIZE;
+                defKeyLabelFormat.color = 0xffffff;
+			}
+			keyLabelFormat = defKeyLabelFormat;
+			
+			absValueLabels = true;
+
+			addChild(grid);
+		}
+		
+		override protected function redraw():void {
+			super.redraw();
+			
+            if (!plotSettings) return;
+            
+            drawBars();
+        }
+
+        override protected function findDataMinMax():void {
+        	if (plotSettings.options.spending == "per_capita") {
+	        	for each (var o:Object in plotSettings.data) {
+    	    		maxDataValue = Math.max(maxDataValue, Math.max(o['region1'], o['region2']));
+        			minDataValue = Math.min(minDataValue, Math.min(o['region1'], o['region2']));
+        			if (isNaN(maxDataValue) || isNaN(minDataValue)) {
+        				trace("NaN!");
+        			}
+            	}
+            } else {
+	        	for each (o in plotSettings.data) {
+	        		maxDataValue = Math.max(maxDataValue, o.pc_diff);
+	        		minDataValue = Math.min(minDataValue, o.pc_diff);
+	        	}
+            }
+            maxDataValue = Math.max(Math.abs(minDataValue), Math.abs(maxDataValue));
+            minDataValue = -maxDataValue;
+            trace("max:"+maxDataValue);
+            trace("min:"+minDataValue);
+        }
+        
+        override protected function makeKeyLabels():void {
+            if (plotSettings.options.xAxisLabels) {
+            	for (var i:int = 0; i < plotSettings.options.xAxisLabels.length; i++) {
+                	var label:String = plotSettings.options.xAxisLabels[i];
+                	var code:String = plotSettings.options.codes[i];
+                	var tf:CodeLabel = new CodeLabel(label, code, keyLabelFormat);
+                    maxKeyLabelSize = maxKeyLabelSize.union(new Rectangle(0, 0, tf.width, tf.height));
+                    labelsContainer.addChild(tf);
+                    keyLabels.push(tf);
+                    if (code.indexOf(".") == -1) {
+						tf.addEventListener(MouseEvent.CLICK, labelClickListener);
+				        tf.useHandCursor = true;
+				        tf.mouseChildren = false;
+				        tf.buttonMode = true;                    	
+                    }
+                }
+            }
+        }
+        
+		override protected function positionAxisLabels():void {
+			var h:Number = plotArea.height/keyLabels.length;
+			var yPos:Number = plotArea.y;
+            for (var i:int = 0; i < keyLabels.length; i++) {
+                var tf:DisplayObject = keyLabels[i];
+                tf.x = 0;
+                tf.y = yPos + (h-tf.height)/2;
+                yPos += h;
+            }
+           	
+           	var w:Number = plotArea.width/(valueLabels.length-1);
+			for (i = 0; i < valueLabels.length; i++) {
+				tf = valueLabels[i];
+				tf.x = plotArea.x+i*w - tf.width/2;
+				tf.y = plotArea.height;
+			}           	
+		}
+		
+		override protected function deleteAxesLabels():void {
+			for each (var o:DisplayObject in keyLabels) {
+				o.removeEventListener(MouseEvent.CLICK, labelClickListener);
+			}
+			super.deleteAxesLabels();
+		}
+		
+		override protected function drawAxes():void {
+			var g:Graphics = this.graphics;
+			g.clear();
+			
+			g.lineStyle(1, axesColor);
+			g.moveTo(plotArea.x, plotArea.bottom);
+			g.lineTo(plotArea.right, plotArea.bottom);
+			
+			g.moveTo(plotArea.x+plotArea.width/2, plotArea.top);
+			g.lineTo(plotArea.x+plotArea.width/2, plotArea.bottom);
+		}
+		
+        override protected function determineChartArea():Rectangle {
+            var r:Rectangle = new Rectangle;
+            r.x = maxKeyLabelSize.width;
+            r.y = 0;
+            r.right = curWidth-r.x;
+            r.bottom = curHeight;
+            return r;
+        }
+        
+        override protected function determinePlotArea():Rectangle {
+            var r:Rectangle = new Rectangle;
+            r.x = chartArea.x+valueLabels[0].width/2;
+            r.y = chartArea.y;
+            r.right = chartArea.right-valueLabels[valueLabels.length-1].width/2;
+            r.bottom = curHeight-valueLabels[0].height;
+            return r;
+		}
+        
+        private function drawBars():void {
+        	var g:Graphics = grid.graphics;
+        	g.clear();
+        	
+        	var dot:BitmapData = new BitmapData(5, 1, false);
+        	dot.fillRect(new Rectangle(0, 0, 3, 1), GRIDLINE_COLOR);
+        	
+        	deleteBars();
+        	
+        	for each (var tf:TextField in barLabels) {
+        		elementsContainer.removeChild(tf);
+        	}
+        	barLabels = new Array;
+        	
+            var colHeight:Number = plotArea.height/plotSettings.data.length;
+            var barHeight:Number = Math.min(MAX_BAR_HEIGHT, colHeight-GUTTER);
+        	var yPos:Number = (colHeight-barHeight)/2;
+        	var midX:Number = plotArea.x + plotArea.width/2;
+        	var colY:Number = 0;
+        	
+        	for each (var o:Object in plotSettings.data) {
+    			var label:String = "+"+Math.abs(o.pc_diff)+"%";
+    			tf = new TextField;
+    			tf.defaultTextFormat = barLabelFormat;
+    			tf.autoSize = TextFieldAutoSize.LEFT;
+    			tf.text = label;
+    			tf.mouseEnabled = false;
+    			tf.selectable = false;
+    			
+    			var bar:ChartComponent = drawBar("Per capita", Math.abs(o.pc_diff), o.label, o.color, barHeight);
+    			if (o.pc_diff < 0) {
+    				bar.x = midX - bar.width;
+    				tf.x = bar.x - tf.width;
+    				if (tf.x < plotArea.x) {
+    					tf.x = midX - tf.width;
+    				}
+    			} else {
+    				bar.x = midX;
+    				tf.x = bar.x + bar.width;
+    				if ((tf.x+tf.width) > plotArea.right) {
+    					tf.x = midX;
+    				}
+    			}
+
+    			elementsContainer.addChild(tf);
+    			barLabels.push(tf);
+    			
+    			if (colY != 0) {
+    				g.beginBitmapFill(dot, null, true, true);
+    				g.drawRect(plotArea.x, Math.floor(colY), plotArea.width, 1);
+    				g.endFill();
+    			}
+    			
+    			bar.y = yPos;
+    			tf.y = yPos;
+    			yPos += colHeight;
+    			colY += colHeight;
+        	}
+        }
+        
+        private function drawBar(name:String, value:Number, label:String, color:uint, barHeight:Number):ChartComponent {
+        	var bar:ChartComponent = makeBar(name, label);
+        	var w:Number = (dataRange != 0) ? plotArea.width*Math.abs(value)/dataRange : 0;
+        	var g:Graphics = bar.graphics;
+        	g.beginFill(color);
+        	g.drawRect(0, 0, w, barHeight);
+        	g.endFill();
+        	return bar;
+        }
+        
+        private function labelClickListener(e:MouseEvent):void {
+        	var label:CodeLabel = e.currentTarget as CodeLabel;
+        	var code:String = label.code;
+        	dispatchEvent(new ViewUserEvent(CODE_CLICK_EVENT, code));
+        }
+	}
+}
+
+
+	import flash.text.TextFormat;
+	import flash.text.TextField;
+	import flash.display.Sprite;
+	import flash.text.TextFieldAutoSize;
+	
+
+class CodeLabel extends Sprite {
+	public var tf:TextField;
+	public var code:String;
+	
+	public function CodeLabel(label:String, code:String, format:TextFormat) {
+		this.code = code;
+		tf = new TextField;
+		tf.defaultTextFormat = format;
+		addChild(tf);
+        tf.embedFonts = true;
+        tf.selectable = false;
+        tf.autoSize = TextFieldAutoSize.LEFT;
+        tf.text = label;
+	}
+}

src/com/iconomical/wdmmg/charts/HorizontalAxesChart.as

         
         override protected function positionAxisLabels():void {
             var g:Graphics = this.graphics;
-            g.lineStyle(1, 0xffffff);
+            g.lineStyle(1, axesColor);
 
             for (var i:int = 0; i < valueLabels.length; i++) {
                 var tf:TextField = valueLabels[i];
         
         override protected function drawAxes():void {
             var g:Graphics = this.graphics;
-            g.lineStyle(1, 0xffffff);
+            g.lineStyle(1, axesColor);
 
             // X-axis
             g.moveTo(chartArea.x, chartArea.bottom);

src/com/iconomical/wdmmg/charts/OldBubbleChart.as

+//
+// Copyright (c) 2009,2010 Iconomical.
+// All Rights Reserved.
+//
+// The copyright above and this notice must be preserved in all
+// copies of this source code.
+//
+// See the file LICENSE.txt for license details.
+//
+//
+
+package com.iconomical.wdmmg.charts
+{
+	import com.greensock.TimelineMax;
+	import com.greensock.TweenMax;
+	import com.iconomical.wdmmg.components.DataPopup;
+	import com.iconomical.wdmmg.views.events.ViewUserEvent;
+	
+	import flash.display.Graphics;
+	import flash.display.Sprite;
+	import flash.events.Event;
+	import flash.events.MouseEvent;
+	import flash.geom.Point;
+	import flash.geom.Rectangle;
+	
+	import mx.core.UIComponent;
+	import mx.formatters.NumberBase;
+	import mx.formatters.NumberBaseRoundType;
+	
+	
+	/**
+	 * Bubble chart.
+	 *  
+	 * @author dave
+	 * 
+	 */	
+	public class OldBubbleChart extends BaseChart
+	{
+		public static const FUNCTION_SELECTED:String = "FunctionSelectedEvent";
+		public static const LINK_CLICK_EVENT:String = "LinkClickEvent";
+		
+		private static const TEXT_SIZES:Array = [20, 12];
+		private static const DEF_MAIN_RADIUS:int = 100;
+		private static const MAX_RADIUS:int = 500;
+		private static const FADE_IN_TIME:Number = 0.5;
+		private static const FADE_OUT_TIME:Number = 0.5;
+		
+		
+		private var initialised:Boolean = false;
+		private var doneDrop:Boolean = false;
+		private var totalValue:Number;
+		private var mainRadius:Number = DEF_MAIN_RADIUS;
+        private var mainBubble:Bubble = null;
+        private var curMainBubble:Bubble = null;		
+		private var curBubbles:Object = null;
+		private var curPopup:DataPopup = null;
+		private var centreP:Point = null;
+		private var bubbleLayer:UIComponent = new UIComponent;
+		private var bubbleList:Array = null;
+		private var curWidth:Number = 0;
+		private var curHeight:Number = 0;
+		private var maxDepth:int = 1;
+		
+		
+		public function OldBubbleChart() {
+			addChild(bubbleLayer);
+		}
+		
+        public function setData(info:Object, overwrite:Boolean = false):void {
+            this.plotSettings = info;
+            maxDepth = determineMaxDepth(info.data);
+            trace("max depth:"+maxDepth);
+            if (!overwrite) {
+	            deleteAllBubbles();
+        	    makeBubbles();
+                curMainBubble = mainBubble;
+            } else {
+            	resetBubbles(info.data, 0);
+            	validateNumbers();
+            }
+            
+            mainRadius = DEF_MAIN_RADIUS;
+            reconfigureBubbles(curMainBubble);
+            
+            doneDrop = false;
+            if (initialised) {
+	            reposition();
+	            if (overwrite) {
+		            translate();
+	            } else {
+	            	dropInPlace();
+	            }
+	        }
+        }
+        
+        public function highlightCode(code:String):void {
+        	var bubble:Bubble = curBubbles[code];
+        	if (initialised && mainBubble && bubble) {
+    			curMainBubble = bubble;
+    			mainRadius = DEF_MAIN_RADIUS;
+    			reconfigureBubbles(bubble);
+    			reposition();
+    			TweenMax.killAll();
+    			translate();
+        	}
+        }
+        
+        public function get selectedCode():String {
+        	return curMainBubble ? curMainBubble.bubbleId : null;
+        }
+        
+        override public function dispose():void {
+        	super.dispose();
+        	deleteAllBubbles();
+        	if (curPopup) {
+        		curPopup.removeEventListener(DataPopup.LINK_CLICK_EVENT, popupClickListener);
+        		if (curPopup.parent) curPopup.parent.removeChild(curPopup);
+        		curPopup.dispose();
+        	}
+        }
+        
+        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
+        	super.updateDisplayList(unscaledWidth, unscaledHeight);
+        	curWidth = unscaledWidth;
+        	curHeight = unscaledHeight;
+        	initialised = true;
+            redraw();
+        }
+        
+        protected function redraw():void {
+        	if (!curMainBubble || !initialised) return;
+        	if (curWidth == 0 || curHeight == 0) return;
+        	
+        	reposition();
+        	if (!doneDrop) {
+        		dropInPlace();
+        	}
+        }
+        
+        private function reposition():void {
+        	if (curMainBubble == mainBubble) {
+        		drawCentred();
+        	} else if (!atDeepestLevel(curMainBubble.childBubbles)) {
+        		drawOffset();
+	        } else {
+	        	drawLinear();
+	        }
+        }
+        
+        private function dropInPlace():void {
+        	TweenMax.killChildTweensOf(bubbleLayer);
+        	
+        	var tweens:Array = new Array;
+        	for each (var bubble:Bubble in curBubbles) {
+        		if (bubble.targetVisible) {
+        			bubble.x = bubble.targetX;
+        			bubble.y = bubble.targetY;
+	        		tweens.push(TweenMax.fromTo(bubble, 0.5,
+    	    									{alpha:1.0, radius:bubble.targetRadius},
+        										{autoAlpha:1.0, radius:bubble.targetRadius}));
+        		} else {
+        			tweens.push(TweenMax.to(bubble, 0.5, {autoAlpha:0.0}));
+        		}
+        	}
+        	var timeline:TimelineMax = new TimelineMax({tweens:tweens});
+        	timeline.play();
+        }
+        
+        private function translate():void {
+        	TweenMax.killChildTweensOf(bubbleLayer);
+
</