Shape Editor Sample Overview

The Shape Editor sample application demonstrates how you can create and edit various shapes using SVG (Scalable Vector Graphics).

The following figure illustrates the main screens of the Shape Editor.

Figure: Shape Editor screens

Shape Editor screens

The application opens with a blank SVG canvas with the action buttons at the bottom:

  • Click Shape to select a specific shape and the size of the shape.
  • Click Color to select the color of the shape from a color picker.
  • Click Clear to remove all the shapes on the SVG canvas.

Source Files

You can create and view the sample application project including the source files in the IDE.

File name Description
config.xml This file contains the application information for the platform to install and launch the application.
css/style.css This file contains the CSS styling for the application UI.
index.html This is a starting file from which the application starts loading. It contains the layout of the application screens.
js/app.js This file contains the code for handling the main functionality of the application.

Implementation

Use graphic elements to create shapes in the SVG board. Attributes, including size and color, of the shape can be defined in the tag.

<--! index.html -->

<svg class="shape-svg" viewBox="0 0 50 50">
    <rect class="shape-element" id="select-rect" width="50" height="50" />
</svg>
<svg class="shape-svg" viewBox="0 0 50 50">
    <rect class="shape-element" id="select-rectrnd" rx="17.5" ry="17.5" width="50" height="50" />
</svg>
<svg class="shape-svg" viewBox="0 0 50 50">
    <ellipse class="shape-element" id="select-ellipse" cx="25" cy="25" rx="25" ry="25" />
</svg>
<svg class="shape-svg" viewBox="0 0 50 50">
    <polygon class="shape-element" id="select-triangle" points="25,0 0,50 50,50" />
</svg>
<svg class="shape-svg" viewBox="0 0 50 50">
    <polygon class="shape-element" id="select-star" points="25,0 10,50 50,19 0,19 40,50" />
</svg>

Since SVG elements are graphics defined in XML format, the innerHtml property is not supported with the SVG elements.
A workaround method is required for inserting new SVG elements into the SVG board dynamically using JavaScript.

/* js/app.js */

function insertSvgShape(boardId, elemId, shapeId, shapeSize, translateX, translateY) {
    var tempDiv = document.createElement("div"), // Create a dummy div element
        svgFragment = "",
        shapeTag = "",
        colorTag = "",
        tempTag = "";

    // Construct the tag for the new shape element
    switch (shapeId) {
        case "select-rect":
        shapeTag = SVG_TAG_RECT;
        break;
        case "select-rectrnd":
        shapeTag = SVG_TAG_RECTRND;
        break;
        case "select-ellipse":
        shapeTag = SVG_TAG_ELLIPSE;
        break;
        case "select-triangle":
        shapeTag = SVG_TAG_TRIANGLE;
        break;
        case "select-star":
        shapeTag = SVG_TAG_STAR;
        break;
    }

    // Add the color information to the tag
    colorTag = "style='fill:" + customColor + ";";
    shapeTag = shapeTag.replace("style='", colorTag);

    // Add the size and location information as well as the id information to the tag
    tempTag = "transform='matrix(" + shapeSize + " 0 0 " + shapeSize + " " + translateX + " " + translateY + ")' id='" + elemId + "'/>";
    shapeTag = shapeTag.replace(" />", tempTag);

    // Wrap the SVG string as an SVG object
    svgFragment = "<svg>" + shapeTag + "</svg>";

    // Add the SVG object to the temporary div element
    tempDiv.innerHTML = svgFragment;

    // Use Array.prototype.slice() method to copy items of tempDiv to the actual SVG board
    Array.prototype.slice.call(tempDiv.childNodes[0].childNodes).forEach(function(el) {
        document.querySelector("#" + boardId).appendChild(el);
        if (boardId === "svg-board") {
        el.addEventListener("touchmove", touchMoveHandler, false);
        }
    });

    // Update the index so that it can be used for the next shape to be added
    customIndex++;
} 

