SVG and MDriven Turnkey Components

SVG – Scalable Vector Graphics – supported in all modern browsers.

As always when I do something I want to have a small sample that runs all the way – that gets data from somewhere – show this data somehow – allow a user to update this data – and finally send that data back to the server.

And as always my tool of choice for this round-trip is MDriven Designer – now with my new favorite MDriven Turnkey Core – as in .net Core Framework.

In this article I will show how you can create data bound SVG graphics and allow for interaction with those graphic objects. I will also show how this is done with MDriven Turnkey Components.

Step 1 – start MDriven Designer – I use this to define the data and views

image

Step 2 – Define the data I want to use for the sample

image

Step 3 – Create some Auto generated forms to manage this data

image

Step 4 – Test what we have so far in a local MDriven Turnkey Core installation

image

Here is a video introducing the MDriven Turnkey Core functionality:

Step 5 – Create a form that will hold the SVG component:

image

Using the TagValue-editor I set the Angular_Ext_Component tag on a ViewModel Column that has the Content override flag set. This ViewModel column will contain a list of Box objects with x and y properties.

Step 6 – Create the AssetsTK folder for the model where the component definition will go:

image

Step 7 – Create the parts of the component – a html file, an optional javascript – in this case via typescript, and an optional css file:

image

Now I will explain the content of these files that build up the component. We will start with the html

<div  >
  <svg id='theSVG' height='600px' width='100%' thesvg  >
    <g class='draggable' ng-repeat="row in ViewModelRoot.Curr_MainDrawingView().ThingsInDrawing" 
        ng-attr-transform="translate({{(row.x)}} {{(row.y)}})" ng-attr-vmclassid="{{row.VMClassId.asString}}">
      <rect  class="svgRectStyle" x="-75" y="-75" width="150" height="150" >    
      </rect>
      <text x="0" y="70"  dominant-baseline="middle" text-anchor="middle">{{row.Text}} rowx{{row.x}} rowy{{row.y}}</text>  
      <text x="0" y="80"  dominant-baseline="middle" text-anchor="middle">{{row.Text}}</text>  
      <circle r="40" stroke="green" stroke-width="4" fill="pink"  />
      <circle  r="20" stroke="green" stroke-width="4" fill="yellow"  />    
    </g>
  </svg>
</div>

The html is basically an outer div holding the svg html element. Then we have a g-element which is a svg group of other things – in this case a rect, 2 texts and 2 circles. In the g-element we have the angularjs command of ng-repeat that will loop over the data that our viewmodel defines. We also set a svg transform on the g-element:

image

Svg can be styled by css – this is how our css looks like:

.draggable {
    cursor: move;
  }

.svgRectStyle{
    fill:blue;
    stroke:pink;
    stroke-width:5;
    fill-opacity:0.1;
    stroke-opacity:0.9;
}

If we want to interact with the svg objects and possibly move them around – then we need javascript – I choose to use typescript and this is what my script looks like:

/// <reference path="../../Scripts/typings/jquery/jquery.d.ts" />
/// <reference path="../../Scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../js/MDrivenAngularApp.ts" />


namespace svgComponentNamespace {

var  _TheSVG =null;
var _ViewData=null;

function makeDraggable(svg:HTMLBaseElement) {

  svg.addEventListener('mousedown', startDrag);
  svg.addEventListener('mousemove', drag);
  svg.addEventListener('mouseup', endDrag);
  svg.addEventListener('mouseleave', endDrag);

  var selectedElement = null;
  var selectedobject=null;
  var offset;

  function getMousePosition(evt) {
    var CTM = svg.getScreenCTM();
    return {
      x: (evt.clientX - CTM.e) / CTM.a,
      y: (evt.clientY - CTM.f) / CTM.d
    };
  }

  function startDrag(evt) {
    var target=evt.target;
    if (target.tagName!="g")
    {
     target=evt.target.parentElement;
    }
    if (target.classList.contains('draggable')) {
      selectedElement =target;
      var vmclassid=selectedElement.attributes['vmclassid'].value;
      selectedobject=_ViewData.VMClassIdMap[vmclassid];
      if (selectedobject) {
        offset = getMousePosition(evt);
        offset.x -= selectedobject.x;
        offset.y -= selectedobject.y;
        selectedobject.SelectRow();
      }
    }
  }
  function drag(evt) {
    if (selectedobject) {
      evt.preventDefault();
      var coord = getMousePosition(evt);
      selectedobject.x=coord.x - offset.x;
      selectedobject.y=coord.y - offset.y;
      _ViewData.StreamingAppClient.CancelTimerForSending();
    }
  }
  function endDrag(evt) {
    if (selectedobject) {
      _ViewData.StreamingAppClient.ScheduleTimerForSending(1);
    }
    selectedElement = null;
    selectedobject = null;
    evt.preventDefault();
  }
}

  

   function InstallTheDirectiveFor_svgComponent(streamingAppController) {
    streamingAppController.directive('thesvg', ['$document', function ($document) {
      return {
        link: function (scope, element: HTMLDivElement[], attr) {
          console.trace("svgComponent component Loaded");
          _TheSVG=element[0];
          _ViewData=scope.ViewData;
           makeDraggable(_TheSVG);
        }
      };
    }]);
  }
  InstallTheDirectiveFor_svgComponent(angular.module(MDrivenAngularAppModule));
}

Breaking down the parts of the script:

image

The last bit of the script is the mouse events:

image

#1 is just hooking up the events of the svg element.
#2 when we see a MouseDown we check if it is a g type – the only one we want to move – we may get a child of g and try to move up to get to g. We check that the class is “draggable” – could be skipped. We then look up the identity of the corresponding javascript object and use this identity to look up the javascript object that matches the definition from our viewmodel. If found we know that it will have properties as defined in the viewmodel columns – like x and y. We also instruct turnkey to SelectRow – this will enable actions that act on vCurrent variable to use the object we clicked last.
#3 in the drag event we get information while the mouse is moving – and we update the object’s x and y property to correspond to the movement
#4 We the mouse button is released we null out the selected object to signal that the move operation is over

We now have an information system that creates data – stores and retrieves from server – allow you to create custom vector graphics for each object – move that graphic around and thus updating properties of the object – detect change state of the object – allowing the user to cancel, undo or save those changes to the server – and signaling to other clients that may look at the same data in order to reflect the changes.

Not bad – and I think svg is very usable for modern browser applications!

Download a zip with this model

Once you are up and running you should be able to do as in this short video:

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *