Refresh a BarChart after adding a BarSeries - apache-flex

first question here.
I have a BarChart showing several normal bars and a BarSeries, like this:
<mx:BarChart id="barchart" dataProvider="{model.myList}" type="clustered">
<mx:horizontalAxis>
<mx:LinearAxis autoAdjust="true"/>
</mx:horizontalAxis>
<mx:verticalAxis>
<mx:CategoryAxis categoryField="name"/>
</mx:verticalAxis>
<mx:series>
<mx:BarSeries dataProvider="{model.myList}" xField="myValue"/>
</mx:series>
</mx:BarChart>
When a user clicks on a button, i need to calculate some values, put them on the "myCalculatedValue" and add another BarSeries as a comparison. I'm doing this:
var barSerie:BarSeries = new BarSeries();
barSerie.dataProvider = model.myList;
barSerie.xField = "myCalculatedValue";
barchart.series.push(barSerie);
But the BarChart does not change at all. Is there some way to refresh the chart after adding the new BarSeries?

The short answer is to use this code:
var barSerie:BarSeries = new BarSeries();
barSerie.dataProvider = model.myList;
barSerie.xField = "myCalculatedValue";
var allSeries:Array = barchart.series;
allSeries.push(barSerie);
barchart.series = allSeries;
The LONG answer, to give you an understanding of how this works:
The way flex knows when to refresh a ui component that is data backed, such as a chart, is on the function set dataProvider property, or equivalent (in this case series).
Here is the code from mx.charts.chartClasses.ChartBase:
public function set series(value:Array /* of Series */):void
{
value = value == null ? [] : value;
_userSeries = value;
var n:int = value.length;
for (var i:int = 0; i < n; ++i)
{
if (value[i] is Series)
{
(value[i] as Series).owner = this;
}
}
invalidateSeries();
invalidateData();
legendDataChanged();
}
Notice that the relevant "invalidate" methods are called at the end of the set method.
If you say barChart.series.push(x), the set series method is never called, only the getter. Therefore in order to force the chart to know there was a change to the series, you need to assign a new value to the series.
It is worth pointing out that even assigning the series to itself will cause an invalidate, although it isn't good coding
barChart.series = barChart.series

Flex is communicating with the server remotely, correct? You probably need to make another explicit call to the app controller that is housing flex.

Related

Animation of flex Hslider / graph interaction

I currenty have an Hslider that is animated and affects the results presented in a bar graph but when I play through the values of the Hslider the animation is jumpy. by jumpy i mean it sometimes tries to go back a value before going forward. for example the slider value reaches 1963 then briefly goes back to 1962 then continues the odd time jumping back.
can anyone identify any errors in my approach...
public var anim:AnimateProperty = new AnimateProperty();
public function Startanimation():void {
anim.target = YEAR_SLIDER;
yearto = 2011 - YEAR_SLIDER.value;
anim.duration = yearto * 150;
anim.property = "value";
anim.fromValue = YEAR_SLIDER.value;
anim.toValue = 2011;
anim.end();
anim.play();
}
public function update_Application():void{
YEAR = YEAR_SLIDER.value;
GEONAME = COMBO.selectedLabel;
var data:Object = new Object();
data = {YEAR : YEAR, GEONAME : GEONAME};
graphdata.getdata(data);
graphdata.getdata_grid(data);
}
<mx:HSlider
id="YEAR_SLIDER" value="{YS_INITIAL_VALUE}"
minimum="{YS_INITIAL_VALUE}"
maximum="2011" change="update_Application()"
liveDragging="true" accessibilityEnabled="true"
snapInterval="1" currentState=""
tickInterval="10"
width="300" showDataTip="false"
borderColor="#133341"
alpha="1.0"
fillAlphas="[0.69, 0.64, 0.91, 0.91]" dataTipFormatFunction="datatip">
</mx:HSlider>
Thanks in advance...
Solved...//
For anyone else having this issue, it seemed to be the amount of data I was returning to the graph. Once I cleaned up my select statement to return only the specific columns needed and also created an index on the table the animation was smooth.

Flex : AdvancedDataGrid columns dataProvider?

