Flex columnset not displayed properly - actionscript-3

I am developing a chart in which i need to have multiple column sets but flex is giving me strange results for it. I am using parsley framework.
Here is my mxml code
<mx:ColumnChart id="columnChart"
width="100%" height="100%"
dataProvider="{myPM.myData}"
showDataTips="true"
click="handleChartClick(event)"
backgroundElements="{backgroundGrids}"
dataTipFunction="{myPM.formatColumnChartToolTipData}"
type="clustered"
>
<mx:horizontalAxis>
<mx:CategoryAxis id="hAxis"
categoryField="{myPM.xFieldLabel}"
title="{myPM.xAxisDisplayLabel}"/>
</mx:horizontalAxis>
<mx:verticalAxis>
<mx:LinearAxis id="vAxis"
title="{myPM.yAxisDisplayLabel}"/>
</mx:verticalAxis>
<mx:series>
**works perfecly fine if i change type="clustered"**
<mx:ColumnSet series="{myPM.columnSeries}"
type="stacked"
verticalAxis="{vAxis}"
>
</mx:ColumnSet>
<mx:ColumnSet>
<mx:ColumnSeries xField="{myPM.xFieldLabel}"
yField="{myPM.yFieldLabel}"
displayName="year"/>
</mx:ColumnSet>
</mx:series>
</mx:ColumnChart>
In my presentation model i am returning column series which is presented in the following code
public class MyPM
{
public var columnSeries:Array;
public void init()
{
columnSeries=getColumnSeries();
}
public function getColumnSeries():Array
{
var series:Array = new Array();
var columnSeries:ColumnSeries;
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter1_profit_count";
columnSeries.displayName="quarter1_title";
series.push(columnSeries);
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter2_profit_count";
columnSeries.displayName="quarter2_title";
series.push(columnSeries);
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter3_profit_count";
columnSeries.displayName="quarter3_title";
series.push(columnSeries);
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter4_profit_count";
columnSeries.displayName="quarter4_title";
series.push(columnSeries);
return series;
}
}
The problem i am getting here is
"In first column there should be 4 stacked column series but flex drops 1 out of 4 stacked column series ( i am able to see only 3 or 2 columns it drops remaining one). I have debugged my code and the data is coming perfectly fine. If i remove all the column sets and write like then it gives me correct result for the first stacked column. But as i want the second column also which will have summation of all data at one place. for e.g. 1st stacked column will be showing quarterwise profit amounts and the second column i want to have to display total profit throughout the year."
You can see the charts here
- without using columnsets and binding series directly in definition
- as you can see the quarters are dropped here. in both the charts data source is same
I am not knowing what i am doing wrong here. Any kind of help will be
appreciated. Thanks in advance.

After a lot of effort i come to the solution. Here is the code for that.
public function getColumnSeries():Array
{
var series:Array = new Array();
var set1:ColumnSet =new ColumnSet();
set1.type="stacked";
var set2:ColumnSet=new ColumnSet();
set2.type="stacked";
var columnSeries:ColumnSeries;
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter1_profit_count";
columnSeries.displayName="quarter1_title";
series.push(columnSeries);
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter2_profit_count";
columnSeries.displayName="quarter2_title";
series.push(columnSeries);
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter3_profit_count";
columnSeries.displayName="quarter3_title";
series.push(columnSeries);
columnSeries=new ColumnSeries();
//columnSeries.xField=xFieldLabel;
columnSeries.yField="quarter4_profit_count";
columnSeries.displayName="quarter4_title";
series.push(columnSeries);
set1.series=series;
//similarly for set2
series=new Array();
//add columnseries to series
set2.series=series;
var temp:Array=new Array();
temp.push(set1);
temp.push(set2);
return temp;
}
Also along with this make sure that you calculate max and min values for the y axis otherwise it will not render propery. This was also one of the reason why my chart was not getting displayed in the way i wanted.
Here is the updated markup
<mx:ColumnChart id="columnChart"
width="100%" height="100%"
dataProvider="{myPM.myData}"
showDataTips="true"
click="handleChartClick(event)"
backgroundElements="{backgroundGrids}"
dataTipFunction="{myPM.formatColumnChartToolTipData}"
type="clustered"
series={myPM.series}
>
<mx:horizontalAxis>
<mx:CategoryAxis id="hAxis"
categoryField="{myPM.xFieldLabel}"
title="{myPM.xAxisDisplayLabel}"/>
</mx:horizontalAxis>
<mx:verticalAxis>
<mx:LinearAxis id="vAxis"
title="{myPM.yAxisDisplayLabel}"/>
</mx:verticalAxis>
</mx:ColumnChart>

