Friday, December 15, 2017

Friday Fun LIV - Path Gradient

Aloha,

I've always thought about how to implement a path gradient. Meaning to say a gradient that follows a path. First of all keep in mind that this kind of gradient is not used very often but sometimes it can create some really nice effects.
I've mainly saw the effect in circular components like the fitness gauge on the Apple Watch...


The color of each bar changes along the bar as you can see on the image. For this specific problem I've created the ConicalGradient in the past which works nicely for circular components but what about this...



If you would like to fill the path above with a gradient from blue over green and yellow to red you will see that it won't work with linear gradients. You can either fill it horizontally or vertically but both times the gradient won't follow the path itself. So here are two examples...


To fill it with a gradient along the path you need to split the path into small segments and fill each segment with a linear gradient along the line.
If you go with this approach you can achieve something like follows...


As you can see here it works nicely but that was not that easy to realize. The most important part of this little fun project was taking a closer look to the goodies that are already in the JDK. Especially the Java2D package contains a lot of nice stuff.
So my idea was to use this kind of path gradient in the JavaFX canvas node and lucky me the Java2D api is quite close to the JavaFX canvas api.
I won't go through the code because it's simply to much but let me explain the steps that are needed to achieve that result...

  • Split the path into it's elements (MoveTo, LineTo, QuadCurveTo, BezierCurveTo, Close etc.)
  • Split each element into single lines
  • Stroke each single line with the right part of the complete gradient
Like already mentioned above a lot of the functionality is already in the JDK but unfortunately it sometimes is hidden in private classes that are not accessible from the outside. So the solution for this problem was to fork all parts that I needed from the Apache Harmony project (the stuff Android is based on). So in principle I've forked the all classes from the Java2D packages that are related to bounds, shapes and transforms.
In the classes above I've removed all 3D related things and completely switched to doubles instead of floats.
I've also added several other classes and added functionality to some of the classes. One problem was for example that in the JDK there was no code to split lines into "sub-lines" which means I had to add this kind of things.
I've also renamed some classes to be more compatible to the JavaFX canvas. So the CubicCurve now is a BezierCurve etc.
In the end I've needed a path class that holds all elements and a lookup mechanism for the gradient.
Long story short with my current implementation the code to produce the path gradient will look like follows...


GradientLookup gradientLookup = new GradientLookup();
gradientLookup.setStops(new Stop(0.0, Color.BLUE),
                        new Stop(0.25, Color.LIME),
                        new Stop(0.5, Color.YELLOW),
                        new Stop(0.75, Color.ORANGE),
                        new Stop(1.0, Color.RED));

Path path = new Path();
path.moveTo(91, 36);
path.lineTo(182, 124);
path.bezierCurveTo(248, 191, 92, 214, 92, 214);
path.bezierCurveTo(-26, 248, 200, 323, 200, 323);
path.bezierCurveTo(303, 355, 383, 141, 383, 141);


double lineWidth = 20;


Canvas            canvas = new Canvas(400, 400);
GraphicsContext2D ctx    = canvas.getGraphicsContext2D();
PathGradient.strokePathWithGradient(ctx, path, gradientLookup, lineWidth, StrokeLineCap.ROUND);


So as you can see I've tried to keep the api as close to the JavaFX canvas api as possible but instead of drawing the path directly on the canvas context you first create the path object, define a gradient lookup which contains the list of stops and colors you need for the gradient and define width for the stroked line.

That's already neat but it could even be better because in the Path class you will find a SVGParser and with this you can directly use a SVG path string from an SVG file.
As an example here is the SVG file of the path on the above image...


<?xml version="1.0" standalone="no"?>

<!-- Generator: Adobe Fireworks CS6, Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: 0.6.1  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="Untitled-Page%201" viewBox="0 0 500 500" style="background-color:#ffffff00" 
    version="1.1"xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
    x="0px" y="0px" width="500px" height="500px">
    <path d="M 91 36 L 182 124 C 248 191 92 214 92 214 C -26 248 200 323 200 323 C 303 355 383 141 383 141 " stroke="#000000" stroke-width="1" fill="none"/>
</svg>


And with the the mentioned parser the code above could also be written like follows...

Path path = new Path();
path.appendSVGPath("M 91 36 L 182 124 C 248 191 92 214 92 214 " +
                   "C -26 248 200 323 200 323 C 303 355 383 141 383 141");

double lineWidth = 20;

