QML Component Tank Level Indicator

See how to create a Tank Level 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.

Use a Rectangle and a Canvas element to create this component. To quickly configure the background color and add a border, start with a Rectangle element.
Rectangle Component Properties (//With Comments):
// Width of component in pixels
width: 140
// Height of component in pixels
height: 160
// Background color of the component
color: “transparent”
// Minimum value for the Tank level
property real min: 0
// Maximum value for the Tank Level
property real max: 10
// Current value of the Tank Level
property real value
// Main color for the Tank used to create gradient
property color tankMainColor: “#325125”
// Light color for the Tank used to create gradient
property color tankLightColor: “#a5cd38”
// 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 tank. This is used 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)
// 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 Tank 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 tank 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 Tank
var xPos = longestText + tickLength + 1;

See the image below for design overview. It shows the reason behind needing to calculate the variable longestText, and why we need to calculate the property heightAdjustment:

With the length of the longest text in pixels known, draw an empty tank 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.

// Draw the empty tank – I’m using a gray linear gradient to show an empty tank with a cylindrical shape

var grBase = ctx.createLinearGradient(longestText + tickLength + 1, 0, width, 0);
//Add color stops for gradient
grBase.addColorStop(0, “#919db2”);
grBase.addColorStop(0.5, “#ffffff”);
grBase.addColorStop(1, “#919db2”);
ctx.fillStyle = grBase;
ctx.fillRect(longestText + tickLength + 1, heightAdjustment, width, heightheightAdjustment*2);

This is what the Tank looks like after the 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, and colors tankMainColor and tankLightColor. To calculate the Y-position where the fill tank starts, use this formula:  (max – min – value) * offset.

Rectangle “Tank Fill”

//Draw the tank based on the value
var grTank = ctx.createLinearGradient(xPos, 0, width, 0);
grTank.addColorStop(0, tankMainColor);
grTank.addColorStop(0.5, tankLightColor);
grTank.addColorStop(1, tankMainColor);
ctx.fillStyle = grTank;
ctx.fillRect(xPos, ((maxminvalue) * offset) + heightAdjustment , width,height);

//Clear excess from bottom
ctx.clearRect(0, heightheightAdjustment, width, heightAdjustment);

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

Now, add the tick marks and scale text to the left of the Tank using the max and min values and step by the tickInterval property. Start from the top of the Tank using the pos variable and draw each text and tick mark until you get to the bottom of the Tank.

Tank 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 Tank using these properties: min=0, max=10, value=5, tickInterval=2.

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

Download the code

Leave a Reply

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