Related

Is there a Priority on Two Sort Fields in Flex for XMLListCollection?

Is there a priority in the sorting algorithm when you provide two fields for a sort (ISort)?i.e. if you are sorting on date and time, will the sort only sort time if the dates are equal?
Sample Code Below:
private function sortXMLListCollection(listCollection:XMLListCollection, fields:Array):XMLListCollection
{
var descendingSort:Sort = new Sort();
descendingSort.fields = new Array();
for each( var field:String in fields)
{
descendingSort.fields.push(new SortField(field, true));
}
listCollection.sort = descendingSort;
listCollection.refresh();
return listCollection;
}
Function Call: sortXMLListCollection(patchCollection, ["date", "time"]);
Sample XML:
<patch>
<time>08:44:46</time>
<date>10/10/12</date>
</patch>
<patch>
<time>08:51:09</time>
<date>10/10/12</date>
</patch>
<patch>
<time>08:46:04</time>
<date>10/11/12</date>
</patch>
Somehow the above function does not work as I expected. I want it to compare the dates first and only sort time when dates are equal.
Thank you for your help
your problem isn't your code... it's your xml.
if you add a root node, your code will work as expected:
var xml:XML =
<root>
<patch>
<time>08:44:46</time>
<date>10/10/12</date>
</patch>
<patch>
<time>08:51:09</time>
<date>10/10/12</date>
</patch>
<patch>
<time>08:46:04</time>
<date>10/11/12</date>
</patch>
</root>
var patchCollection:XMLListCollection = new XMLListCollection(xml.patch);
sortXMLListCollection(patchCollection, ["date", "time"]);

List with different column count

