Friday, November 3, 2017

Friday Fun XLIX SunburstChart

Aloha again,

Last weekend I was looking for dashboards on the web and stumbled upon some nice charts that I found really interesting.
These charts are called RadialTreeMaps or SunburstCharts and one that I've found (guess it is from Excel) looks as follows...



It really represents a forward directed tree structure in a radial layout where the root node itself is not really visible. First of all I thought that might be easy because it looks like a multi-donut chart and in principle this is correct but the difference is the tree structure of this chart.

So it took some early morning hours to figure out how to get it right. So the first thing that I needed was a tree structure that is able to hold the data of this chart. 
Therefor I created one which works good enough for my chart and is build from one class.
The TreeNode class that I use to build the tree structure will also fire TreeNodeEvents of Type EventType.PARENT_CHANGED and EventType.CHILDREN_CHANGED so that you can register a listener to those events and react on changes of the tree.

Update:
I've changed the behavior in the way that all tree nodes fire their event via the root node of the tree. Means you just have to hook up a listener to the root of the tree to get informations.

The final idea behind this chart is to add it to TilesFX so I will use the same ChartData class in the tree structure.
I've decided to make use of the JavaFX canvas node again to keep the number of nodes on the scene graph low. This comes with the drawback that it is not trivial to implement mouse event handling for the segments. So in this version I don't support any mouse event handling. But I think I will also create a node based version...maybe during the next weekend :)
To create a graph like this you first have to setup the underlying tree. With my TreeNode class this will look like follows:


TreeNode tree   = new TreeNode(new ChartData("ROOT"));
TreeNode first  = new TreeNode(new ChartData("1st", 8.3, PETROL_0), tree);
TreeNode second = new TreeNode(new ChartData("2nd", 2.2, PINK_0), tree);
TreeNode third  = new TreeNode(new ChartData("3rd", 1.4, YELLOW_0), tree);
TreeNode fourth = new TreeNode(new ChartData("4th", 1.2, GREEN_0), tree);

TreeNode jan    = new TreeNode(new ChartData("Jan", 3.5, PETROL_1), first);
TreeNode feb    = new TreeNode(new ChartData("Feb", 3.1, PETROL_1), first);
TreeNode mar    = new TreeNode(new ChartData("Mar", 1.7, PETROL_1), first);
TreeNode apr    = new TreeNode(new ChartData("Apr", 1.1, PINK_1), second);
TreeNode may    = new TreeNode(new ChartData("May", 0.8, PINK_1), second);
TreeNode jun    = new TreeNode(new ChartData("Jun", 0.3, PINK_1), second);
TreeNode jul    = new TreeNode(new ChartData("Jul", 0.7, YELLOW_1), third);
TreeNode aug    = new TreeNode(new ChartData("Aug", 0.6, YELLOW_1), third);
TreeNode sep    = new TreeNode(new ChartData("Sep", 0.1, YELLOW_1), third);
TreeNode oct    = new TreeNode(new ChartData("Oct", 0.5, GREEN_1), fourth);
TreeNode nov    = new TreeNode(new ChartData("Nov", 0.4, GREEN_1), fourth);
TreeNode dec    = new TreeNode(new ChartData("Dec", 0.3, GREEN_1), fourth);

TreeNode week1  = new TreeNode(new ChartData("Week 1", 1.2, PETROL_2), feb);
TreeNode week2  = new TreeNode(new ChartData("Week 2", 0.8, PETROL_2), feb);
TreeNode week3  = new TreeNode(new ChartData("Week 3", 0.6, PETROL_2), feb);
TreeNode week4  = new TreeNode(new ChartData("Week 4", 0.5, PETROL_2), feb);

With this we have setup the tree and now we only have to create the control itself. To make it easier I've created a SunburstChartBuilder. So the code to create a sunburst chart like the one on the image above will look like follows:


SunburstChart sunburstChart = 
    SunburstChartBuilder.create()
                        .prefSize(400, 400)
                        .tree(tree)
                        .textOrientation(TextOrientation.TANGENT)
                        .useColorFromParent(false)
                        .visibleData(VisibleData.NAME)
                        .backgroundColor(Color.WHITE)
                        .textColor(Color.WHITE)
                        .decimals(1)
                        .interactive(false)
                        .build();

Now you just have to add this node to your scene graph and you should see something like this...



Not too bad :)