Canvas canvas       = new Canvas(400, 400);
GraphicsContext ctx = canvas.getGraphicsContext2D();

PathGradient.strokePathWithGradient(ctx, path, gradientLookup, lineWidth, StrokeLineCap.ROUND);

To me that looks quite ok :)
As always I do not really have any use case yet so the whole thing is not tested for all possible use cases but at least it will give you an idea what is possible :)

And also as always the code is available on github.

Well that's it for today...I hope it was somehow interesting...keep coding... :)

Wednesday, December 6, 2017

William Playfair inspired charts...

Aloha,

Today I just would like to share a little blogpost about a chart that I've implemented last Friday evening. The chart design is inspired by William Playfair who invented this kind of chart to visualize economic data like import and export of theUS in relation to other countries.
One of his charts looks like follows...

The idea is pretty simple, take two line charts and fill the area between the two charts with a color. The color depends on which line is on top. With this technique one can visualize for example gain and loss of processes etc.
Like mentioned the idea is simple but the realization is not as trivial as it looks. The problem here is that you have two separate line charts that you have to combine to not only one polygon but to multiple polygons.
So every time the two lines cross each other one has to fill the area before the intersection point with one color and the area after the intersection point with the other color.
After thinking about the chart for a bit I've got an idea on how to realize that behavior and here is one result...


As always I do not really have a use case for that chart but I can imagine that it might be useful for someone out there :)

So if you would like to play around with it, feel free to check out the code at github.

I hope to find some time within the next days to add some interactivity to the chart...so stay tuned :)

Well...I guess that's it for today...so keep coding... :)

Friday, December 1, 2017

Friday Fun LIII - Sankey Plots

Aloha everyone,

Creating charts is really fun...again Thomas Nield(@thomasnield9272) pointed me to a nice chart which is called Sankey chart and again I could not withstand to try my best to implement it in JavaFX.
To give you an idea on what I'm talking about here is an example of such a Sankey plot...


Compared to last weeks Circular plots these plots can be multilevel and after searching the web for some hours I figured out that you can find all sorts of Sankey plots which might look completely different...here another example...


That doesn't make it easier to implement such a chart and so I've started reading about the history of that chart. In the end it turned out that the main purpose of this kind of chart is the visualization of flows where the width of the arrows/lines is shown proportionally to the flow quantity.
So I had to make a decision which style I should follow and I've decided to go with the first visualization of the above pictures.
Lucky me there is also a version of the Sankey plots in the Google charts which I took as a template.
Here is a screenshot of what I've come with...


This is the more colorful version of the chart but it is also possible to create other versions as you can see here...


In this version I've used different parameters for the width of the items and here also the direction of the flow is indicated by arrows (but I only support one direction anyway).

To get nice results you have to keep in mind that in my implementation you have to think about how to order the items in the chart when adding it to the control.
Meaning to say I do not have some hyper smart algorithm that do some fancy automatic sorting of items but you have to use your own brain and think about the chart before you create it. For the example above I've added the items exactly in the order as they appear on the chart which would look like follows...

// Setup chart itemsPlotItem brazil      = new PlotItem("Brazil", Colors.LIGHT_BLUE.get());
PlotItem mexico      = new PlotItem("Mexico", Colors.ORANGE.get());
PlotItem usa         = new PlotItem("USA", Colors.ORANGE.get());
PlotItem canada      = new PlotItem("Canada", Colors.LIGHT_RED.get());

PlotItem germany     = new PlotItem("Germany", Color.web("#FF48C6"));

PlotItem portugal    = new PlotItem("Portugal", Colors.LIGHT_BLUE.get());
PlotItem spain       = new PlotItem("Spain", Colors.LIGHT_GREEN.get());
PlotItem england     = new PlotItem("England", Colors.LIGHT_RED.get());
PlotItem france      = new PlotItem("France", Colors.LIGHT_GREEN.get());

PlotItem southAfrica = new PlotItem("South Africa", Colors.YELLOW.get());
PlotItem angola      = new PlotItem("Angola", Colors.PURPLE.get());
PlotItem morocco     = new PlotItem("Morocco", Colors.YELLOW.get());
PlotItem senegal     = new PlotItem("Senegal", Colors.PURPLE.get());
PlotItem mali        = new PlotItem("Mali", Colors.BLUE.get());