Handle touch events:

  • The touchStartHandler() method is called when the touchstart event is triggered. It uses the changedTouches array event property to access the data of the current touch object. The current touch data is saved to the drawPath object, which is an associative array indexed with the identifier parameter of the current touch object.

    /* js/app.js */
    
    function touchStartHandler(event) {
        var touch = event.changedTouches[0];
    
        // Store information about the first touch point in the drawPath array
        drawPath[touch.identifier] = touch;
    }
  • The touchMoveHandler() method is called when the touchmove event is triggered on the SVG elements. It moves the element around the board following the path of the touch points.

    /* js/app.js */
    
    function touchMoveHandler(event) {
        var i = 0,
            j = 0,
            x = 0,
            y = 0,
            touches = event.changedTouches,
            currentDrawPath = null,
            originalSize = 0,
            originalTransform = "",
            newTransform = "",
            transformArray = null;
    
        // While moving a shape, the color of the shape becomes translucent
        event.target.style.opacity = 0.5;
    
        // Move shape around following the path of the touch points
        for (i = 0; i < touches.length; i += 1) {
            currentDrawPath = drawPath[touches[i].identifier];
    
            if (currentDrawPath !== undefined) {
                // Get a string about the original transformation data of the shape object
                originalTransform = event.target.getAttribute("transform");
    
                // Split the string and insert each data into a temporary array so that each item can be accessed
                transformArray = originalTransform.split(" ");
    
                // Get size of the shape
                originalSize = Number(transformArray[3]);
    
                // Calculate x and y coordinates of the current touch point
                x = currentDrawPath.pageX - svgContainer.offsetLeft - (originalSize * 25);
                y = currentDrawPath.pageY - svgContainer.offsetTop - (originalSize * 25);
    
                // Insert the new coordinate data to the transformArray
                transformArray[4] = x;
                transformArray[5] = y + ")";
    
                // Create a string using the new data
                for (j = 0; j < transformArray.length; j++) {
                    newTransform += transformArray[j] + " ";
                }
    
                // Change the transformation data of the shape object with the new string
                event.target.setAttribute("transform", newTransform);
    
                drawPath[touches[i].identifier] = touches[i];
            }
        }
    
        // Indicate that a shape is being moved (not inserting a new shape)
        isMoving = 1;
    
        event.preventDefault();
    }
  • The touchEndHandler() method is called when the touchend event is triggered, and it adds a new shape into the SVG board. The pageX and pageY properties of the touch object represent the coordinates of the touch point. After a new shape is inserted, the method resets the drawPath object to remove the information about the touch that no longer exists.

    /* js/app.js */
    
    function touchEndHandler(event) {
        var i = 0,
            x = 0,
            y = 0,
            touch = event.changedTouches[0],
            originalSize = 0,
            originalTransform = "",
            newTransform = "",
            transformArray = null;
    
        // If not moving a shape, insert a new shape to the SVG board
        if (!isMoving) {
            // Get x and y coordinates of the final touch point
            x = touch.pageX - svgContainer.offsetLeft - (customSize * 25);
            y = touch.pageY - svgContainer.offsetTop - (customSize * 25);
    
            insertSvgShape("svg-board", "shape" + customIndex, customShape, customSize, x, y);
    
            delete drawPath[touch.identifier];
        }
        // If moving an existing shape, locate the shape to a new point at the end
        else {
            // Get a string about the original transformation data of the shape object
            originalTransform = event.target.getAttribute("transform");
    
            // Split the string and insert each data into a temporary array so that each item can be accessed
            transformArray = originalTransform.split(" ");
    
            // Get size of the shape
            originalSize = Number(transformArray[3]);
    
            // Calculate x and y coordinates of the final touch point
            x = touch.pageX - svgContainer.offsetLeft - (originalSize * 25);
            y = touch.pageY - svgContainer.offsetTop - (originalSize * 25);
    
            // At the end of moving a shape, restore the color opacity to the original state
            event.target.style.opacity = 1;
    
            // Insert the new coordinate data to the transformArray
            transformArray[4] = x;
            transformArray[5] = y + ")";
    
            // Create a string using the new data
            for (i = 0; i < transformArray.length; i++) {
                newTransform += transformArray[i] + " ";
            }
    
            // Change the transformation data of the shape object with the new string
            event.target.setAttribute("transform", newTransform);
    
            delete drawPath[touch.identifier];
    
            // Set the flag back to the initial state
            isMoving = 0;
        }
    }