As you can see the control comes with the following properties
  • backgroundColor
  • textColor
  • visibleData (NONE, VALUE, NAME, NAME_VALUE)
  • textOrientation (HORIZONTAL, TANGENT, ORTHOGONAL)
  • decimals (0 - 5)
  • useColorFromParent (true, false)
  • interactive (true, false)
  • autoTextColor (true, false)
  • brightTextColor 
  • darkTextColor
  • useChartDataTextColor (true, false)
One can define a text color in the ChartData objects and if you would like to use the color defined in there you should set useChartDataTextColor to true.

You also have the ability to only define the fill color in the ChartData objects and let the chart automatically adjust the text color dependent on the segment fill color.
Therefor you can define a brightTextColor that will be used for dark segment fills and a darkTextColor that will be used for bright segment fills. The default bright text color is Color.WHITE and the default dark text color is Color.BLACK.

The useColorFromParent property can be used to set the color of all children of one group to the same color as the group root. Means the color of each ChartData object will be ignored except the one of the group root node.
So if I switch it on for the example above the result will look like this...

As you can see now all children of each group have the same color as the root node of this group. If you use this feature you just have to define the colors for the four root nodes (1st, 2nd, 3rd, 4th).

Means the code of the tree for the above chart would look like follows:


TreeNode tree   = new TreeNode(new ChartData("ROOT"));
TreeNode first  = new TreeNode(new ChartData("1st", 8.3, PETROL_0), tree);
TreeNode second = new TreeNode(new ChartData("2nd", 2.2, PINK_0), tree);
TreeNode third  = new TreeNode(new ChartData("3rd", 1.4, YELLOW_0), tree);
TreeNode fourth = new TreeNode(new ChartData("4th", 1.2, GREEN_0), tree);

TreeNode jan    = new TreeNode(new ChartData("Jan", 3.5), first);
TreeNode feb    = new TreeNode(new ChartData("Feb", 3.1), first);
TreeNode mar    = new TreeNode(new ChartData("Mar", 1.7), first);
TreeNode apr    = new TreeNode(new ChartData("Apr", 1.1), second);
TreeNode may    = new TreeNode(new ChartData("May", 0.8), second);
TreeNode jun    = new TreeNode(new ChartData("Jun", 0.3), second);
TreeNode jul    = new TreeNode(new ChartData("Jul", 0.7), third);
TreeNode aug    = new TreeNode(new ChartData("Aug", 0.6), third);
TreeNode sep    = new TreeNode(new ChartData("Sep", 0.1), third);
TreeNode oct    = new TreeNode(new ChartData("Oct", 0.5), fourth);
TreeNode nov    = new TreeNode(new ChartData("Nov", 0.4), fourth);
TreeNode dec    = new TreeNode(new ChartData("Dec", 0.3), fourth);

TreeNode week1  = new TreeNode(new ChartData("Week 1", 1.2), feb);
TreeNode week2  = new TreeNode(new ChartData("Week 2", 0.8), feb);
TreeNode week3  = new TreeNode(new ChartData("Week 3", 0.6), feb);
TreeNode week4  = new TreeNode(new ChartData("Week 4", 0.5), feb);

Here you see that we defined the colors only for the four root nodes of the chart.

Update:
After I spend another early morning now the control also supports interactivity. I've added an interactive property to the control which default value is false. In this case the chart is drawn by using the JavaFX canvas node only (for all charts this leads to 3 nodes on the scene graph). 
If you switch interactivity on the chart will be drawn using JavaFX Path elements. In this case the number of nodes on the scene graph will depend on the number of segments in the chart (for the the chart above it leads to 31 nodes).

The control contains a demo that you can start on the console with

gradle demo

This will start the following application:



The chart on the left side is the Canvas based chart where you don't have any interaction at all. The chart on the right side is the interactive chart that makes use of Path elements. 
Here you have a tooltip when you hover over a segment and if you click a segment it will fire a TreeNodeEvent of type EventType.NODE_SELECTED. With this feature you could hook up a listener to the root node of your tree structure and get notified when someone clicks on a segment.

So in principle this control contains two approaches, one that saves nodes on the scene graph with the cost of no interaction and the other which comes with interaction but with the cost of more nodes on the scene graph...it's your choice :)

As always you can find the code on github and you would like to start the demo that is part of the source code you simply have to pull the source code from github and start the demo on the console with

gradle demo

That's it for today, so enjoy the upcoming weekend and do not forget...keep coding... ;)

No comments:

Post a Comment