PlotItem china       = new PlotItem("China", Colors.BLUE.get());
PlotItem japan       = new PlotItem("Japan", Colors.GREEN.get());
PlotItem india       = new PlotItem("India", Colors.GREEN.get());

After that is done you have to define the connections between the items by defining only the outgoing streams for each item. Because that's a lot for the chart above I will only show you the ones for the first column which will look as follows...

// Setup flowsbrazil.addToOutgoing(portugal, 5);
brazil.addToOutgoing(france, 1);
brazil.addToOutgoing(spain, 1);
brazil.addToOutgoing(england, 1);
canada.addToOutgoing(portugal, 1);
canada.addToOutgoing(france, 5);
canada.addToOutgoing(england, 1);
mexico.addToOutgoing(portugal, 1);
mexico.addToOutgoing(france, 1);
mexico.addToOutgoing(spain, 5);
mexico.addToOutgoing(england, 1);
usa.addToOutgoing(portugal, 1);
usa.addToOutgoing(france, 1);
usa.addToOutgoing(spain, 1);
usa.addToOutgoing(england, 5);

As mentioned you define the streams that goes from each item to other items with their values.
After that is done you can setup the chart using the SankeyPlotBuilder as follows...

SankeyPlot sankeyPlot = SankeyPlotBuilder.create()
                                         .prefSize(600, 400)
                                         .items(brazil, mexico, usa, canada,germany,
                                                portugal, spain, england, france,
                                                southAfrica, angola, morocco, 
                                                senegal, mali, china, japan, india)
                                         .build();

And that's all it takes to create such a chart. Because the chart is again based on the JavaFX Canvas node there is no interactivity at the moment but I'm already working on a little project that will make it possible to have interactivity in the future...so stay tuned :)

Of course the code is available on github as always.

That's it for today...so keep coding...

Friday, November 24, 2017

Friday Fun LII - Circular Plots

Aloha,

Slowly getting into the charting business ;)
After Thomas Nield (@thomasnield9272) pointed me to so called Circular Plots I've couldn't hold back. If you know these charts you might understand why I was so keen on creating such a chart. 
So to give you an example here is an image of such a chart...




To be honest when I first saw the plot I was fascinated even without knowing how to read it and without knowing what this chart is good for. But after I took a look at more and more of those charts I got an idea on how to use them.
So I'm not a data scientist which is the reason why I've implemented it in a way that seemed logical for me.
The biggest problem was to find some data that I could use for the visualization. So in the end I've decided to take the public available data from the current parliamentary election in Germany.
To give you an idea what my chart is visualizing I think I need to explain it a bit. Each section on the chart shows one party with it's name, color and the voting result related to the number of eligible voters (61.5 Million).
Because there was a new party this year it was interesting to see where did the voters came from, so my chart visualizes the migration from all parties to other parties. The bigger the arrow the more voters migrated from the party to another.
So here is my chart...


So in the chart one can see that most of the AfD voters came from the Union party, the so called Non-Voters ("Nichwaehler") and others.
I've also created another chart that shows fictive data about travellers that travel between some asian countries. As always I do not have any use case for this chart and cannot guarantee that it is useful for real data analysis but at least it works for me and was fun to create. So here is the other chart...


At the moment there is not interactivity in this chart but if I will find some time I will definitely add it.

As always you can find the code on github.

That's it for today...enjoy the upcoming weekend and...keep coding ;)

Monday, November 20, 2017

Just some conversion tool...

Aloha,

Last week I was in Switzerland and somehow got reminded on some tool that I've created some time ago which might be handy for some of you. It is a simple unit conversion tool which supports conversion between the following categories of units:
- ACCELERATION 
- ANGLE 
- AREA 
- DATA 
- CURRENT 
- ELECTRIC_CHARGE 
- ENERGY 
- FORCE 
- HUMIDITY 
- LENGTH 
- LUMINANCE 
- LUMINOUS_FLUX 
- MASS 
- PRESSURE 
- SPEED 
- TEMPERATURE 
- TEMPERATURE_GRADIENT 
- TIME 
- TORQUE 
- VOLUME 
- VOLTAGE 
- WORK
As an example let's convert a temperature in Celsius in Fahrenheit and Kelvin which would 
look as follows: 
Converter temperatureConverter = new Converter(TEMPERATURE, CELSIUS);

double celsius    = 32.0;
double fahrenheit = temperatureConverter.convert(celsius, FAHRENHEIT);
double kelvin     = temperatureConverter.convert(celsius, KELVIN);

