This post will be about a so called heat map that I have created in JavaFX. Some years ago I was working on a tool to visualize so called wafer maps that display defect data (from phyiscal particles, electrical defects, scratches etc.) of a semiconductor production process. If you have a typcial 300mm wafer it could contain several thousands of dies (chips). A wafer will be produced by applying layer after layer from bottom to top in different complex process steps. And at each process step new defects will be added to the wafer. After the last process step the wafer could contain around 10000 to 500000 defects (some of them are critical, some not). Because the defects are not only on the surface but also in deeper layers you might get a lot of defects on the same physical location.
To visualize the hot spots (spots with a high defect density) I used a heat map, which made it possible to directly see the critical areas on the wafer without taking a closer look to each defect.
Here are some links to images that will give you an idea on what I am talking about link, link, link, link
To make it short and release you from the short semiconductor production introduction, a heat map is a graphical representation of data where the individual values contained in a matrix are represented as colors (taken from Wikipedia).
There are two typical types of heat maps, one that visualizes the frequency of events and the other that visualizes the density of events. So you could use it for a wide range of use cases.
But as a software developer that is interested in UI etc. there is one use case that I was interested in most...visualizing the clicks or mouse positions on a user interface. There are tools to create these kind of maps for your website (e.g. link, link, link).
So I was asking myself how to implement such a heat map in JavaFX ?
Let's see what we have...
- Events with x,y coordinates
- Events could be at the same location
- The color should depend on the density of the events at a location
The idea is as follows, I will use a circle with a radial gradient from white to transparent (inside to outside) to visualize each event.
So one event would look like this...
If we add more events to the map it could look like this...
On the lower image one could already see the hot spot where most of the events are located.
The next step is to visualize this by using a colorful gradient. Therefor we have to map one of the following parameters to a color
- perceived brightness
We have to map each pixel to a color means we have to iterate over all pixels of the image and because of that we should use a fast calculation that will still give us a good result. So let's take a look at the brightness which will be calculated as follows...
b = (0.2126 * red + 0.7152 * green + 0.0722 * blue)
As you could see the brightness is a result of weighting the red, green and blue parts of a given color.
If we have calculated the brightness of each pixel we now have to map the brightness to a color. Therefor I created a gradient that looks nice...
The brightness is a value between 0 and 1 which means in this example...
- b == 0.0 => Color.rgb(0, 0, 255, 0.0)
- b == 0.5 => Color.rgb(255, 255, 0, 0.5
- b == 1.0 => Color.rgb(255, 0, 0, 1.0)
And if you implement this, it will lead you to a result that looks like this...
That's in principle what I did but in code it looks a bit different..
So let me shortly give you an idea on how I implemented it
1. Create a WritableImage for the event circle
- draw the radial gradient using the PixelWriter
2. Create a WritableImage for the Heat Map.
3. Create a Canvas node with the size of the heat map
- draw the event image for each event onto the GraphicsContext of the Canvas
4. Update the HeatMap
- create a snapshot of the Canvas node and store it in an WritableImage (monochrome image)
- iterate over each pixel in the monochrome image using the PixelReader
- get the color of the pixel and calculate the brightness
- get the mapped color for the calculated brightness from a given gradient
- write a pixel with the mapped color in the Heat Map image using the PixelWriter
With this approach we could keep the number of objects low because we create the event image with the radial gradient directly as an image and simply draw it on the GraphicsContext of the Canvas node which only uses one Node.
You could argue that I could also use an image that I have created with a drawing program (and that's possible too) but with my approach I'm able to weight events by creating an event image with a bigger or smaller radius in code. That means if you would like to visualize events in critical locations you could add them with a bigger radius so they will have a bigger impact on the visual result.
This is NOT a bullet proof implementation but more a fun side project that I was working on over the christmas holidays in my spare time so if you have ideas on how to improve it...feel free to do so and let me know the results...
One idea I had was adding the Heat Map to ScenicView to use it as a click map for a running JavaFX application. There are so many different use cases for Heat Maps that I would love to hear ideas from you in the comments if you like...
The code of this little project is hosted on github, so feel free to fork it...
I hope my explanation was not too confusing for you and you will enjoy playing around with heat maps like I do...
That's all I have to tell you today...so keep coding...