Skip to main content

Create an HTML Canvas Widget: Introductory Demo

Learn how to design a custom HTML widget in Ubidots.

David Sepúlveda avatar
Written by David Sepúlveda
Updated this week

Requirements

1. Introduction

Ubidots offers many off-the-shelf widgets that cover visualization and control needs for your IoT projects. However, in some cases, you may need to code your customized widgets using the HTML Canvas widget, which is why we want to demonstrate its capabilities to manage and display data using HTML/JS/CSS code.

In this introductory HTML Canvas demo, we will create a simple widget to monitor a dynamic variable. Imagine you have a temperature value on a 0-100 degree scale.

  • Values below 30, a blue LCD screen is displayed.

  • Values between 30 and 70, a green LCD screen is displayed.

  • Values above 70, a red LCD screen is displayed.

The scale can be powered by a sensor's variable or manual control based on your coding and desired application. For the purpose of this example we use the Slider widget to manually change the values. To start, you should have created both a temperature variable and a Slider widget. If you haven't created any of them yet, please refer to the following articles to learn how to do it.

Once done, you can continue with this guide.

2. Create a Custom HTML Canvas Widget

Now, let's see how we created this custom widget.​

Step 1: Log in to your Ubidots account.
Step 2: Go to "dashboards" and click on the upper right "+" button.
Step 3: Click on HTML Canvas option. The settings menu opens up.
Step 4: Click on the "edit code" option (next to "code editor").
Step 5: Insert the below HTML, CSS and JS codes in the respective tabs.

In the first tab, we will insert the HTML code. This code will create the markup for our widget.

HTML CODE:

<div id="image_background">
<p>Last value:</p>
<span id="image_background_text">No data</span>
</div>

The second tab is CSS. This code will give style to our HTML markup.

CSS CODE:

@import url('https://fonts.googleapis.com/css?family=VT323');
#image_background {
background: url('https://i.imgur.com/uuYygjz.png') 0 / contain no-repeat;
font-family: "VT323";
font-size: 25px;
letter-spacing: 10.5px;
height: 220px;
width: 450px;
}
#image_background p, #image_background_text {
margin: 0;
left: 75px;
position: absolute;
}
#image_background p {
top: 88px;
font-size: 25px !important;
}
#image_background_text {
top: 121px;
}

The third tab is for JavaScript, the logic code of our widget.

You just need to replace the variable ID in it.

JS CODE:

const DEVICE_LABEL = "<DEVICE-LABEL>";      // <-- Replace this
const VARIABLE_LABEL = "<VARIABLE-LABEL>"; // <-- Replace this

function changeImage(bgElement, value) {
const background = {
blue: "https://i.imgur.com/ZE0W7Yx.png",
normal: "https://i.imgur.com/uuYygjz.png",
yellow: "https://i.imgur.com/RHvDkUE.png",
};

let choose = "normal";
if (value <= 30) {
choose = "blue";
bgElement.style.color = "white";
} else if (value > 30 && value <= 70) {
choose = "yellow";
bgElement.style.color = "white";
}

bgElement.style.background = `url(${background[choose]}) 0 / contain no-repeat`;
}

async function fetchInitialValue(deviceLabel, variableLabel, token, bgElement, textElement) {
ubidots.api.authenticate(token);

const response = await ubidots.api.variables
.addRawParams({
'device__label': deviceLabel,
'label': variableLabel,
})
.first();


const value = response.data.lastValue.value;

if (value != null) {
textElement.textContent = value;
changeImage(bgElement, value);
return value;
}

return null;
}

function connectMQTT(deviceLabel, variableLabel, token, bgElement, textElement, initialValue) {
let lastValue = initialValue;

const topic = `/v1.6/devices/${deviceLabel}/${variableLabel}/lv`;
const clientId = "mqttjs_" + Math.random().toString(16).substr(2, 8);

const options = {
keepalive: 60,
clientId: clientId,
protocolId: "MQTT",
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
username: token,
will: {
topic: "WillMsg",
payload: "Connection Closed abnormally..!",
qos: 0,
retain: false,
},
};

const client = mqtt.connect("wss://industrial.api.ubidots.com:8084/mqtt", options);

client.on("connect", () => {
console.log("Connected to Ubidots MQTT broker");
client.subscribe(topic, (err) => {
if (!err) {
console.log(`Subscribed to topic: ${topic}`);
} else {
console.error("Subscription error:", err);
}
});
});

client.on("message", (_, message) => {
const newValue = parseFloat(message.toString());
if (!isNaN(newValue) && newValue !== lastValue) {
lastValue = newValue;
textElement.textContent = newValue;
changeImage(bgElement, newValue);
}
});

client.on("error", (err) => {
console.error("MQTT connection error:", err);
});

client.on("close", () => {
console.warn("MQTT connection closed");
});

return client;
}

(async function init() {
const token = ubidots.token;
const bg = document.getElementById("image_background");
const text = document.getElementById("image_background_text");

const initialValue = await fetchInitialValue(DEVICE_LABEL, VARIABLE_LABEL, token, bg, text);
connectMQTT(DEVICE_LABEL, VARIABLE_LABEL, token, bg, text, initialValue);
})();

