<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:fl="rw.*"
minWidth="600" minHeight="400"
creationComplete="onLoad()"
viewSourceURL="srcview/index.html">
<fx:Declarations>
</fx:Declarations>
<fx:Script>
<![CDATA[
import flare.animate.*;
import flare.display.TextSprite;
import flare.util.Shapes;
import flare.util.Strings;
import flare.vis.Visualization;
import flare.vis.controls.ClickControl;
import flare.vis.controls.HoverControl;
import flare.vis.controls.TooltipControl;
import flare.vis.data.*;
import flare.vis.events.SelectionEvent;
import flare.vis.events.TooltipEvent;
import flare.vis.operator.label.Labeler;
import rw.*;
import spark.components.Button;
private var _vis:Visualization;
private var _layout:TreeMapLayout;
private var _labeler:TreeMapLabeler;
private var _data:Tree;
[Bindable]
private var _rootData:NodeSprite;
[Bindable]
private var _currentRootData:NodeSprite;
private var _minAge:int = 0;
private var _maxAge:int = 21;
private var _showMale:Boolean = true;
private var _showFemale:Boolean = true;
private var _maleHighlight:Sprite;
private var _femaleHighlight:Sprite;
private var _showLabels:Boolean = true;
public function onLoad():void {
_data = buildData();
_rootData = _data.root;
callLater(function():void {
stage.quality = StageQuality.LOW;
});
_vis = new TreeVisuakization(_data);
flexVis.visualization = _vis;
_rootData = _data.root;
_vis.bounds = getSizeRect();
var renderer:PeopleRenderer = new PeopleRenderer;
_vis.data.nodes.visit(function(n:NodeSprite):void {
n.buttonMode = true;
n.shape = Shapes.BLOCK;
n.renderer = renderer;
if(n.childDegree > 0) {
n.fillColor = 0;
n.mouseEnabled = false;
} else {
n.fillColor = ICD10.colorMap(String(n.data.path).substr(1,7)); n.fillAlpha = 0.7; }
n.lineColor = 0xffcccccc;
n.lineWidth = n.depth==1 ? 2 : (n.childDegree ? 1 : 0);
});
updateSizes();
_vis.data.edges["visible"] = false;
_layout = new TreeMapLayout();
_vis.setOperator('layout', _layout);
_labeler = new TreeMapLabeler(
"data.name",
Data.NODES, new TextFormat("Arial", 14, 0, true),
function(n:NodeSprite):Boolean {
return _showLabels && (n.depth == _currentRootData.depth+1);
},
Labeler.LAYER); _labeler.xOffset = 5;
_labeler.yOffset = 3;
_vis.setOperator('labeler', _labeler);
_currentRootData = _rootData;
_vis.update(null, 'layout').play();
_vis.update(null, 'labeler').play();
_vis.update();
updateLabels();
_vis.controls.add(new HoverControl(NodeSprite,
HoverControl.MOVE_AND_RETURN,
function(evt:SelectionEvent):void {
var n:NodeSprite = evt.node;
n.props['highlight'] = true;
n.dirty();
},
function(evt:SelectionEvent):void {
var n:NodeSprite = evt.node;
n.props['highlight'] = false;
n.dirty();
}
));
_vis.controls.add(new TooltipControl(NodeSprite, null,
function(evt:TooltipEvent):void {
var maleDeaths:int = getMaleDeaths(evt.node);
var femaleDeaths:int = getFemleDeaths(evt.node);
var descPath:String = '';
var iterNode:NodeSprite = evt.node.parentNode;
while(iterNode !== _rootData) {
descPath += iterNode.data.name + ' <i>[' + iterNode.data.code + ']</i><br>';
iterNode = iterNode.parentNode;
}
TextSprite(evt.tooltip).htmlText = Strings.format("<b>{0:,0} deaths</b> (<font color='#4DA3C1'>Male: {1:,0}</font>, <font color='#FC90A5'>Female: {2:,0}</font>)<br/><b>{3}</b> <i>[{4}]</i><br/>{5}",
maleDeaths + femaleDeaths,
maleDeaths,
femaleDeaths,
evt.node.data.name,
evt.node.data.code,
descPath
);
}
));
_vis.controls.add(new ClickControl(NodeSprite, 1, onDataClick));
}
public function updateSizes():void {
_rootData.visitTreeDepthFirst(function(n:NodeSprite):void {
n.data.deathsMale = 0;
n.data.deathsFemale = 0;
n.size = 0;
}, false);
_rootData.visitTreeDepthFirst(function(n:NodeSprite):void {
if (n.childDegree == 0) {
var dm:int = getMaleDeaths(n);
var df:int = getFemleDeaths(n);
n.data.deathsMale = dm;
n.data.deathsFemale = df;
n.size = dm + df;
var p:NodeSprite = n.parentNode;
while(p) {
p.data.deathsMale += dm;
p.data.deathsFemale += df;
p.size += dm + df;
p = p.parentNode;
}
}
}, false);
}
public function getMaleDeaths(n:NodeSprite):int {
var deaths:int = 0;
if(_showMale && n.data.male) {
for(var i:int = _minAge; i < _maxAge; i++) {
deaths += n.data.male[i];
}
}
return deaths;
}
public function getFemleDeaths(n:NodeSprite):int {
var deaths:int = 0;
if(_showFemale && n.data.female) {
for(var i:int = _minAge; i < _maxAge; i++) {
deaths += n.data.female[i];
}
}
return deaths;
}
public function onDataClick(evt:SelectionEvent):void {
var newRoot:NodeSprite = evt.node;
var newRootDapth:int = NodeSprite(_layout.layoutRoot).depth+1;
while(newRoot.depth > newRootDapth) newRoot = newRoot.parentNode;
updateRootNode(true, newRoot);
}
public function updateRootNode(useTrans:Boolean, newRoot:NodeSprite = null):void {
if(!newRoot) newRoot = _currentRootData;
breadCrumbs.removeAllElements();
var parentNode:NodeSprite = newRoot.parentNode;
while(parentNode) {
var newButton:Button = new Button;
newButton.label = parentNode.data.code;
newButton.toolTip = parentNode.data.name;
newButton.addEventListener(MouseEvent.CLICK, onCrumbClick);
breadCrumbs.addElementAt(newButton, 0);
parentNode = parentNode.parentNode;
}
_vis.data.nodes.visit(function(n:NodeSprite):void {
if(!inSubtree(newRoot, n) || n.size <= 0) {
n.visible = false;
}
});
_labeler.makeAllLablesInvisible();
_layout.layoutRoot = newRoot;
_currentRootData = newRoot;
updateLabels();
if(useTrans) {
var trans:Transitioner =new Transitioner(1);
trans.easing = Easing.easeInOutSine;
trans.addEventListener(TransitionEvent.END, onTransitionEndMakeVisible);
_vis.update(trans, 'layout').play();
} else {
_vis.update(null, 'layout').play();
onTransitionEndMakeVisible();
}
function onTransitionEndMakeVisible(e:TransitionEvent = null):void {
_vis.data.nodes.visit(function(n:NodeSprite):void {
n.lineWidth = (n.depth==_currentRootData.depth+1) ? 2 : (n.childDegree ? 1 : 0);
if(n.size > 0 && inSubtree(newRoot, n)) {
n.visible = true;
}
});
_vis.update(null, 'labeler').play(); }
function inSubtree(root:NodeSprite, node:NodeSprite):Boolean {
var parentAtRootDeapth:NodeSprite = node;
while(parentAtRootDeapth.depth > root.depth) parentAtRootDeapth = parentAtRootDeapth.parentNode;
return (parentAtRootDeapth === newRoot);
}
function onCrumbClick(e:MouseEvent):void {
var found:NodeSprite = null;
_vis.data.nodes.visit(function(n:NodeSprite):Boolean {
if(n.data.name == e.target.toolTip) {
found = n;
return true;
} else {
return false;
}
});
if(found) {
updateRootNode(true, found);
} else {
throw new Error('Unknown code: ' + e.target.toolTip);
}
}
}
public function onResize():void
{
if(!_vis) return;
_vis.bounds = getSizeRect();
updateRootNode(false);
}
public function getSizeRect():Rectangle {
return new Rectangle(
0,
0,
width - Number(flexVis.left) - Number(flexVis.right),
height - Number(flexVis.top) - Number(flexVis.bottom)
);
}
public function containsCode(parent:String, child:String):Boolean {
if(parent == child) return true;
var childMain:String = child.split('.')[0];
if(parent.indexOf('-') > 0) {
var bounds:Array = parent.split('-');
return (bounds[0] <= childMain) && (childMain <= bounds[1]);
} else {
return (parent == childMain);
}
}
public function buildData():Tree {
var startTime:Number = (new Date).time;
var tree:Tree = new Tree();
var root:NodeSprite = tree.addRoot();
root.data = {name:"All deaths", code:'A00-Z99', path:'/'};
var map:Object = {};
for each(var code:String in ICD10.sortedCodes) {
var attach:NodeSprite = root;
switch(code.length) {
case 7:
if(ICD10.attachMap.hasOwnProperty(code)) {
attach = map[ICD10.attachMap[code]];
} break;
case 3:
attach = map[ICD10.attachMap[code]];
break;
case 5:
attach = map[code.substr(0, 3)];
break;
default:
throw new Error('Invalid ICD10 code');
}
var description:String = ICD10.codes[code];
var path:String = attach.data.path + code + '/';
var newNode:NodeSprite = tree.addChild(attach);
newNode.data = {name:description, code:code, path:path};
delete attach.data.male;
delete attach.data.female;
var deathValues:Object = ICD10.deaths[code];
if(deathValues) {
if(deathValues.male) newNode.data['male'] = deathValues.male;
if(deathValues.female) newNode.data['female'] = deathValues.female;
}
if(code.length != 5) {
map[code] = newNode;
}
}
ICD10.codes = null;
ICD10.sortedCodes = null;
ICD10.deaths = null;
return tree;
}
public function updateLabels():void {
if(codeLabel) {
codeLabel.text = _currentRootData.data.code + ' /';
}
if(titleLabel) {
titleLabel.text = _currentRootData.data.name;
}
if(numbersLabel) {
numbersLabel.text = Strings.format("(T:{0:,0} M:{1:,0} F:{2:,0})",
_currentRootData.size,
_currentRootData.data.deathsMale,
_currentRootData.data.deathsFemale
);
}
}
public function onSliderChange():void {
_minAge = ageSlider.values[0];
_maxAge = ageSlider.values[1];
updateSizes();
updateRootNode(true);
}
public function onSexFilterChange():void {
if(!_rootData) return;
switch(sexFilter.selectedIndex) {
case 0:
_showMale = true;
_showFemale = true;
break;
case 1:
_showMale = true;
_showFemale = false;
break;
case 2:
_showMale = false;
_showFemale = true;
break;
default:
_showMale = true;
_showFemale = true;
break;
}
updateSizes();
updateRootNode(true);
}
public function onTextChange():void {
var filter:String = filterInput.text.toLowerCase();
var queryParts:Array = filter.split(' ');
var codeParts:Array = new Array;
var filterParts:Array = new Array;
for each(var q:String in queryParts) {
if(q == '') continue;
if(isCode(q)) {
codeParts.push(q.toUpperCase());
} else if(isPartialCode(q)) {
} else {
filterParts.push(q.toLowerCase());
}
}
_vis.data.nodes.visit(function(n:NodeSprite):void {
if(n.childDegree > 0) return;
var descPath:String = '';
var iterNode:NodeSprite = n;
while(iterNode !== _rootData) {
descPath += iterNode.data.name + ' ';
iterNode = iterNode.parentNode;
}
descPath = descPath.toLowerCase();
var strikes:int = 0;
var maxStrikes:int = 0;
if(codeParts.length > 0) {
maxStrikes += 2;
var codeCont:Boolean = false;
for each(var c:String in codeParts) {
if(containsCode(c, n.data.code)) {
codeCont = true;
break;
}
}
if(!codeCont) strikes += 2;
}
for each(var f:String in filterParts) {
maxStrikes += 1;
if(descPath.indexOf(f) == -1) strikes += 1;
}
if (strikes == 0) {
n.fillColor = ICD10.colorMap(String(n.data.path).substr(1,7)); } else {
n.fillSaturation = 0;
n.fillValue = (1 - strikes/maxStrikes)/2;
}
n.fillAlpha = 0.7; });
function isCode(code:String):Boolean {
var pattern:RegExp;
switch(code.length) {
case 7:
pattern = /\w\d\d-\w\d\d/;
break;
case 5:
pattern = /\w\d\d\.\d/;
break;
case 3:
pattern = /\w\d\d/;
break;
default:
return false;
}
return Boolean(pattern.exec(code));
}
function isPartialCode(code:String):Boolean {
var pattern:RegExp;
switch(code.length) {
case 6:
case 5:
pattern = /^\w\d\d-\w?\d?$/;
break;
case 4:
pattern = /^\w\d\d[\.-]/;
break;
case 3:
case 2:
case 1:
pattern = /^\w\d?\d?$/;
break;
default:
return false;
}
return Boolean(pattern.exec(code));
}
}
public function onShowLabelsClick():void {
_showLabels = showLabelsCheckBox.selected;
if(_showLabels) {
_vis.update(null, 'labeler').play();
} else {
_labeler.makeAllLablesInvisible();
}
}
]]>
</fx:Script>
<s:states>
<s:State name="Default"/>
<s:State name="More"/>
</s:states>
<s:HGroup id="breadCrumbs" left="20" top="10">
</s:HGroup>
<s:HGroup horizontalCenter="0" top="35" verticalAlign="middle">
<s:Label id="codeLabel" text="N/A" fontSize="14"/>
<s:Label id="titleLabel" fontSize="22" fontWeight="bold"/>
<s:Label id="numbersLabel" text="N/A" fontSize="14"/>
</s:HGroup>
<fl:FlexVis id="flexVis" left="20" right="20" top="60" bottom="75" resize="onResize()"/>
<s:Button top="10" right="20" label="More..." click="currentState = 'More'"/>
<s:Label text="Filter:" fontSize="18" bottom="10" left="20" text.Default="Highlight:"/>
<s:TextInput id="filterInput" bottom="10" left="74" change="{onTextChange()}" left.Default="100" restrict="0-9A-Za-z-. " bottom.Default="10" right="306"/>
<s:Button label="X" bottom="10" right="277" width="30" height="22" click="{filterInput.text = ''; onTextChange()}" cornerRadius="0"/>
<s:Label text="Show:" fontSize="18" bottom="10" right="218"/>
<s:DropDownList id="sexFilter" labelField="name" right="20" bottom="11" selectedIndex="0" change="onSexFilterChange()" width="190" fontSize="14">
<mx:ArrayCollection>
<fx:Object name="both males and females" value="0"/>
<fx:Object name="males only" value="1"/>
<fx:Object name="females only" value="2"/>
</mx:ArrayCollection>
</s:DropDownList>
<s:Group left="25" right="25" bottom="53">
<s:layout><s:TileLayout requestedColumnCount="21" columnAlign="justifyUsingWidth"/></s:layout>
<s:Label textAlign="center" text="< 1"/>
<s:Label textAlign="center" text="1-4"/>
<s:Label textAlign="center" text="5-9"/>
<s:Label textAlign="center" text="10-14"/>
<s:Label textAlign="center" text="15-19"/>
<s:Label textAlign="center" text="20-24"/>
<s:Label textAlign="center" text="25-29"/>
<s:Label textAlign="center" text="30-34"/>
<s:Label textAlign="center" text="35-39"/>
<s:Label textAlign="center" text="40-44"/>
<s:Label textAlign="center" text="45-49"/>
<s:Label textAlign="center" text="50-54"/>
<s:Label textAlign="center" text="55-59"/>
<s:Label textAlign="center" text="60-64"/>
<s:Label textAlign="center" text="65-69"/>
<s:Label textAlign="center" text="70-74"/>
<s:Label textAlign="center" text="75-79"/>
<s:Label textAlign="center" text="80-84"/>
<s:Label textAlign="center" text="85-89"/>
<s:Label textAlign="center" text="90-94"/>
<s:Label textAlign="center" text="95+"/>
</s:Group>
<mx:HSlider
id="ageSlider"
toolTip="Select the age range to display"
thumbCount="2"
minimum="0"
maximum="21"
values="{[0, 21]}"
left="20" right="20" bottom="38"
snapInterval="1"
tickColor="0x222222"
showDataTip="false"
showTrackHighlight="true"
labelOffset="2"
tickOffset="0"
tickValues="{[0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5, 20.5]}"
change="onSliderChange()"
/>
<s:Group includeIn="More" left="0" right="0" top="0" bottom="0">
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill><s:SolidColor color="0x000000" alpha="0.4"/></s:fill>
</s:Rect>
<s:Group width="400" height="400" horizontalCenter="0" verticalCenter="-40">
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill><s:SolidColor color="0xFFFFFF"/></s:fill>
<s:stroke><s:SolidColorStroke color="0x000000" weight="2"/></s:stroke>
</s:Rect>
<s:Label text="Deaths by cause and sex" horizontalCenter="0" top="12" fontSize="24"/>
<s:Label text="England and Wales 2007" horizontalCenter="0" top="39" fontSize="18"/>
<s:VGroup top="70" bottom="100" left="10" right="10" gap="10">
<s:VGroup gap="3">
<s:Label text="Description:" fontSize="14"/>
<s:Group width="100%">
<s:Label left="20" width="360" text="Deaths, repeatedly grouped by cause, are represented as rectangles where the area is proportional to the number of fatalities." textDecoration="none"/>
</s:Group>
</s:VGroup>
<s:VGroup gap="3">
<s:Label text="Highlighter usage:" fontSize="14"/>
<s:Group width="100%">
<s:Label left="20" width="360" text="Enter search terms and/or ICD10 code ranges separated by space. Example: “V20-V80 V90 driver” will highlight every datum whose code is ether between V20 and V80 or is V90 and whose description contains ‘driver’." textDecoration="none"/>
</s:Group>
</s:VGroup>
<s:VGroup gap="3">
<s:Label text="Visualization by Vadim Ogievetsky" fontSize="14"/>
<s:Group width="100%" buttonMode="true" useHandCursor="true" click="{navigateToURL(new URLRequest('mailto:vadim.ogievetsky@cs.stanford.edu'), '_blank')}">
<s:Label left="20" width="360" text="vadim.ogievetsky@cs.stanford.edu" color="#5AC85A" textDecoration="none"/>
</s:Group>
</s:VGroup>
<s:VGroup gap="3">
<s:Label text="Submited as Assignment 3 of CS488b Data Visualization" fontSize="14"/>
<s:Label text="(project updated after grading)" fontSize="12"/>
<s:Group width="100%" buttonMode="true" useHandCursor="true" click="{navigateToURL(new URLRequest('https://graphics.stanford.edu/wikis/cs448b-09-fall'), '_blank')}">
<s:Label left="20" width="360" text="https://graphics.stanford.edu/wikis/cs448b-09-fall" color="#5AC85A" textDecoration="none"/>
</s:Group>
</s:VGroup>
<s:VGroup gap="3">
<s:Label text="Data taken from the British death register" fontSize="14"/>
<s:Group width="100%" buttonMode="true" useHandCursor="true" click="{navigateToURL(new URLRequest('http://www.statistics.gov.uk/downloads/theme_health/DR2007/DR_07_2007.pdf'), '_blank')}">
<s:Label left="20" width="360" text="http://www.statistics.gov.uk/downloads/theme_health/DR2007/DR_07_2007.pdf" color="#5AC85A" textDecoration="none"/>
</s:Group>
</s:VGroup>
<s:CheckBox id="showLabelsCheckBox" x="15" y="323" label="Show labels" selected="true" click="onShowLabelsClick()"/>
</s:VGroup>
<s:Button label="Close" horizontalCenter="0" bottom="10" click="currentState = 'Default'"/>
</s:Group>
</s:Group>
</s:Application>