I have a AdvancedDataGrid, defined as:
<mx:AdvancedDataGrid id="mainGrid" width="200" height="200" designViewDataType="flat" >
<mx:columns>
</mx:columns>
</mx:AdvancedDataGrid>
I am adding dynamically the columns to the grid, and the main question is:
How to setup an array or vector as dataprovider to each Column?
P.S.
this function I am using to fill up the data:
private function buildUpTheGrid() : void {
masterColumns = new Array();
masterData = new Array();
var tempColumn : AdvancedDataGridColumn;
for( var iY : int = 0; iY < columsCount; iY++ )
{
masterData.push( new Array() );
tempColumn = new AdvancedDataGridColumn();
tempColumn.width = 20;
for( var iX : int = 0; iX < rowsCount; iX++ )
masterData[ iY ].push( iX );
// tempColumn.dataField = ???
masterColumns.push( tempColumn );
}
mainGrid.columns = masterColumns;
mainGrid.validateNow();
}
Short answer to your question "How to setup an array or vector as dataprovider to each Column ?": You cannot.
The data 'views' in flex are all row based, not column based. So, for this to work, you'll need to transform your columns into rows. It's a fairly simple algorithm that I'm sure you can figure out.
As for adding the columns dynamically, you just need to add the columns to an array and then set that array in the 'columns' property of the grid. You'll need to set a 'dataField' property on the column for it to know which data property to display from the row data you've just transformed.
How to setup an array or vector as dataprovider to each Column ?
In the architecture of a DataGrid, and AdvancedDataGrid individual columns do not have their own, separate, dataProviders. To set the dataProvider on the component, you can do something like this:
<mx:AdvancedDataGrid id="mainGrid" dataPRovider="{myDataProvider}" >
in ActionScript you can set the dataProvider like this:
mainGrid.dataProvider = myDataProvider
If you have data coming from a myriad of different sources that you need to display in a single DataGrid, you should try to combine that data into a single dataProvider; using some common element, such as a database primary key value. Creating a single object to contain each separate element should work. Conceptually something like this:
public class myCombinedObject{
public var commonElement : int;
public var fromDataProvider1 : myCustomDataClass1;
public var fromDataProvider2 : myCustomDataClass2;
}
From there just write itemRenderers for each column to show the relevant data.
Usually we will set the dataprovider to the Datagrid. And in the columns we can set the datafield, which will take the property from the objects of the dataprovider to be displayed in the particular column. Are you aiming to make the columns coming from different arrays? In that case it will be a tricky one.
I've used an Array of Arrays as a dataProvider.
You can just set the dataField property to the index of the "sub Array".
For example:
tempColumn.dataField = iY.toString();
This works, (it fills the column with every value from the the sub array [iY]), but I can't recommend doing this because if the order of your data in the sub arrays changes
it could be become difficult to debug...

When to render Legend in Flex