As the pic show(This is drawn by PhotoShop, not implemented yet), I want to implemnt a List like this one. It has different column count, say the first row has only one item, and the others have two items . I tried to use itemRendererFunction to detect the different item(the first row treat as a rendererA, the others treat as another rendererB),but it didn't work.
The cleanest solution to this problem, is to create a custom layout (we've discussed in the comments how Romi's solution will eventually cause too many problems). However, this is usually not an easy thing to do.
I will give you a rough sketch of what this custom layout might look like, so you can use it as a starting point to create one that does exactly what you need.
To create a custom layout, you must subclass BaseLayout and override and implement its updateDisplayList and measure methods.
To make things easier (and in order not to dump 500 lines of code in here), I used some hardcoded variables for this example. It assumes there will always be two columns, the first item will always be 200x200 px, and the other items will always be 100x100 px. There is no horizontalGap or verticalGap.
The consequence is of course that you can use this custom layout (as it is now) only for this specific List and these specific ItemRenderers. If you want it to be more generic, you'll have to do a lot more calculations.
But now for the code:
public class MyCustomLayout extends LayoutBase {
//hardcoded variables
private var columnCount:int = 2;
private var bigTileWidth:Number = 200;
private var bigTileHeight:Number = 200;
private var smallTileWidth:Number = 100;
private var smallTileHeight:Number = 100;
override public function updateDisplayList(width:Number, height:Number):void {
var layoutTarget:GroupBase = target;
if (!layoutTarget) return;
var numElements:int = layoutTarget.numElements;
if (!numElements) return;
//position and size the first element
var el:ILayoutElement = useVirtualLayout ?
layoutTarget.getVirtualElementAt(0) : layoutTarget.getElementAt(0);
el.setLayoutBoundsSize(bigTileWidth, bigTileHeight);
el.setLayoutBoundsPosition(0, 0);
//position & size the other elements in 2 columns below the 1st element
for (var i:int=1; i<numElements; i++) {
var x:Number = smallTileWidth * ((i-1) % 2);
var y:Number = smallTileHeight * Math.floor((i-1) / 2) + bigTileHeight;
el = useVirtualLayout ?
layoutTarget.getVirtualElementAt(i) :
layoutTarget.getElementAt(i);
el.setLayoutBoundsSize(smallTileWidth, smallTileHeight);
el.setLayoutBoundsPosition(x, y);
}
//set the content size (necessary for scrolling)
layoutTarget.setContentSize(
layoutTarget.measuredWidth, layoutTarget.measuredHeight
);
}
override public function measure():void {
var layoutTarget:GroupBase = target;
if (!layoutTarget) return;
var rowCount:int = Math.ceil((layoutTarget.numElements - 1) / 2);
//measure the total width and height
layoutTarget.measuredWidth = layoutTarget.measuredMinWidth =
Math.max(smallTileWidth * columnCount, bigTileWidth);
layoutTarget.measuredHeight = layoutTarget.measuredMinHeight =
bigTileHeight + smallTileHeight * rowCount;
}
}
And you can use it like this:
<s:List dataProvider="{dp}" height="300">
<s:layout>
<l:MyCustomLayout />
</s:layout>
</s:List>
Whenever you want to change the defined behavior of an existing component, always check first if you can solve the problem with skinning. It is a really powerful feature i Flex, and can also provide a solution in this case.
So, let's begin, assuming you already have your List, you only need to create a custom skin which "splits" the data provider in two parts, the first item, and all the others. So, let's assume we have this initial setup:
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var c:ArrayCollection = new ArrayCollection([
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6",
"String 7",
"String 8",
"String 9",
"String 10",
"String 11",
"String 12",
"String 13",
"String 14",
"String 15"]);
]]>
</fx:Script>
<s:List skinClass="CustomSkinList" dataProvider="{c}" />
As you can see, we define a custom list skin, which is just a copy of spark.skins.spark.ListSkin, the default skin for spark.components.List element.
Before we handle the data provider logic, we need to take a look at how the list items are rendered. This is done by using a DataGroup element, added to the skin, like so:
<s:Scroller left="0" top="0" right="0" bottom="0" id="scroller" minViewportInset="1" hasFocusableChildren="false">
<!--- #copy spark.components.SkinnableDataContainer#dataGroup -->
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<!--- The default layout is vertical and measures at least for 5 rows.
When switching to a different layout, HorizontalLayout for example,
make sure to adjust the minWidth, minHeight sizes of the skin -->
<s:VerticalLayout gap="0" horizontalAlign="contentJustify" requestedMinRowCount="5" />
</s:layout>
</s:DataGroup>
</s:Scroller>
Here is the place where we will have to make the changes, in order to get the first element to render differently. What we need to do, is just add another DataGroup, for rendering the first element in a custom way (this of course means using a custom item renderer). Now, our scroller looks like this:
<s:Scroller left="0"
top="0"
right="0"
bottom="0"
id="scroller"
minViewportInset="1"
hasFocusableChildren="false">
<!--- #copy spark.components.SkinnableDataContainer#dataGroup -->
<s:VGroup width="100%" height="100%">
<s:DataGroup id="firstItemDataGroup"
width="100%"
itemRenderer="CustomItemRenderer"
height="20">
<s:layout>
<s:VerticalLayout />
</s:layout>
</s:DataGroup>
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<!--- The default layout is vertical and measures at least for 5 rows.
When switching to a different layout, HorizontalLayout for example,
make sure to adjust the minWidth, minHeight sizes of the skin -->
<s:TileLayout horizontalAlign="center" requestedColumnCount="2" />
</s:layout>
</s:DataGroup>
</s:VGroup>
</s:Scroller>
Notice the 'firstItemDataGroup' addition, also the fact that it uses a different item renderer, than the default dataGroup element. With this new container in place we can proceed to render the elements. The custom skin need to override the parent initializationComplete() method, like so:
override protected function initializationComplete():void
{
useChromeColor = true;
if (hostComponent.dataProvider && hostComponent.dataProvider.length > 0)
{
var allItems:Array = hostComponent.dataProvider.toArray().concat();
firstItemDataGroup.dataProvider = new ArrayCollection([hostComponent.dataProvider.getItemAt(0)]);
var remainingItems:Array = allItems.concat().reverse();
remainingItems.pop();
var reversed:Array = remainingItems.reverse();
dataGroupProvider = new ArrayCollection(reversed);
}
super.initializationComplete();
}
What was added was just the 'if' block, and a private variable, named dataGroupProvider. This is because we will set the new dataProvider, the one starting from the second element, to the dataGroup element, in the updateDisplayList() method. Here is what it looks like:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
if (getStyle("borderVisible") == true)
{
border.visible = true;
background.left = background.top = background.right = background.bottom = 1;
scroller.minViewportInset = 1;
}
else
{
border.visible = false;
background.left = background.top = background.right = background.bottom = 0;
scroller.minViewportInset = 0;
}
// Here we assign the new data provider to the dataGroup element
if (dataGroupProvider)
dataGroup.dataProvider = dataGroupProvider;
borderStroke.color = getStyle("borderColor");
borderStroke.alpha = getStyle("borderAlpha");
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
In conclusion, just by creating a custom skin for our List element, we can use two containers for rendering the first item in a different way, from the rest of the elements. You shouldn't underestimate the power of Flex Skinning :)
Hope this helps. Have a great day!

