An experiment.
A Pen by Jase Smith on CodePen.
An experiment.
A Pen by Jase Smith on CodePen.
| <div ng-app="app" ng-controller="AppController" ng-class="{'x-ray': xRay, 'horizontal': horiz}"> | |
| <h1>Proof of Concept</h1> | |
| <p>Using only CSS & HTML to draw the diagram, aside from using Angular to build the HTML and hook on the magic calculations. <b>No canvas, no SVG.</b></p> | |
| <p>Tested in Chrome, Safari, Firefox. And it seems to also work in IE 10+</p> | |
| <p><label><input type="checkbox" ng-model="xRay" /> X-Ray Goggles</label></p> | |
| <spork config="config" model="model"></spork> | |
| </div> |
| // utility methods | |
| var getNumbers = function(target) { | |
| var numbers = {}; | |
| if (target) { | |
| numbers = { | |
| t: target.offsetTop, | |
| r: target.offsetLeft + target.offsetWidth, | |
| b: target.offsetTop + target.offsetHeight, | |
| l: target.offsetLeft, | |
| w: target.offsetWidth, | |
| h: target.offsetHeight | |
| }; | |
| // find x|y center | |
| numbers.cx = (numbers.l + (numbers.w / 2)); | |
| numbers.cy = (numbers.t + (numbers.h / 2)); | |
| } | |
| return numbers; | |
| }; | |
| var getMetrics = function(el, parent, rtl, factor) { | |
| rtl = rtl || false; | |
| factor = factor || false; | |
| var en = getNumbers(el); | |
| var pn = getNumbers(parent); | |
| var p1 = { | |
| x: en.cx, | |
| y: en.cy | |
| }; | |
| var p2 = { | |
| x: pn.cx, | |
| y: pn.cy | |
| }; | |
| // angle in radians | |
| var rads = Math.atan2(p2.y - p1.y, p2.x - p1.x); | |
| // angle in degrees | |
| var degs = rads * 180 / Math.PI; | |
| if (rtl) { | |
| // this because if the line orientation is right to left | |
| degs += 180; | |
| } | |
| // length of line between two points | |
| // last operation as this alters the number set | |
| var lens = Math.sqrt(((p1.x -= p2.x) * p1.x) + ((p1.y -= p2.y) * p1.y)); | |
| if (factor) { | |
| // for if we want the line length to extend beyond the node | |
| lens = lens * factor; | |
| } | |
| // returns object of calculations | |
| return { | |
| lens: lens, | |
| rads: rads, | |
| degs: degs | |
| }; | |
| }; | |
| angular.module('app', []) | |
| .controller('AppController', ['$scope', function($scope) { | |
| // nodes data model | |
| $scope.model = [{ | |
| id: 0, | |
| children: [{ | |
| id: 1, | |
| children: [{ | |
| id: 11, | |
| children: [{ | |
| id: 111 | |
| }, { | |
| id: 112 | |
| }] | |
| }, { | |
| id: 12 | |
| }] | |
| }, | |
| { | |
| id: 2, | |
| children: [{ | |
| id: 21 | |
| }, { | |
| id: 22 | |
| }, { | |
| id: 23, | |
| children: [{ | |
| id: 231 | |
| }, { | |
| id: 232 | |
| }, { | |
| id: 233 | |
| }] | |
| }, { | |
| id: 24 | |
| }] | |
| }, | |
| ] | |
| }]; | |
| }]) | |
| .directive('spork', [function spork() { | |
| return { | |
| restrict: 'E', | |
| replace: true, | |
| scope: { | |
| config: '=?', | |
| model: '=' | |
| }, | |
| controller: ['$scope', function controller($scope) { | |
| var me = this; | |
| $scope.connector = function connector(el, parent) { | |
| el = angular.element('#node-' + el)[0]; | |
| parent = angular.element('#node-' + parent)[0]; | |
| var metrics = getMetrics(el, parent, true); | |
| return { | |
| width: Math.round(metrics.lens) + 'px', | |
| transform: 'translateY(-50%) rotate(' + Math.round(metrics.degs) + 'deg)' | |
| }; | |
| }; | |
| }], | |
| template: '' + | |
| '<div class="spork">' + | |
| '<ul>' + | |
| '<li ng-repeat="a in model">' + | |
| '<div id="node-{{a.id}}">' + | |
| '<div class="node"><span class="label">{{a.id}}</span></div>' + | |
| '</div>' + | |
| '<ul ng-if="a.children.length">' + | |
| '<li ng-repeat="b in a.children">' + | |
| '<div id="node-{{b.id}}">' + | |
| '<div class="node"><span class="label">{{b.id}}</span></div>' + | |
| '<div class="line" ng-style="connector(b.id, a.id)"></div>' + | |
| '</div>' + | |
| '<ul ng-if="b.children.length">' + | |
| '<li ng-repeat="c in b.children">' + | |
| '<div id="node-{{c.id}}">' + | |
| '<div class="node"><span class="label">{{c.id}}</span></div>' + | |
| '<div class="line" ng-style="connector(c.id, b.id)"></div>' + | |
| '</div>' + | |
| '<ul ng-if="c.children.length">' + | |
| '<li ng-repeat="d in c.children">' + | |
| '<div id="node-{{d.id}}">' + | |
| '<div class="node"><span class="label">{{d.id}}</span></div>' + | |
| '<div class="line" ng-style="connector(d.id, c.id)"></div>' + | |
| '</div>' + | |
| '</li>' + | |
| '</ul>' + | |
| '</li>' + | |
| '</ul>' + | |
| '</li>' + | |
| '</ul>' + | |
| '</li>' + | |
| '</ul>' + | |
| '</div>' | |
| }; | |
| }]); |
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script> |
| // | |
| .spork { | |
| position: relative; | |
| ul { | |
| margin: 0; | |
| padding: 0; | |
| list-style: none; | |
| flex: 1 0 auto; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| li { | |
| flex: 0 0 auto; | |
| margin: 0 .5em; | |
| display: flex; | |
| flex-direction: row; | |
| align-items: center; | |
| transition: .2s; | |
| > div { | |
| margin: .5em; | |
| position: relative; | |
| flex: 0 0 auto; | |
| } | |
| } | |
| .node { | |
| position: relative; | |
| z-index: 2; | |
| padding: 2em; | |
| background: skyblue; | |
| /* box-shadow: 0 0 0 .2em currentColor inset; */ | |
| border: 2px solid; | |
| border-radius: 100%; | |
| transition: .2s; | |
| cursor: default; | |
| } | |
| .label { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| pointer-events: none; | |
| } | |
| .line { | |
| position: absolute; | |
| z-index: 1; | |
| padding: .2em; | |
| background: currentColor; | |
| top: 50%; | |
| right: 50%; | |
| transform-origin: right center; | |
| transition: .2s; | |
| pointer-events: none; | |
| } | |
| } | |
| // playground | |
| .spork { | |
| ul { | |
| margin: .5em; | |
| } | |
| li { | |
| margin: .5em; | |
| border-radius: .3em; | |
| li { | |
| font-size: .85em; | |
| } | |
| &:hover { | |
| > div { | |
| .node, | |
| .node+.line { | |
| background: mix(#fff, yellowGreen, 5%); | |
| } | |
| .node { | |
| z-index: 3; | |
| transform: scale(1.2); | |
| border-color: #fff; | |
| &:hover { | |
| transform: scale(2) !important; | |
| box-shadow: 0 0 0 .3em fade-out(yellowGreen, .7); | |
| } | |
| + .line { | |
| padding: .4em; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // electrical wiring | |
| .x-ray .spork li { | |
| background: rgba(#000,.1); | |
| .node { | |
| opacity: .7; | |
| } | |
| } | |
| // basics | |
| body { | |
| font-family: sans-serif; | |
| font-size: 15px; | |
| } | |
| .spork *, .spork *::after, .spork *::before { | |
| box-sizing: border-box; | |
| } |