System.out.println(celsius + "°C => " + fahrenheit + "°F => " + kelvin + "°K");

So first you create a Converter instance with a category and a base unit (here Temperature 
as category and Celsius as base unit).
After that is done you can convert celsius based temperatures to other units like 
Fahrenheit and Kelvin.
In addition I've also added a method to shorten long numbers with abbreviations. 
Sometimes this is really useful when working with big numbers, 
a little example would look like follows...

System.out.println(Converter.format(1_500_000, 1));

System.out.println(Converter.format(1_000_000, 0)); 
And the result will look like this... 

1.5M 

1M

The format method will support the following abbreviations
- kilo 
- Mega 
- Giga 
- Tera 
- Peta 
- Exa 
- Zetta 
- Yotta

Not really sure if you can use it but at least I wanted to share it with you folks... :) The code is as always available on github. That's it for today...keep coding...

Friday, November 17, 2017

Friday Fun LI - Horizon Charts

Aloha,

Another Friday and again time for some fun... :)
Last week someone pointed me to so called horizon charts which are a really nice way to visualize data in a compact way.
To give you an idea what they look like here is an example...




The idea behind it is to reduce the amount of space and preserve the information by splitting a graph in the vertical direction in so called bands.

The following image will show how it works (it was taken from here)...



As you can see in the image above first you flip the negative values to the positive side by inverting them (2nd graph).
Now you split the vertical axis in bands and place all of them on the 0 value of the y-axis. The image above shows an example for 1 band, 2 bands, 3 bands and 4 bands.
For every band you change the color (usually from a lighter to a darker color). So you get some kind of a heat map where darker colors visualize higher values.

So my JavaFX implementation looks like follows for some random data...



For the implementation of this chart I used the JavaFX Canvas node again which should also work with lots of data because it will only use 1 node on the scene graph. So the HorizonChart will take 2 nodes on the scene graph, one for the Region and one for the Canvas.
At the moment you can split the graph in up to 5 bands which seems to be enough for most of the visualizations I've found.
To get nice colors for the chart I've also added some convenience methods in the Helper class to create color variations for a given color.
For example let's assume you would like to visualize positive values with shades of blue and negative values with shades of red and you will use 4 bands.
To create the colors simply use the following code...


List<Color> positiveColors = Helper.createColorVariationsAsList(Color.BLUE, 4);
List<Color> negativeColors = Helper.createColorVariationsAsList(Color.RED, 4);

Now you can set these colors on the chart instance as follows...


HorizonChart chart = new HorizonChart(4, series);
chart.setPositiveColors(positiveColors);
chart.setNegativeColors(negativeColors);

The HorizonChart doesn't come with any axis or bounds which should make it easy to implement it in your own more feature rich chart. Meaning to say the chart will always take the whole space you defined for it using the setPrefSize() method.
So you decide the size of the chart and the chart will scale it's content always to the defined area.
When using not so many data points you might want to smooth the chart which is possible by using the setSmoothed() method of the chart or by setting the smoothed value in the constructor (usually you don't need smoothing in this kind of chart because it is normally used to visualize big datasets).


The HorizonChart will listen to changes in the Series of Data objects and will redraw itself when changes occur.
If you just change the value in the existing Data objects the chart won't notice but you have to explicitly call the charts redraw() method.

The Series will also fire SeriesEvents  which will be used to either redraw the chart or select data in the chart. In the provided Demo class you will see that I simply fire one SeriesEvent of SeriesEventType.REDRAW to redraw all charts because they are all using the same Series. Otherwise I would have to call the redraw() method of each chart after I've changed the data.

The HorizonChart also supports mouse interaction where you can click somewhere in the chart and it will show the current Data object values in a tooltip. In addition a SeriesEvent of type SeriesEventType.SELECT_DATA will be fired. By adding a SeriesEventListener to the Series you can get the selected data information from the SeriesEvent which contains the Data object of the selected point (just take a look at the provided Demo which prints the data of the selected point to the console).

The toString() methods of the Point, Data and Series class will return a JSON String which might come in handy sometimes.

Another thing that could be useful is the ability to decide if the reference value should be 0 or the first y-value in the Series items.
If you would like to visualize stock data you usually start with a given stock value. In this case you would like to use the first y-value of the data list as reference value and all following data should be visualized as negative value if it is below the reference and as positive value if it is above the reference value. In this case just call setReferenceZero(false) method.
But if you would like to visualize data that is alternating around zero (like the cos values in the provided Demo) you simply leave the reference value to 0 (which is the default value).

I think that's more or less everything I have to offer today...so I hope some of you can use it...I for myself have again no use for it :)

Oh and if you need something special or have an idea for a special graph/chart/control...just let me know.
 
And as always you can find the source code over at github

Enjoy your weekend and...keep coding... :)

