Multi Segment Display in JavaScript

In this post I will develop an SVG-based JavaScript multi-segment display widget for embedding in a web page to emulate the kind of displays commonly used on electronic equipment.

The widget will be able to display numbers, letters and various other symbols as well as having customizable colours.

The Project

The project consists of the following files which can be downloaded as a zip, or you can clone/download the Github repository if you prefer.

  • multi_segment_display.js
  • multi_segment_display.htm
  • multi_segment_display_page.js

Source Code Links

ZIP File
GitHub

The display itself lives in multi_segment_display.js and is implemented as a ES6/ES2015 class. Let's look at the code, starting with the constructor.

multi_segment_display.js Part 1: Constructor

class MultiSegmentDisplay
{
    constructor(SVGID)
    {
        this._ColorSchemes =
        {
            LCD: 1,
            Green: 2,
            Orange: 3,
            Blue: 4,
            Sky: 5,
            Red: 6
        };

        this._NumberOfCharacters = 12;
        this._Text = "AaBbCc123456";
        this._BackgroundColor = "#E0E0E0";
        this._LitSegmentColor = "#202020";
        this._UnlitSegmentColor = "#D0D0D0";
        this._ColorScheme = this._ColorSchemes.LCD;
        this._SVGID = SVGID;
        this._PreviousText = "";
        this._Segments = null;
        this._Background = null;
        this._SegmentPatterns = [];

        this._InitSegmentPatterns();

        this._SetHeight();

        this._CreateSegments();

        this._SetSegmentColours(false);
    }

Firstly the _ColorSchemes object is used as an enumeration for the ColorScheme property. There are then a number of variables for the content and format of the display which we will see in use later on. Finally there are several function calls to set up the display which I'll describe in detail in due course.

multi_segment_display.js Part 2: Properties

    //---------------------------------------------------------------
    // PROPERTIES
    //---------------------------------------------------------------

    get ColorSchemes() { return this._ColorSchemes; }

    get BackgroundColor() { return this._BackgroundColor; }
    set BackgroundColor(BackgroundColor)
    {
        this._BackgroundColor = BackgroundColor;
        this._Background.setAttributeNS(null, 'style', "fill:" + this._BackgroundColor + ";");
    }

    get LitSegmentColor() { return this._LitSegmentColor; }
    set LitSegmentColor(LitSegmentColor)
    {
        this._LitSegmentColor = LitSegmentColor;
        this._SetSegmentColours(true);
    }

    get UnlitSegmentColor() { return this._UnlitSegmentColor; }
    set UnlitSegmentColor(UnlitSegmentColor)
    {
        this._UnlitSegmentColor = UnlitSegmentColor;
        this._SetSegmentColours(true);
    }

    get ColorScheme() { return this._ColorScheme; }
    set ColorScheme(ColorScheme)
    {
        this._ColorScheme = ColorScheme;

        switch (this._ColorScheme)
        {
            // LCD
            case 1:
                this._BackgroundColor = "#E0E0E0";
                this._LitSegmentColor = "#202020";
                this._UnlitSegmentColor = "#D0D0D0";
                break;

            // Green
            case 2:
                this._BackgroundColor = "#002000";
                this._LitSegmentColor = "#00FF00";
                this._UnlitSegmentColor = "#003000";
                break;

            // Orange
            case 3:
                this._BackgroundColor = "#000000";
                this._LitSegmentColor = "#FF8000";
                this._UnlitSegmentColor = "#201000";
                break;

            // Blue
            case 4:
                this._BackgroundColor = "#000020";
                this._LitSegmentColor = "#0000FF";
                this._UnlitSegmentColor = "#000030";
                break;

            // Sky
            case 5:
                this._BackgroundColor = "#00FFFF";
                this._LitSegmentColor = "#0000FF";
                this._UnlitSegmentColor = "#00DFDF";
                break;

            // Red
            case 6:
                this._BackgroundColor = "#200000";
                this._LitSegmentColor = "#FF0000";
                this._UnlitSegmentColor = "#300000";
                break;

            default:
                break;
        }

        this._Background.setAttributeNS(null, 'style', "fill:" + this._BackgroundColor + ";");
        this._SetSegmentColours(true);
    }

    get NumberOfCharacters() { return this._NumberOfCharacters; }
    set NumberOfCharacters(NumberOfCharacters)
    {
        let ivalue = parseInt(NumberOfCharacters);

        if (isNaN(ivalue) || ivalue == null || ivalue < 1 || ivalue > 32)
        {
            throw new Error("NumberOfCharacters must be an integer between 1 and 32");
        }
        else
        {
            this._NumberOfCharacters = NumberOfCharacters;
            this._SetHeight();
            this._CreateSegments();
            this._SetSegmentColours(true);
        }
    }

    get Text() { return this._Text; }
    set Text(Text)
    {
        if (Text.length > this._NumberOfCharacters)
        {
            throw new Error("Length of Text is longer than NumberOfCharacters");
        }
        else
        {
            this._Text = Text;

            if (this._Text.length < this._NumberOfCharacters)
            {
                for (let i = 0, s = this._NumberOfCharacters - this._Text.length; i < s; this._Text += " ", i++);
            }

            this._SetSegmentColours(false);
        }
    }

Most of the properties are no more than getters/setters for various backing variables. However, the last two are slightly more complex:

NumberOfCharacters

The setter for this property first validates the new value and then needs to call a few functions to completely reconfigure the display.

Text

Here we need to check the new value isn't too long. If it is OK we pad the string with spaces and then call a function to display it.

multi_segment_display.js Part 3: Methods

