The Axiis Data Visualization library comes complete with an attractive sample bubble chart, which met my needs exactly – except that I needed drill-down capability. In short, my application had to show drill-down data as new bubbles the color of the selected bubble. Double-clicking a bubble would select it and invoke the drill-down, a second double-click, on any bubble, would dismiss the drill-down.
In the interest of helping you over the hurdle faster, in case you have similar needs, I’m providing this brief tutorial.
First enable double-click event handling.
BaseLayout comes with support for double-click events baked in, passing a custom LayoutItemEvent event. The event includes the current AxiisSprite that generated the event. The AxiisSprite has a data property, which contains the data with which it was rendered. So, implementing double-click handling was as simple as adding a listener, and a handler.
public function init():void
{
dataProvider=loadData();
plotGroup.addEventListener("itemDoubleClick",onItemDoubleClick);
}
public function onItemDoubleClick(e:LayoutItemEvent):void
{
var data:Object = e.item.data;
{... do stuff ...}
dc.invalidateDisplayList(); // required to update the chart display
}
.
Next Overlay the Charts.
Displaying the drill-down data as additional bubbles on the same chart is straight forward. A dive into the source (src/DataCanvas.as) provides the following bit of intelligence.
/**
* An Array of ILayouts that this DataCanvas should render. Layouts
* appearing later in the array will render on top of earlier layouts.
*/
public var layouts:Array;
.
This means is that if you want two PlotGroup charts to overlay one another all you need to do is stack them in the same DataCanvas. The second chart has its own data provider, and its visible property set to false, and so my canvas looks like the following (code elements not directly related, such as axis, have been trimmed for clarity).
<axiis:DataCanvas top="100"
bottom="120"
width="{this.width*.8}"
id="dc"
verticalCenter="0"
horizontalCenter="0"
showDataTips="true">
<!-- Layouts -->
<axiis:layouts>
<axiis:PlotGroup id="plotGroup"
dataProvider="{dataProvider}"
verticalScale="{vScale}"
horizontalScale="{hScale}"
radiusScale="{rScale}"
verticalField="aggregates.business_value_avg"
horizontalField="aggregates.complexity_avg"
radiusField="aggregates.weight_avg"
plotFill="{plotFill}"
markerGeometry="{marker}"
height="{dc.height-15}"
width="{dc.width-15}"
dataTipLabelFunction="{dataTipFunction}"
showDataTips="true"
showMarker="false"
x="15">
</axiis:PlotGroup>
<axiis:PlotGroup id="drilldownPlotGroup"
dataProvider="{dataProvider2}"
verticalScale="{vScale}"
horizontalScale="{hScale}"
radiusScale="{rScale}"
verticalField="aggregates.business_value_avg"
horizontalField="aggregates.complexity_avg"
radiusField="aggregates.weight_avg"
plotFill="{drilldownPlotFill}"
markerGeometry="{marker}"
visible="false"
height="{dc.height-15}"
width="{dc.width-15}"
dataTipLabelFunction="{dataTipFunction}"
showDataTips="true"
showMarker="false"
x="15">
</axiis:PlotGroup>
</axiis:layouts>
</axiis:DataCanvas>
.
Update Data and Toggle Visible Property.
Now we need to have our double-click handler fill the drill-down data provider with the appropriate details and update the visible property. A second double-click toggles the visible property back off. The handler now looks like this:
public function onItemDoubleClick(e:LayoutItemEvent):void {
var data:Object = e.item.data;
if (drilldownPlotGroup.visible)
drilldownPlotGroup.visible = false;
else
{
drilldownDataProvider=loadData(data);
drilldownPlotGroup.visible = true;
}
dc.invalidateDisplayList();
}
.
Fix the Color of the Drill-Down Bubble.
The final requirement is to set the bubble colors for the drill-down chart to the color of the selected bubble. Ah, but here’s where it gets a bit hairy. The Axiis library, as delivered, doesn’t hold the geometry information used for rendering in the AxiisSprites — to set the color in the drill-down chart, we need the RadialGradientFill data that was used to render the selected bubble. To get it, I modified the function that stored the data that is copied into the AxiisSprite. The function in question is this one, located in src/org/axiis/core/BaseLayout.as
/**
* The callback method called by the referenceRepeater before it applies
* the PropertyModifiers on each iteration. This method updates the
* currentIndex, currentDatum, currentValue, and currentLabel
* properties. It is recommended that subclasses override this method
* to perform any custom data-driven computations that affect the
* drawingGeometries.
*/
protected function preIteration():void
{
_currentIndex = referenceRepeater.currentIteration;
_currentDatum = dataItems[_currentIndex];
if (_currentDatum) {
_currentValue= getProperty(_currentDatum,dataField);
_currentLabel = getProperty(_currentDatum,labelField);
}
else {
_currentValue=null;
_currentLabel=null;
}
}
.
As suggested, we should override this function in a subclass. Fortunately the PlotGroup.mxml is a subclass of BaseLayout, so we can change the access of the function to public add the override function, as follows:
override public function preIteration():void
{
super.preIteration();
_currentDatum.fill = plotFill;
}
.
However, this does not provide the expected results. The plotFill property of the PlotGroup layout is bound, and assigning it this way just transfers the binding. What we need is to copy the current value of the RadialGradientFill property into a new object, and add that to the Sprite’s data.
override public function preIteration():void
{
super.preIteration();
var rgf:RadialGradientFill = new RadialGradientFill();
for each (var gs:GradientStop in (plotFill as RadialGradientFill).gradientStops)
{
var gstop:GradientStop = new GradientStop(gs.color,gs.alpha, gs.ratio);
rgf.gradientStops.push(gstop);
}
_currentDatum.fill = rgf;
}
.
Final Pass on the Double-Click Handler.
Now we can update our double-click handler to grab the current Sprite’s RadialGradientFill, so it can display the drill-down bubbles with the same colors. Finally our callback looks like this:
public function onItemDoubleClick(e:LayoutItemEvent):void {
var data:Object = e.item.data;
if (drilldownPlotGroup.visible)
drilldownPlotGroup.visible = false;
else
{
drilldownPlotGroup.visible = true;
drilldownDataProvider=loadData(data);
drilldownPlotGroup.plotFill = e.item.data["fill"];
}
dc.invalidateDisplayList();
}
.
And we’re done.
I don’t have code that you can grab and build, as the sample data I’m working with can’t be released, and I don’t have time right now, to mock up data that can be shared. Let me know if you need working code in the comments, and I’ll see what I can do.