My Flex chart has code that creates a legend from each data series. Currently, I have the drawLegend function execute when a button is clicked.
I am trying to get the legend to draw automatically but I am having trouble determining when to call the drawLegend function. Users click on an 'Update' button which retrieves data. I want the legend to get created after this, without the users having to click on another button.
I have put my drawLegend function in several places (ResultHandler, afterEffectEnd(for chart animation, etc.) and nothing works.
Sometime after the series has been added to the chart and after the series animation ends, the legend can be added.
How can I trap this point in time and call my function?
Any ideas?
Below is the code I used to create the legend. Note that even though the code processes each series, I noticed that the Fill color is null if it is called too early.
private function drawLegend(event:Event):void {
clearLegend();
// Use a counter for the series.
var z:int = 0;
var numRows:int;
if (chart.series.length % rowSize == 0) {
// The number of series is exactly divisible by the rowSize.
numRows = Math.floor(chart.series.length / rowSize);
} else {
// One extra row is needed if there is a remainder.
numRows = Math.floor(chart.series.length / rowSize) + 1;
}
for (var j:int = 0; j < numRows; j++) {
var gr:GridRow = new GridRow();
myGrid.addChild(gr);
for (var k:int = 0; k < rowSize; k++) {
// As long as the series counter is less than the number of series...
//skip columnset group
if (chart.series[z] is LineSeries || chart.series[z] is ColumnSeries){
if (z < chart.series.length) {
var gi:GridItem = new GridItem();
gr.addChild(gi);
var li:LegendItem = new LegendItem();
// Apply the current series' displayName to the LegendItem's label.
li.label = chart.series[z].displayName;
// Get the current series' fill.
var sc:SolidColor = new SolidColor();
sc.color = chart.series[z].items[0].fill.color;
// Apply the current series' fill to the corresponding LegendItem.
li.setStyle("fill", sc.color);//was just sc
// Apply other styles to make the LegendItems look uniform.
li.setStyle("textIndent", 5);
li.setStyle("labelPlacement", "left");
li.setStyle("fontSize", 9);
gi.setStyle("backgroundAlpha", "1");
gi.setStyle("backgroundColor", sc.color);
//gi.width = 80;
// Add the LegendItem to the GridItem.
gi.addChild(li);
}
}
// Increment any time a LegendItem is added.
z++;
}
}
}
Well, I couldn't quite figure out what event to trap so I used the following in the chart's parent container:
chart.addEventListener(ChartItemEvent.ITEM_ROLL_OVER,drawLegend);
Seems to work ok.

DateTimeAxis - set Label to display Date + Time

Is there a way in flex 3 chart component to display both the date and time using horizontal DateTimeAxis?
Currently the DateTimeAxis element has an attribute dataunits which allows to set the value to any of "milliseconds|seconds|minutes|hours|days|weeks|months|years" but I want to display the label as "2009/09/15 06:00:00" which includes the day and the time too.
Here is the sample that i'm using
[Bindable]
public var deck:ArrayCollection = new ArrayCollection([
{date:"2009-09-15 06:00:00", close:42.71},
{date:"2009-09-16 06:15:00", close:42.99}
]);
public function myParseFunction(s:String):Date {
var sDate = s.substring(0,s.indexOf(" "));
var sTime = s.substring(s.indexOf(" "));
var aDate = sDate.split("-");
var aTime = sTime.split(":");
return new Date(aDate[0],(aDate[1]*1-1),aDate[2],aTime[0],aTime[1],aTime[2],0);
}
]]>
Thanks in advance.
To solve this, you have to extend the <mx:DateTimeAxis> (unfortunately).
However, it is not hard to do, and an example is posted here: http://codeznips.blogspot.com/2010/12/escape-american-date-format-in.html
You'd might have to extend the example above further by adding a formatSeconds() and set formatStringSeconds = "YYYY/MM/DD JJ:NN:SS".
Or possibly just use the existing formatStringDays and set dataUnits = "days", like this:
<mx:horizontalAxis>
<charts:FormattedDateTimeAxis
formatStringDays="YYYY/MM/DD JJ:NN:SS"
dataUnits = "days"/>
</mx:horizontalAxis>
Vegard

Using Per Item Fills in Flex/Actionscript

I am creating a chart using mxml. The mxml tags only create a chart with a horizontal axis and vertical axis.
My result event handler has actionscript code that loops through the xml result set and creates all the series (line series and stacked bar). This part of the code works fine.
Now I need to use the functionfill function to set individual colors to each series. All the examples I have found call the functionfill from within an MXML tag, like so:
<mx:ColumnSeries id="salesGoalSeries"
xField="Name"
yField="SalesGoal"
fillFunction="myFillFunction"
displayName="Sales Goal">
I am having trouble calling functionfill from actionscript.
A portion of the code that build the data series is below:
if (node.attribute("ConfidenceStatus")=="Backlog"
|| node.attribute("ConfidenceStatus")=="Billings") {
// Create the new column series and set its properties.
var localSeries:ColumnSeries = new ColumnSeries();
localSeries.dataProvider = dataArray;
localSeries.yField = node.attribute("ConfidenceStatus");
localSeries.xField = "TimebyDay";
localSeries.displayName = node.attribute("ConfidenceStatus");
localSeries.setStyle("showDataEffect", ChangeEffect);
localSeries.fillFunction(setSeriesColor(xxx));
// Back up the current series on the chart.
var currentSeries:Array = chart.series;
// Add the new series to the current Array of series.
currentSeries.push(localSeries);
//Add Array of series to columnset
colSet.series.push(localSeries);
//assign columnset to chart
chart.series = [colSet];
My setSeriesColor function is:
private function setSeriesColor(element:ChartItem, index:Number):IFill {
var c:SolidColor = new SolidColor(0x00CC00);
var item:ColumnSeriesItem = ColumnSeriesItem(element);
//will put in logic here
return c;
}
What parameters do I put in the line localSeries.fillFunction(setSeriesColor(xxx)) ?
I tried localSeries as the first argument but I get an implicit coercion error telling me localSeries can't be cast as ChartItem.
How do I call the function correctly?
localSeries.fillFunction = setSeriesColor;
The code you have right now is actually CALLING setSeriesColor the way you have it set up. You only want it to refer to a reference of the function, not calling it, so just send it "setSeriesColor" as a variable.

Resources