Step 6: Save the code by clicking on the "accept" button.

IMPORTANT NOTE: Back in the widget's settings panel, there is a section used to import 3rd party libraries. In this example we will use the Google CDN for jQuery. A CDN is a service given by a third party that hosts this library and lets us use it in our application.

Please copy and paste the following URL into the text box:

https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js

jQuery is a library that will let us manipulate the DOM simply. The DOM is literally everything you see in your browser.


With this final step, you have completed your custom color-adjusting widget based on predetermined trigger values of 30 and 70.

Your final widget should look like this:​

3. Adjust contrast

To further display the capabilities of this HTML Canvas, let's keep going. The demo widget was designed to adjust a display based on value triggers (30 & 70). But, what if you wanted to add one more layer and overlay an image onto another image? This is also possible with Ubidots' HTML Canvas. Using an LCD image, we coded the HTML to adjust its contrast according to the value of a variable. For this, LCD screens use analog input. This lets you change the contrast of the screen by varying the current, we will do something similar here with a new variable.

Step 1: Create a new variable.
Step 2: Use the new assets of the screen.

As we are just modifying the display of the LCD screen, we need to separate the display screen from the border. We will work with two images: The LCD screen body and the LCD.

Step 3: Let's take a look back at our example and edit it to incorporate this new layer.

HTML CODE:

<div id="image_background">
<img id="display_background" src="http://i.imgur.com/meoKhMG.png" />
<p>Last value:</p>
<span id="image_background_text">No data</span>
</div>

CSS CODE:

@import url('https://fonts.googleapis.com/css?family=VT323');
#image_background {
background: url('http://i.imgur.com/ehNdUxY.png') 0 / contain no-repeat;
font-family: "VT323";
font-size: 25px;
letter-spacing: 10.5px;
height: 220px;
width: 450px;
}
#image_background p, #image_background_text {
margin: 0;
left: 75px;
position: absolute;
}
#image_background p {
top: 88px;
font-size: 25px !important;
}
#image_background_text {
top: 121px;
}
#display_background {
height: 94px;
position: absolute;
top: 70px;
left: 52px;
pointer-events: none;
}

JS CODE:


const DEVICE_LABEL = "<DEVICE-LABEL>";
const CONTROL_VARIABLE_LABEL = "<VARIABLE-LABEL>";
const CONTRAST_VARIABLE_LABEL = "<CONTRAST-VARIABLE-LABEL>";

function updateValueText(textElement, value) {
textElement.textContent = value;
}

function changeContrast(imgElement, value) {
const contrast = (value / 1023) * 4;
imgElement.style.filter = `contrast(${contrast})`;
}

async function fetchInitialValue(deviceLabel, variableLabel, token) {
ubidots.api.authenticate(token);
const response = await ubidots.api.variables
.addRawParams({
device__label: deviceLabel,
label: variableLabel,
})
.first();

return response.data.lastValue.value;
}

function connectMQTT(deviceLabel, variableLabel, token, onNewValue) {
const topic = `/v1.6/devices/${deviceLabel}/${variableLabel}/lv`;
const clientId = "mqttjs_" + Math.random().toString(16).substr(2, 8);

const options = {
keepalive: 60,
clientId: clientId,
protocolId: "MQTT",
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
username: token,
will: {
topic: "WillMsg",
payload: "Connection Closed abnormally..!",
qos: 0,
retain: false,
},
};

const client = mqtt.connect("wss://industrial.api.ubidots.com:8084/mqtt", options);

client.on("connect", () => {
console.log(`Connected. Subscribing to ${topic}`);
client.subscribe(topic, (err) => {
if (err) {
console.error(`Error subscribing to ${topic}`, err);
}
});
});

client.on("message", (msgTopic, message) => {
if (msgTopic === topic) {
const newValue = parseFloat(message.toString());
if (!isNaN(newValue)) {
onNewValue(newValue);
}
}
});

client.on("error", (err) => console.error("MQTT error:", err));
client.on("close", () => console.warn("MQTT closed"));

return client;
}

(async function init() {
const token = ubidots.token;

const img = document.getElementById("display_background");
const text = document.getElementById("image_background_text");

const [initialControl, initialContrast] = await Promise.all([
fetchInitialValue(DEVICE_LABEL, CONTROL_VARIABLE_LABEL, token),
fetchInitialValue(DEVICE_LABEL, CONTRAST_VARIABLE_LABEL, token),
]);

if (initialControl != null) {
updateValueText(text, initialControl);
}

if (initialContrast != null) {
changeContrast(img, initialContrast);
}

connectMQTT(DEVICE_LABEL, CONTROL_VARIABLE_LABEL, token, (value) => {
updateValueText(text, value);
});

connectMQTT(DEVICE_LABEL, CONTRAST_VARIABLE_LABEL, token, (value) => {
changeContrast(img, value);
});
})();

IMPORTANT NOTE: Before saving the new code and trying it out, a second third-party library will be needed. As in the first example, please add the following library to your HTML canvas:

https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.0/d3.min.js

Finally, this is an example of how this demo would look like:


If you develop something amazing, please share it with us on Facebook, Twitter, or Youtube.

Did this answer your question?