Im new to Flex and Actionscript (2 weeks).
I'm trying to use a Chart whose DataProvider is an ArrayCollection that contains Objects
'Person' for example. Person's properties are Age and Salary.
My chart will be an ColumnChart and the xField=Age and the yField would be the AVG(or SUM) Salary for Persons with the same age.
There is any easy way to do that?
Using the way I've just learned, each Person will own one Column in the chart.
My mxml code:
< mx :ColumnChart id="myChart" height="100%" color="0x323232"
showDataTips="true" dataProvider="{GraphArray}">
< mx:series>
< mx:ColumnSeries xField="age" yField="salary"/>
< /mx:series>
< /mx:ColumnChart>
As far as I know, the standard charts will only display exactly what you give them, so you'll need to create a new data provider that contains the average/sum of the data for each age group.
To make a derived provider that stays up to date with changes to GraphArray, you can either add listeners to GraphArray or, if the GraphArray is only set and not modified, you can get away with breaking the variable out into getters and setters, something like this (untested) code:
If you had
[Bindable]
public var GraphArray:ArrayCollection;
Replace it with
private var _GraphArray:ArrayCollection;
[Bindable]
private var DerivedGraphArray:ArrayCollection;
[Bindable]
public function get GraphArray():ArrayCollection
{
return _GraphArray;
}
public function set GraphArray(value:ArrayCollection):void
{
var ageBucketedArray:Array = new Array(121);
var ageBucketedCountsArray:Array = new Array(121);
//Compute salary sums
for each (var o:Object in value)
{
if (ageBucketedArray[o["age"]])
{
ageBucketedArray[o["age"]] += o["salary"];
ageBucketedCountsArray[o["age"]] += 1;
}
else
{
ageBucketedArray[o["age"]] = o["salary"];
ageBucketedCountsArray[o["age"]] = 1;
}
}
//Compute averages
for (var age:int = 0; age < ageBucketedArray.length; age++)
{
if (ageBucketedArray[age])
{
ageBucketedArray[age] /= ageBucketedCountsArray[age];
}
else
{
ageBucketedArray[age] = 0;
}
}
_GraphArray.removeAll();
_GraphArray.addAll(value);
DerivedGraphArray = new ArrayCollection(ageBucketedArray);
}
Related
i have a problem using the itemRenderer functionality. When using an ArrayCollection the visible Data in the DataGrid using the itemRenderer will be rendered just fine. But if i start scrolling the entries are repeating in the cells using the renderer. The cells are not filled with date according to the id. What mistake i'm doing here.
I read a lot of the explainations like:
http://blogs.adobe.com/aharui/2007/03/thinking_about_item_renderers_1.html
here is the code for the set data function (itemRenderer is extending HBox):
override public function set data(value:Object):void {
_data = value;
if(data!=null)
{
var maxValue:Number = 0;
var maxFontHeight:int = 18;
for each(var term:ArrayCollection in _data.story)
{
if((term.getItemAt(1) as Number)>maxValue)
maxValue=term.getItemAt(1) as Number;
}
for each(var term:ArrayCollection in _data.story)
{
var FontHeight:int = Math.floor((term.getItemAt(1) as Number) * maxFontHeight / maxValue);
var l:Label = new Label();
l.text = term.getItemAt(0) as String;
l.setStyle("fontWeight","normal");
l.setStyle("fontFamily","Verdana");
l.setStyle("paddingRight",0);
l.setStyle("paddingLeft",0);
l.setStyle("fontSize", FontHeight);
l.setStyle("color", 0x000000);
this.addChild(l);
}
}
}
You aren't clearing down what's already there in the renderer before adding new stuff.
Try moving the construction of the label to createChildren, setting the text in set data (rather than constructing another label), and remembering to clear the text if the data is null.
There's a few more optimizations you could make such as checking the new data isn't the same as the current data before doing the work, for example.
My goal is to create a generic function that selects a value in a combobox accoring to a value.
(My comoBox holds arrayCollection as dataProvider.)
The difficulty is infact to get a propertyname in runtime mode
public function selectComboByLabel(combo:ComboBox , propetryName:String, value:String):void {
var dp:ArrayCollection = combo.dataProvider as ArrayCollection;
for (var i:int=0;i<dp.length;i++) {
if (dp.getItemAt(i).propertyName==value) {
combo.selectedIndex = i;
return;
}
}
}
the line if (dp.getItemAt(i).propertyName==value)
is of course incorrect.
It should be arther something like: dp.getItemAt(i).getPropertyByName(propertyName)
Any clue on how to that ?
Don't use Object Property notation. Do this:
dp.getItemAt(i)[propertyName]
In addition to what Flextras said, you could also redo your for loop to make it easier to read:
for each(var item:Object in dp) {
if(item[propertyName] == value) {
combo.selectedItem = item;
return;
}
}
I am trying to create a dynamic datagrid in Flex 3, I have a list of columns a list of objects which correspond to datapoints for those columns which I fetch from a url. While the grid works perfectly fine the problem is that sorting on the columns is done in lexical order.
I am aware that this can be fixed by adding a sortcomparefunction to a column, which is not easy for this case. I have tried doing
var dgc:DataGridColumn = new DataGridColumn(dtf);
f1[dtf] = function(obj1:Object, obj2:Object):int {
return Comparators.sortNumeric(obj1[dtf],obj2[dtf]);
};
dgc.sortCompareFunction = f1[dtf];`
But the problem is that the function object that I am creating here is being overwritten in every iteration (as I am adding columns) and eventually all the columns will have sorting done only on the last column added.
Suggestions please.
Lets assume you have dynamicColumns array of some objects that you want create datagrid columns from.
var columns:Array = new Array();
for (var i:int = 0; i < dynamicColumns.length; i++) {
var column:DataGridColumn = new DataGridColumn(dynamicColumns[i].label);
var dataGridColumnSortingHelper:DataGridColumnSortingHelper = new DataGridColumnSortingHelper(column);
column.sortCompareFunction = dataGridColumnSortingHelper.columnSortCompareFunction;
columns.push(column);
}
yourDataGrid.columns = columns;
Where DataGridColumnSortingHelper is like this:
public class DataGridColumnSortingHelper
{
private var column:DataGridColumn;
// Constructor
public function DataGridColumnSortingHelper(column:DataGridColumn)
{
this.column = column;
}
// Public Methods
public function columnSortCompareFunction(item1:Object, item2:Object):int
{
// define your custom function here and use column field to know what column you sorting by...
return 0;
}
}
Thanks Tom for comment.
Essentially this is what I want to accomplish, however it doesn't work like this. Is there any solution: - The problem is I can't dynamically name a new object... ??? pulling my hair out.
import views.printingView;
public function initComponent(o:Array):void{
SomeObject::Array = o;
for(i=0; i <=SomeObject.length-1; i++){
'invoice'+{SomeObject[i].namedID}:printingView = new printingView();
someDisplayContainer.addChild('invoice'+{SomeObject[i].namedID});
'invoice'+{SomeObject.namedID}.publicInitFunction(SomeObject[i]);
}
}
From the code you posted, there's no need for a dynamically named variable at all. The same code can be simplified to this:
import views.printingView;
public function initComponent(o:Array):void{
for each(var item:Object in o)
{
var v:printingView = new printingView();
someDisplayContainer.addChild(v);
v.publicInitFunction(item);
}
}
If for some reason you really need a dynamically named variable, you can do it like this. This assumes the current object is declared dynamic.
import views.printingView;
public function initComponent(o:Array):void{
SomeObject::Array = o;
for each(var item:Object in o)
{
var name:String = 'invoice' + item.namedID;
this[name] = new printingView();
someDisplayContainer.addChild(this[name]);
this[name].publicInitFunction(item);
}
}
I'm a bit new to flex and cannot get my head around this problem. can someone help. thanks in advance.
I have a string list path.
path 1 - "one/two/three"
path 2 - "one/two/four"
path 3 - "five/six"
i need an advanced datagrid to show a tree structure like so
one/
...two/
........three/
............four
five/
.......six
but i want to achieve this dynamicall with arrays, objects, or arraycollection (as applicable)
I need to loop through each string path using string methods which isnt a problem but how do i create "DYNAMIC" (depth) children? please help as i'm about to pull my hair out.
You can try something like:
var paths:Array = ['one/two/three','one/two/four','five/six'];
var pathsCollection:ArrayCollection = new ArrayCollection();
for(var i:int = 0 ; i < paths.length ; i++){
var folderArr:Array = paths[i].split('/');
var folderNum:int = folderArr.length;
var folderLabel:String = '';
for(var j:int = 0 ; j < folderNum; j++){
trace(folderLabel+folderArr[j]);
pathsCollection.addItem({label:folderLabel+folderArr[j],level:j,path:folderArr});
folderLabel += '...';
}
}
and as sharvey says, do have a look at recursion.
Recursion is the way to go when you are operating on objects which contain an unknown number of elements. Try this sample application:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="{init();}"
layout="vertical"
verticalAlign="middle">
<mx:Script>
<![CDATA[
import mx.utils.ObjectUtil;
import mx.collections.HierarchicalData;
private var paths:Array = ['one/two/three','one/two/four','five/six'];
private static const DELIMITER:String = "/";
private function init():void {
var test:Array = buildHierarchy(paths);
dg_test.dataProvider = new HierarchicalData(test);
trace(ObjectUtil.toString(test));
}
private function buildHierarchy(arr:Array):Array {
var ret:Array = new Array();
var o:Object = new Object();
/* Loop over the paths array */
for (var i:int = 0; i < arr.length; i++) {
/* Split the string block according to the delimiter */
var parts:Array = String(arr[i]).split(DELIMITER);
if (parts.length) {
/* Make a new object with a label equal to the first string element */
o = new Object();
o.label = parts[0];
/* Remove the first item in the string list */
parts.splice(0, 1);
/* Important - If the string has remaining members, call this
function again with the remaining members. Assign this to
the 'children' property of the newly created object */
if (parts.length > 0)
o.children = buildHierarchy([parts.join(DELIMITER)]);
/* Add the object to the new array */
ret.push(o);
}
}
return ret;
}
]]>
</mx:Script>
<mx:AdvancedDataGrid id="dg_test" height="200" width="400">
<mx:columns>
<mx:AdvancedDataGridColumn id="col_label" dataField="label"/>
</mx:columns>
</mx:AdvancedDataGrid>
This function will call itself once for every element contained in the 'string/string/string' block. The key to getting this structure displayed in the ADG is to set the adg.dataProvider = new HierarchicalData(myArray);
Hope this works! I can't get the code formatted 100% but you should get the idea. Don't forget to add the closing application tag.