Adobe TLF with Flex/AS3: how to programmatically change \n in text string into paragraph tags and insert into TextFlow?

My Flex/AS3 web application accesses a database and retrieves text containing \n for newline character. Otherwise the text looks like normal text.
Is there a way to convert the \n into paragraph tags that I can insert into an spark TextFlow?
For example, my only experience with TLF is in writing mxml code such as follows:
<s:RichEditableText width="100%" height="100%"
textAlign="left" paddingTop="0"
paragraphSpaceAfter="0.3"
editable="false">
<s:textFlow>
<s:TextFlow>
<s:p fontWeight="bold" fontSize="15">My Title Text</s:p>
<s:p fontSize="2"/>
<s:p>{paragraph1_text}</s:p>
<s:p>{paragraph2_text}</s:p>
<s:p>{paragraph3_text}</s:p>
<s:p fontSize="2"/>
</s:TextFlow>
</s:textFlow>
</s:RichEditableText>
Suppose I retrieve a string from the database such as:
paragraph1_text = "This is some text.\nThis is some more text.\nThis is even more text."
Even though inserting this into the code above will recognize the new line character, it won't recognize the paragraphSpaceAfter="0.3". My goal is to have the effect of:
<s:RichEditableText width="100%" height="100%"
textAlign="left" paddingTop="0"
paragraphSpaceAfter="0.3"
editable="false">
<s:textFlow>
<s:TextFlow>
<s:p fontWeight="bold" fontSize="15">My Title Text</s:p>
<s:p fontSize="2"/>
<s:p>This is some text.</s:p>
<s:p>This is some more text.</s:p>
<s:p>This is even more text.</s:p>
<s:p>{paragraph2_text}</s:p>
<s:p>{paragraph3_text}</s:p>
<s:p fontSize="2"/>
</s:TextFlow>
Is there any way to achieve something like this programmatically (the database text could be anything)?
I tried changing paragraph1_text to:
paragraph1_text="<s:p>This is some text.</s:p><s:p>This is some more text.</s:p><s:p>This is even more text.</s:p>"
and then using this in the first code block above, but it simply printed out all of the <s:p> and </s:p> tags.
Any advice appreciated.
You can achieve this by using ActionScript. Let's say you name your TextFlow like
<s:TextFlow id="myFlow">
Your ActionScript code block could look like:
import flashx.textLayout.elements.*;
public function insertParagraph( textFlow:TextFlow, text:String, locationIndex:uint ):void
{
var paragraphs:Vector.<ParagraphElement> = getParagraphElements( text );
for ( var i:uint = 0; i < paragraphs.length; i++ )
{
textFlow.addChildAt( paragraphs[i], locationIndex + i );
}
}
public function getParagraphElements( text:String ):Vector.<ParagraphElement>
{
var textParagraphs:Array = text.split("\n");
var result:Vector.<ParagraphElement> = new Vector.<ParagraphElement>();
for ( var i:uint = 0; i < textParagraphs.length; i++ )
{
var p:ParagraphElement = new ParagraphElement();
var span:SpanElement = new SpanElement();
span.text = textParagraphs[i];
p.addChild(span);
result.push(p);
}
return result;
}
In your case, since you want to add the paragraphs starting as the 3rd element, once the text is retrieved, you can call insertParagaph( myFlow, text, 2 );