    //-------------------------------------------------------------------
    // METHODS
    //-------------------------------------------------------------------

    _SetHeight()
    {
        document.getElementById(this._SVGID).setAttribute("height", (document.getElementById(this._SVGID).getClientRects()[0].width) / (this._NumberOfCharacters * 0.8));
    }

    _CreateSegments()
    {
        document.getElementById(this._SVGID).innerHTML = "";

        // Create Background
        let rect = document.createElementNS("http://www.w3.org/2000/svg", 'rect');

        rect.setAttributeNS(null, 'x', 0);
        rect.setAttributeNS(null, 'y', 0);
        rect.setAttributeNS(null, 'height', document.getElementById(this._SVGID).getClientRects()[0].height);
        rect.setAttributeNS(null, 'width', document.getElementById(this._SVGID).getClientRects()[0].width);
        rect.setAttributeNS(null, 'style', "fill:" + this._BackgroundColor + ";");

        document.getElementById(this._SVGID).appendChild(rect);

        this._Background = rect;

        // Create paths
        this._Segments = [];

        let SVGWidth = document.getElementById(this._SVGID).getClientRects()[0].width;
        let SVGHeight = document.getElementById(this._SVGID).getClientRects()[0].height;
        let CharacterWidth = SVGWidth / this._NumberOfCharacters;
        let Unit = SVGHeight / 30.0;
        let ThreeUnits = Unit * 3;
        let x;
        let y;
        let Character;
        let Column;
        let Row;

        for (Character = 0; Character < this._NumberOfCharacters; Character++)
        {
            y = ThreeUnits;

            this._Segments[Character] = [];

            for (Row = 0; Row <= 8; Row++)
            {
                this._Segments[Character][Row] = [];

                x = (CharacterWidth * Character) + ThreeUnits;

                for (Column = 0; Column <= 6; Column++)
                {
                    this._Segments[Character][Row][Column] = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                    this._Segments[Character][Row][Column].setAttributeNS(null, "cx", x);
                    this._Segments[Character][Row][Column].setAttributeNS(null, "cy", y);
                    this._Segments[Character][Row][Column].setAttributeNS(null, "r", Unit);
                    document.getElementById(this._SVGID).appendChild(this._Segments[Character][Row][Column]);

                    x += ThreeUnits;
                }

                y += ThreeUnits;
            }
        }
    }

    _SetSegmentColours(RedrawAll)
    {
        if (RedrawAll == true)
        {
            this._PreviousText = "";
        }

        let Character;
        let Column;
        let Row;
        let CharCode;

        for (let Character = 0; Character < this._NumberOfCharacters; Character++)
        {
            CharCode = this._Text.charCodeAt(Character);

            if (this._SegmentPatterns[CharCode] === undefined)
            {
                CharCode = 32;
            }

            if (this._Text[Character] != this._PreviousText[Character])
            {
                for (Row = 0; Row <= 8; Row++)
                {
                    for (Column = 0; Column <= 6; Column++)
                    {
                        if (this._SegmentPatterns[CharCode][Row][Column] == 1)
                            this._Segments[Character][Row][Column].setAttributeNS(null, 'style', "fill:" + this._LitSegmentColor + ";");
                        else
                            this._Segments[Character][Row][Column].setAttributeNS(null, 'style', "fill:" + this._UnlitSegmentColor + ";");
                    }
                }
            }
        }

        this._PreviousText = this._Text;
    }

_SetHeight

This function sets the height of the widget to a fixed proportion of the width for any particular number of characters.

_CreateSegments

Each segment is an SVG element, and these are created by this function.

_SetSegmentColours

Letters, numbers and other characters are displayed by setting the appropriate elements to either the lit or unlit segment colours. The RedrawAll argument specifies whether all elements are redrawn. If this is false only changed characters will be redrawn, which is sufficient if the Text property has been changed. However, if a colour has been changed this needs to be true to force the widget to redraw all segments even though the text is the same.

multi_segment_display.js Part 4: _InitSegmentPatterns (partial)

    _InitSegmentPatterns()
    {
        // A
        this._SegmentPatterns[65] = [
        [0,1,1,1,1,1,0],
        [1,0,0,0,0,0,1],
        [1,0,0,0,0,0,1],
        [1,0,0,0,0,0,1],
        [1,1,1,1,1,1,1],
        [1,0,0,0,0,0,1],
        [1,0,0,0,0,0,1],
        [1,0,0,0,0,0,1],
        [1,0,0,0,0,0,1]];

This is a tediously long function which creates a two-dimensional array for each displayable character, showing which segments are "on" and which are "off". I have only shown one here, spread across multiple lines to show that the actual character 'A' can be discerned in the sea of 0s and 1s.

If the _SetSegmentColours function cannot find a character from the Text property in the _SegmentPatterns it will print a space instead. However, you can add other characters to _SegmentPatterns in this function if you feel the urge.

Trying it Out

The code for the widget is now finished but I have included in the zip and Github repository an HTML file and a JavaScript file for trying it out. I won't show the code for these here but if you open the page it looks like this.

As you can see there are a few controls on the left for setting properties, and an instance of the Widget on the right. The multi_segment_display_page.js file just contains code for handling the various onchange events. You might like to spend a few minutes playing with the UI.