Premise

Suppose you have a numeric range, and certain points within it. You need to visualize the segments that these points define within the range. For instance, given a range [A, B], and a list of points [a, b, c] within that range, we may assign colors to the segments [A, a), [a, b), [b, c) and [c, B], and plot them on a horizontal spectrum of sorts.

So let's do exactly that, and moreover convert our solution into a jQuery UI widget in order for it to be reusable.

Wishful Thinking

Ideally, our widget, starting with an empty div, should produce markup like this:

<div id="thermo">
    <div class="thermo-container">
        <span class="thermo-segment" style="width: 18.4615384615385%; background: yellow;"></span>
        <span class="thermo-segment" style="left: 18.4615384615385%; width: 26.1538461538462%; background: red;"></span>
        <span class="thermo-segment" style="left: 44.6153846153846%; width: 20%; background: green;"></span>
        <span class="thermo-segment" style="left: 64.6153846153846%; width: 35.3846153846154%; background: blue;"></span>
    </div>
</div>

i.e. a div container, consisting of a list of spans, for each one of our segments. The spans take their percentile width and color as inline styles. For this to work, we need the tiniest bit of CSS (most of which is there to produce rounded corners):

.thermo-segment {
    position: absolute;
    height: 12px;
}
.thermo-segment:first-child {
    -webkit-border-top-left-radius: 5px;
    -webkit-border-bottom-left-radius: 5px;
    -moz-border-radius-topleft: 5px;
    -moz-border-radius-bottomleft: 5px;
    border-top-left-radius: 5px;
    border-bottom-left-radius: 5px;
}
.thermo-segment:last-child {
    -webkit-border-top-right-radius: 5px;
    -webkit-border-bottom-right-radius: 5px;
    -moz-border-radius-topright: 5px;
    -moz-border-radius-bottomright: 5px;
    border-top-right-radius: 5px;
    border-bottom-right-radius: 5px;
}

Implementation

First of all, let's decide on what kind of parameterization our widget is allowed. Since we want to be generic we shall allow any kind of numeric values for points A and B, including negative ones. In addition, we will allow our widget's width to be scaled by accepting a width option and use it as a percentage, so in summary, our widget definition looks like so:

(function ($) {
    $.widget('gtryf.thermometer', {
        options: {
            min: 0,
            max: 100,
            size: 100,
            segments: []
        },

        // Rest of our widget definition goes here
    });

    // Private functions go here
{})(jQuery);

The segments option is an array of objects, each one consisting of a position and a color property, each object implying that we should paint the range from position to the previous point in the designated color.

Creating and destroying the widget is a simple task. All we need to do is create a container div, and give it a special CSS class, i.e.:

_create: function () {
    this.element.addClass('thermometer');

    this._container = $('<div class="thermo-container"></div>')
        .appendTo(this.element);

    this._setOptions({
        'min': this.options.min,
        'max': this.options.max,
        'size': this.options.size,
        'segments': this.options.segments
    });
}
_destroy: function () {
    this.element.removeClass('thermometer');
    this.element.empty();
}

Creation of the widget boils down to the setOptions() call, which updates the widget's option values. The setOptions() call is a widget factory inherited method, which in turn calls our overriden setOption() method. This method delegates the actual work to a private function which draws the UI. If any of our options change, we trigger the 'setOption' custom event, passing in the previous and new value:

_setOption: function (key, value) {
    var self = this, prev = this.options[key];
    var fnMap = {
        'min': function () {
            updateSegments(value, self.options.max, self.options.segments, self);
        },
        'max': function () {
            updateSegments(self.options.min, value, self.options.segments, self);
        },
        'segments': function () {
            updateSegments(self.options.min, self.options.max, value, self);
        }
    };

    this._super(key, value);

    if (key in fnMap) {
        fnMap[key]();

        this._triggerOptionChanged(key, prev, value);
    }
},
_triggerOptionChanged: function (optionKey, previousValue, currentValue) {
    this._trigger('setOption', {
        type: 'setOption'
    }, {
        option: optionKey,
        previous: previousValue,
        current: currentValue
    });
}

So most of the work happens in the private updateSegments() function. What does it look like? Well, here is the implementation:

var updateSegments = function (min, max, ticks, widget) {
    widget._container.find('.thermo-segment').remove();
    var width = max - min;

    if (typeof ticks != "undefined") {
        ticks.sort(function (a, b) {
            return a.position - b.position;
        });
        $.each(ticks, function (i, e) {
            var segment = $('<span class="thermo-segment"></span>'),
                left = min;
            if (i > 0) {
                left = ticks[i - 1].position;
                segment.css('left', ((left - min) / width * widget.options.size) + "%");
            }
            segment.css('width', ((e.position - left) / width * widget.options.size) + "%").css('background', e.color);

            segment.appendTo(widget._container);
        });
    }
}

A quick walkthrough: first of all, we remove any existing segments. Then, we calculate the total range as the difference between max and min. Following that, we sort the segments by position. Finally, for each segment we calculate its width and left position, as a percentage. After all this is done, we append the segment span to the container.

You can see the end result on this fiddle. Feel free to use/modify it if you find it useful.

Further Steps

It would be nice if the widget supported right-to-left ordering, or vertical orientation. In addition, as it stands, there is no kind of feedback on the point values.

Furthermore, this kind of widget would shine if combined with knockoutjs, giving it the ability to dynamically update itself based on point values taken from a viewmodel. With a little effort, this could be turned into a multi-range slider.

Have fun and, as always, happy coding!