Friday, November 10, 2017

Friday Fun L - Smooth Charts

Aloha,

If you ever worked with JavaFX charts you might know the need for smoothed line- and areacharts. 
If you search the web you will probably stumble upon an implementation over at fxexperience.com. I've used this version from Jasper for some time and it worked for me in 80% of all cases.
But sometimes you need more features or dynamic behavior and for TilesFX I've created a new version of such a chart which comes with the following features

  • line or area chart
  • smoothed or not smoothed
  • supports mouse interaction (click on the chart and get the y-value)
  • supports snap to ticks (click on the chart and the next point will be selected)
  • convenience methods
    • set visibility of symbols 
    • set series fill
    • set series stroke
    • set symbol background
    • set legend background
    • set legend text fill
    • set legend symbol background
    • set symbol size
    • get symbols (List<StackPane>)
    • get fillPath
    • get strokePath
    • get chart plot background
    • get horizontal/vertical gridlines
    • set chart plot background fill/background
    • set x-axis and y-axis tick label fill
    • set x-axis and y-axis tick mark fill
    • set x-axis and y-axis border fill

I'm not sure if these features will be useful for all of you but even if not I thought it might be worth sharing it with you. In principle most of those features I could have set using CSS but if I need to set it via code I always have to check the css styles etc. So for this reason I've added all those convenience methods :)

To smooth the chart I use a so called Catmull-Rom spline which has the advantage that it is tightly follows the control points of the chart.

The chart also comes with a property to change the number of points between the control points. Means if you just need a rough smoothing you could try a value of 2 or if you need a fine smoothing you might want to try 32 (the limit is 64 in my implementation). The default value is 16 and this gives nice results, so usually you don't need to change it. This factor depends on the size of the chart. For smaller charts even 8 gives nice results.

So here is a screenshot of the all four chart variants...





The code for this charts can be found in the Demo.java file that comes with the chart. Because in TilesFX I needed a smoothed chart which I can tweak from code without using CSS I've added a couple of convenience methods. If you use this chart as a replacement for a standard JavaFX AreaChart all the CSS stuff should also work. But if you (like me) need to tweak some things dynamically the convenience methods come in quite handy.

Here is a little video that show the mouse interactivity in action...



If the interactive property is set to true the the chart will fire a JavaFX event of type SmoothedChartEvent.DATA_SELECTED
The event contains a double property that contains the y value of the selected point. To get those events you have to add an EventHandler to the chart object as follows...


SmoothedChart<String, Number> areaChartSmoothed = new SmoothedChart<>(xAxis4, yAxis4);
areaChartSmoothed.getData().addAll(series4);
areaChartSmoothed.setSmoothed(true);
areaChartSmoothed.setChartType(ChartType.AREA);
areaChartSmoothed.setInteractive(true);
areaChartSmoothed.setSubDivisions(8);
areaChartSmoothed.setSnapToTicks(false);
areaChartSmoothed.setLegendVisible(false);

areaChartSmoothed.addEventHandler(SmoothedChartEvent.DATA_SELECTED, 
                                  e -> System.out.println(e.getValue()));

The tooltip shows different information dependent on the mode
  • snapToTicks = true  -> The name and the value of the selected Data object
  • snapToTicks = false -> The y-value of the selected point
It might be possible that you need more or different functionality so...feel free to fork the code on github as always :)

Just be aware that this class won't work in Java 9 in the current state because it makes use of the following internal api

  • com.sun.javafx.charts.Legend
  • com.sun.javafx.charts.Legend.LegendItem

These classes will be used in the following methods:

setLegendBackground() 
setLegendTextFill() 
setLegendSymbolFill()

So you could either remove those methods and it will run on JDK 9 or you have to fiddle around with some JVM command line parameters like

--add-exports javafx.controls/com.sun.javafx.charts=ALL_UNNAMED

To be honest I did not try that yet but it might work, otherwise just let me know and I will correct this blogpost :)

Well that's it for today...so keep coding... :)