As a change of pace from complex visualizations, here's simple Material Design gauge control implemented in pure CSS/HTML. The implementation includes an optional JavaScript component to change the gauge value dynamically. If you'd like to use the gauge on your web pages (it's open source), you can download it from GitHub, where you'll also find documentation on how to use it. In this post we'll walk through the implementation to understand how it works.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Material Design Gauge</title>
<meta name="description"
content="Simple Material Design gauge control implemented in pure CSS/HTML">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
color: #444;
font-family: Varela, Helvetica, Arial, sans-serif;
font-size: 24px;
font-weight: normal;
height: 32px;
letter-spacing: normal;
line-height: 32px;
}
p {
text-align: center;
}
/*
* #### Gauge Component
*
* The standard markup for the component is:
*
* <div class="gauge">
* <div class="gauge--container">
* <div class="gauge--marker"></div>
* <div class="gauge--background"></div>
* <div class="gauge--center"></div>
* <div class="gauge--data"></div>
* <div class="gauge--needle"></div>
* </div>
* <div class="gauge--labels">
* <span class="gauge--label__low">No</span>
* <span class="gauge--label__spacer"></span>
* <span class="gauge--label__high">Yes</span>
* </div>
* </div>
*/
/*
* First define all of the relevant rules that aren't dependent
* on the size of the gauge. We want to collect the size-depenent
* rules in one place to make it easier to adjust the size.
*/
.gauge {
position: relative;
}
.gauge--container {
margin: 0;
padding: 0;
position: absolute;
left: 50%;
overflow: hidden;
text-align: center;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
-ms-transform: translateX(-50%);
-o-transform: translateX(-50%);
transform: translateX(-50%);
}
.gauge--background {
z-index: 0;
position: absolute;
background-color: #C0D3D3;
top: 0;
border-radius: 300px 300px 0 0;
}
.gauge--data {
z-index: 1;
position: absolute;
background-color: #007979;
margin-left: auto;
margin-right: auto;
border-radius: 300px 300px 0 0;
-webkit-transform-origin: center bottom;
-moz-transform-origin: center bottom;
-ms-transform-origin: center bottom;
-o-transform-origin: center bottom;
transform-origin: center bottom;
}
.gauge--center {
z-index: 2;
position: absolute;
background-color: #fff;
margin-right: auto;
border-radius: 300px 300px 0 0;
}
.gauge--marker {
z-index: 3;
background-color: #fff;
position: absolute;
width: 1px;
}
.gauge--needle {
z-index: 4;
background-color: #F77C15;
height: 3px;
position: absolute;
-webkit-transform-origin: left center;
-moz-transform-origin: left center;
-ms-transform-origin: left center;
-o-transform-origin: left center;
transform-origin: left center;
}
.gauge--labels {
display: table;
margin: 0 auto;
position: relative;
}
.gauge--label__low {
display: table-cell;
text-align: center;
}
.gauge--label__spacer {
display: table-cell;
}
.gauge--label__high {
display: table-cell;
text-align: center;
}
/*
* Now define the rules that depend on the size of
* the gauge. We start with sizing for a small mobile
* device.
*/
.gauge { height: calc(120px + 3em); }
.gauge--container { width: 240px; height: 120px; }
.gauge--marker { height: 120px; left: 119.5px; }
.gauge--background { width: 240px; height: 120px; }
.gauge--center { width: 144px; height: 72px; top: 48px; margin-left: 48px; }
.gauge--data { width: 240px; height: 120px; }
.gauge--needle { left: 120px; top: 117px; width: 120px; }
.gauge--labels { top: 120px; width: 240px; }
.gauge--label__low { width: 48px; }
.gauge--label__spacer { width: 144px; }
.gauge--label__high { width: 48px; }
/*
* Increase the gauge size slightly on larger viewports.
*/
@media only screen and (min-width: 400px) {
.gauge { height: calc(150px + 3em); }
.gauge--container { width: 300px; height: 150px; }
.gauge--marker { height: 150px; left: 149.5px; }
.gauge--background { width: 300px; height: 150px; }
.gauge--center { width: 180px; height: 90px; top: 60px; margin-left: 60px; }
.gauge--data { width: 300px; height: 150px; }
.gauge--needle { left: 150px; top: 147px; width: 150px; }
.gauge--labels { top: 150px; width: 300px; }
.gauge--label__low { width: 60px; }
.gauge--label__spacer { width: 180px; }
.gauge--label__high { width: 60px; }
}
/*
* As an option, the `gauge__liveupdate` class can be added
* to the main gauge element. When this class is present,
* we add a transition that animates any changes to the gauge
* value. Currently, the app does not use this option because
* all the inputs that can change gauge values are present
* on tab panels that are different from the gauge itself.
* Therefore, users won't be able to see any gauge changes
* when they make input changes. The code is available, though,
* should this change.
*/
.gauge__liveupdate .gauge--data,
.gauge__liveupdate .gauge--needle {
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-ms-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}
/*
* For a given gauge value, x, ranging from 0.0 to 1.0, set
* the `transform: rotate()` property according to the
* following equation: `-0.5 + 0.5x turns` The default
* properties below represent an x value of 0.
*/
.gauge--data {
-webkit-transform: rotate(-.50turn);
-moz-transform: rotate(-.50turn);
-ms-transform: rotate(-.50turn);
-o-transform: rotate(-.50turn);
transform: rotate(-.50turn);
}
.gauge--needle {
-webkit-transform: rotate(-.50turn);
-moz-transform: rotate(-.50turn);
-ms-transform: rotate(-.50turn);
-o-transform: rotate(-.50turn);
transform: rotate(-.50turn);
}
</style>
</head>
<body>
<p>Hover over me</p>
<div class="gauge gauge__liveupdate" id="gauge">
<div class="gauge--container" id="gauge-container">
<div class="gauge--marker"></div>
<div class="gauge--background"></div>
<div class="gauge--center"></div>
<div class="gauge--data"></div>
<div class="gauge--needle"></div>
</div>
<div class="gauge--labels mdl-typography--headline">
<span class="gauge--label__low">No</span>
<span class="gauge--label__spacer"></span>
<span class="gauge--label__high">Yes</span>
</div>
</div>
<script>
// #### Gauge
// The Gauge object encapsulates the behavior
// of simple gauge. Most of the implementation
// is in the CSS rules, but we do have a bit
// of JavaScript to set or read the gauge value
function Gauge(el) {
// ##### Private Properties and Attributes
var element, // Containing element for the info component
data, // `.gauge--data` element
needle, // `.gauge--needle` element
value = 0.0, // Current gauge value from 0 to 1
prop; // Style for transform
// ##### Private Methods and Functions
var setElement = function(el) {
// Keep a reference to the various elements and sub-elements
element = el;
data = element.querySelector(".gauge--data");
needle = element.querySelector(".gauge--needle");
};
var setValue = function(x) {
value = x;
var turns = -0.5 + (x * 0.5);
data.style[prop] = "rotate(" + turns + "turn)";
needle.style[prop] = "rotate(" + turns + "turn)";
};
// ##### Object to be Returned
function exports() { };
// ##### Public API Methods
exports.element = function(el) {
if (!arguments.length) { return element; }
setElement(el);
return this;
};
exports.value = function(x) {
if (!arguments.length) { return value; }
setValue(x);
return this;
};
// ##### Initialization
var body = document.getElementsByTagName("body")[0];
["webkitTransform", "mozTransform", "msTransform", "oTransform", "transform"].
forEach(function(p) {
if (typeof body.style[p] !== "undefined") { prop = p; }
}
);
if (arguments.length) {
setElement(el);
}
return exports;
};
var gauge = new Gauge(document.getElementById("gauge"));
gauge.value(0.25);
document.getElementById("gauge-container").addEventListener("mouseover", function() {
gauge.value(0.75);
});
document.getElementById("gauge-container").addEventListener("mouseout", function() {
gauge.value(0.25);
});
</script>
</body>
</html>