Flash Builder: PieChart issue

I am trying to show data in my pieChart. Everything works great if I have multiple fields in my dataprovide (ex: EmployeeA, EmployeeB..etc). The pie will be divided into pieces and have texts shown on each pieces.
However, if my dataprovider only has one field (ex: EmployeeA). The pieChart will show the whole pie with no texts on the pieChart. I was wondering if there are any ways to show the data text inside the pieChart. Thanks a lot.
Code:
<mx:PieChart id="piechart1" x="254" y="321" width="367" height="367"
dataProvider="{getHours.lastResult}">
<mx:series>
<mx:PieSeries id="pieSeries" labelFunction="pieFunction"
explodeRadius="0.05" field="EmployeeOverTime" labelPosition="inside"/>
</mx:series>
</mx:PieChart>
//EmployeeOverTime data will show on each pie pieces if I have multiple
//employees in the department but won't show if the department has only one employee.
It looks like it was a bug in the PieSeries code. I had to monkey patch the charting code in order to get this to work. Here's an fxp file that works.
http://dl.dropbox.com/u/20054635/forever/piechart.fxp
The bug is that on line 3323 there's this code:
var rscale:Number = 1;
// find out how much we would have to scale to get to the next neighbor up
/* var RXScale:Number = Math.abs(ld.labelX - nextNeighbor.labelX)/(ld.labelWidth/2 + nextNeighbor.labelWidth/2);
Then on line 3338 it removes the label based on rscale (which is now zero because there was no nextNeighbor when there is only 1 item in the piechart)
if (rscale * Number(labelFormat.size) < insideLabelSizeLimit)
{
ld.prev.next = ld.next;
ld.next.prev = ld.prev;
removedLabels.push(ld);

result object to arrayList

<lfm status="ok">
<artists user="Ewout1" page="1" perPage="50" totalPages="36" total="1766">
<artist>
<name>Have Heart</name>
<playcount>2582</playcount>
<tagcount>0</tagcount>
<mbid>e519e012-e1a3-4592-b3f6-5a16227ab654</mbid>
<url>http://www.last.fm/music/Have+Heart</url>
<streamable>1</streamable>
<image size="small">http://userserve-ak.last.fm/serve/34/36974461.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/36974461.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/36974461.jpg</image>
<image size="extralarge">http://userserve-ak.last.fm/serve/252/36974461.jpg</image>
<image size="mega">
http://userserve-ak.last.fm/serve/_/36974461/Have+Heart+s+final+show+of+thei.jpg
</image>
</artist>
...
</lfm>
I have an httpservice that returns this xml-file. What I want to do is put all the names from the artists in one Arraylist. This is my code, but it doesn't work, and searches haven't helped me.
private var arArtists:ArrayList;
arArtists = event.result.artists.artist.name;
As mentioned in a comment by ToddBFisher, you will have to iterate through your XMLList and add each item to the ArrayList. XMLLists inherit directly from the base Object and has no direct inheritance relation to ArrayLists.
This block would work, but will be a point of contention based on the size of your data set.
var list:ArrayList = new ArrayList();
var name:XML;
for each(name in xml.artists.artist.name)
{
list.addItem(name);
}
Best of luck!