QML Component Thermometer Indicator

See how to create a Thermometer Indicator QML component using Qt Creator 3.2.1 (Qt Version 5.3.2).  This element was also tested with Qt Creator 4.1.0 (Qt Version 5.5.1).

Reach Display Module Development Kits include Qt Creator, a cross-platform Integrated Developing Environment (IDE) for QML and Qt C++. It makes it easy to write, debug, and run programs. It offers autocomplete and help pages for Qt provided objects and components. Download the Qt Creator Installer from the link below. You do not need to purchase a license. Download it here.


This image shows what the component looks like in Qt Creator design mode.  Notice you can change the colors, fonts, tick length, tick intervals, min, max, and value properties.


We used a Rectangle and Canvas element to create this component. Tquickly set the background color and add a border, start with a Rectangle element.

Rectangle Element properties (//With Comments):

// Width of component in pixels
width: 50
// Height of component in pixels
height: 220
// Background color of the component
color: “transparent”
// Minimum value for the Thermometer
property real min: 0
// Maximum value for the Thermometer
property real max: 10
// Current value of the Thermometer
property real value
// Main color for the Thermometer used to create gradient
property color indicatorMainColor: “#ff0000”
// Light color for the Thermometer used to create gradient
property color tankLightColor: “#fd5656”
// Interval between tick marks
property real tickInterval: 2
// Length of tick marks in pixels
property int tickLength: 4
// Scale font size in pixels
property int scaleFontPixelSize: 13
// Scale font family
property string scaleFontFamily: “DejaVu Sans”
// Scale font style normal, bold
property string scaleFontStyle: “normal”
// Scale font color
property color scaleFontColor: “#000000”
// Adjustment used for drawing the thermometer. This is used to adjust for the top and bottom arcs, and to keep the top
// most ticker (max) text visible
readonly property real heightAdjustment: scaleFontPixelSize/2
// Calculate the height per value in pixels
readonly property real offset: (heightheightAdjustment*2) / (maxmin)
// Used to draw the top and bottom arc
property real radius
// Width of the indicator
property int indicatorWidth: 40
// boolean property used so we don’t redraw the tickmarks
property bool firstPaint: true

Note: Whenever the value is changed, the Canvas element is forced to repaint:

onValueChanged: {
    canvas.requestPaint();
}

The drawing of the Thermometer is done in the Canvas element’s onPaint function.

First, get the context to draw.

var ctx = canvas.getContext(“2d”);

Next figure out the longest text. Draw the scale and tick marks on the left hand side. Use the longest text length and tick mark length to adjust for the width of the thermometer drawn.

// Set scale font properties to measure text
ctx.font= scaleFontStyle + ” ” + scaleFontPixelSize + “px ‘” + scaleFontFamily + “‘”;
ctx.fillStyle = scaleFontColor;

// Calculate the longest text
var longestText = Math.max(ctx.measureText(min.toString()).width, ctx.measureText(max.toString()).width);

// x position where we will draw the Thermometer
var xPos = longestText + tickLength + 1;
// Set the radius for the top and bottom arcs
radius = (width – longestText – tickLength + 1)/2;
if (heightAdjustment < radius)
    heightAdjustment = radius;

See the image below for a design overview. It shows the reason behind needing to calculate the variable longestText, radius, and why it is necessary to calculate the property heightAdjustment:

With the length of the longest text in pixels known, draw an empty thermometer with a gray linear gradient color to give it a cylindrical look. Note: Use a linear gradient for the full width of the component and add three color stops.

// Clear the thermometer area
ctx.clearRect(xPos, heightAdjustment, width, height);

// Draw the thermometer base – I’m using a gray linear gradient to show an empty thermometer with a cylindrical shape
var grBase = ctx.createLinearGradient(xPos, 0, width, 0);
//Add color stops
grBase.addColorStop(0, “#919db2”);
grBase.addColorStop(0.5, “#ffffff”);
grBase.addColorStop(1, “#919db2”);
ctx.fillStyle = grBase;
ctx.fillRect(xPos, heightAdjustment, width, height – heightAdjustment*2);

// Draw the top arc
ctx.beginPath();
ctx.arc(longestText + tickLength + radius, heightAdjustment + 1, radius-1, 0, Math.PI, true);
ctx.fill();

This what the Thermometer looks like after this code runs. In this example, the Rectangle element has a black background color to help with visualization.

Next draw another rectangle based on the property value, indicatorWidth, and the property colors indicatorMainColor and indicatorLightColor.  To calculate the Y-position where the fill tank starts, use this formula:  (max – min – value) * offset.

Rectangle “Thermometer Fill”

// Draw the indicator based on the value
var grTank = ctx.createLinearGradient(xPos, 0, width, 0);
grTank.addColorStop(0, indicatorMainColor);
grTank.addColorStop(0.5, indicatorLightColor);
grTank.addColorStop(1, indicatorMainColor);
ctx.fillStyle = grTank;

// Determine the Y-position to draw the indicator rectangle
var widthOffset = (width – xPos – indicatorWidth)/2;
ctx.fillRect(xPos + widthOffset, ((max – min – value) * offset) + heightAdjustment , indicatorWidth, height);
// Clear excess from bottom
ctx.clearRect(xPos, height – heightAdjustment, width, heightAdjustment);

// Draw the bottom arc
ctx.fillStyle = grTank;
ctx.beginPath();
ctx.arc(longestText + tickLength + radius, height – heightAdjustment, radius-1, Math.PI, 2*Math.PI, true);
ctx.fill();

This is what the Thermometer looks like after the above code runs (note: using 0=min, 10=max, and 5=value):

Now, add the tick marks and the scale text to the left of the Thermometer.  Start from the top of the Thermomter using the pos variable and draw each text and tick mark until you get to the bottom of the Thermometer.

Thermometer Tick Marks and Scale Text (//With Comments)

// Draw the scale text and tick marks
ctx.font = scaleFontStyle + ” ” + scaleFontPixelSize + “px ‘” + scaleFontFamily + “‘”;
ctx.fillStyle = scaleFontColor;
var pos = 0;

for (var i=max; i >= min; i=itickInterval)
{
    //  Draw scale text – I add scaleFontPixelSize/4 to the height because I
    // found that this will center the text on the tick mark. Trial and error.
    ctx.fillText(i.toString(), 0, (pos*offset) + heightAdjustmentscaleFontPixelSize/4);

    // Draw Tick marks
    ctx.beginPath();
    ctx.moveTo(longestText + 1, pos*offset + heightAdjustment);
    ctx.lineTo(longestText + tickLength + 1, pos*offset + heightAdjustment);
    ctx.stroke();
    pos += tickInterval;
}

The finished Thermometer using these properties: min=0, max=10, value=5, tickInterval=2.

Even better, consider formatting the scale text to account for decimals or allow users to place the scale on the right side of the thermometer.  

Download the code.

Leave a Reply

Your email address will not be published. Required fields are marked *