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
Step 2 – Define the data I want to use for the sample
Step 3 – Create some Auto generated forms to manage this data
Step 4 – Test what we have so far in a local MDriven Turnkey Core installation
Here is a video introducing the MDriven Turnkey Core functionality:
Step 5 – Create a form that will hold the SVG component:
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:
Step 7 – Create the parts of the component – a html file, an optional javascript – in this case via typescript, and an optional css file:
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:
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:
The last bit of the script is the mouse events:
#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: