├── LICENSE ├── .gitignore ├── img └── angular-slopegraph.png ├── README.md └── js └── angular-slopegraph.js /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /img/angular-slopegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bentorfs/angular-slopegraph/master/img/angular-slopegraph.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularJS slopegraphs 2 | 3 | An AngularJS directive to easily create slopegraphs (for more info on slopegraphs: http://charliepark.org/slopegraphs/) 4 | 5 | The rendering code is based on an implementation by Kevin Marks, found here: https://github.com/kevinmarks/slopegraph. It depends on the Raphael SVG library 6 | 7 | ## Example rendering 8 | ![Alt text](/img/angular-slopegraph.png) 9 | 10 | ## Example usage 11 | 12 | Import the necessary JS. angular-slopegraph.js depends on Raphael (http://raphaeljs.com/) 13 | ``` 14 | 15 | 16 | ``` 17 | 18 | Create a slopegraph as follows 19 | ``` 20 | 21 | ``` 22 | slopeData refers to a scope variable that contains the data. The data must be a 2-dimensional array, where each element is a list of 4 elements: 23 | - The left label 24 | - The value behind the left label 25 | - The right label 26 | - The value behind the right label 27 | 28 | Higher values will be displayed lower on the graph. For example, the example rendering above is backed by this data: 29 | 30 | ```javascript 31 | angular.module('myApp', ['ngSlopeGraph']). 32 | controller('MyController', ['$scope', function ($scope) { 33 | 34 | $scope.slopeData = [ 35 | ['Sweden', 46.9, 'Sweden', 57.4], 36 | ['Netherlands', 44.0, 'Netherlands', 55.8], 37 | ['Britain', 40.7, 'Britain', 39.0], 38 | ['Belgium', 35.2, 'Belgium', 43.2] 39 | ]; 40 | 41 | }]); 42 | ``` 43 | -------------------------------------------------------------------------------- /js/angular-slopegraph.js: -------------------------------------------------------------------------------- 1 | angular.module('ngSlopeGraph', []) 2 | .directive('slopegraph', function () { 3 | 4 | function drawSlopeGraph(container, data, leftLabel, rightLabel, width, height) { 5 | 6 | container.html(""); // Clear current slopegraph 7 | var seenBoundingBoxes = []; 8 | 9 | var r = Raphael(container[0], width, height), 10 | leftLabelCss = { 'text-anchor': 'end', fill: "#000", 'font-size': "12px"}, 11 | rightLabelCss = { 'text-anchor': 'start', fill: "#000", 'font-size': "12px"}, 12 | leftHeaderCss = { 'text-anchor': 'end', fill: "#000", 'font-size': "12px", 'font-weight': 'bold'}, 13 | rightHeaderCss = { 'text-anchor': 'start', fill: "#000", 'font-size': "12px", 'font-weight': 'bold'}, 14 | labelWidth = 120, 15 | headerHeight = 50, 16 | footerHeight = 100, 17 | contentHeight = height - headerHeight - footerHeight, 18 | max = getMaxValue(data), 19 | min = getMinValue(data), 20 | scale = (contentHeight / (max - min)); 21 | r.text(labelWidth, 10, leftLabel).attr(leftHeaderCss); 22 | r.text(width - labelWidth, 10, rightLabel).attr(rightHeaderCss); 23 | for (var i = 0; i < data.length; i++) { 24 | var leftKey = data[i] [0], 25 | leftVal = data[i] [1], 26 | rightKey = data[i] [2], 27 | rightVal = data[i] [3]; 28 | var startY = headerHeight + scale * (leftVal - min); 29 | var endY = headerHeight + scale * (rightVal - min); 30 | while (collides({x: 0, y: startY, w: labelWidth, h: 12}, seenBoundingBoxes)) startY++; 31 | while (collides({x: width - labelWidth, y: endY, w: labelWidth, h: 12}, seenBoundingBoxes)) endY++; 32 | if (leftKey) { 33 | r.text(labelWidth, startY, leftKey).attr(leftLabelCss); 34 | } 35 | if (rightKey) { 36 | r.text(width - labelWidth, endY, rightKey).attr(rightLabelCss); 37 | } 38 | if (leftVal && rightVal) { 39 | var line = ["M", labelWidth + 5, " ", startY, "L", width - labelWidth - 5, " ", endY].join(""); 40 | } 41 | var p = r.path(line); 42 | } 43 | }; 44 | 45 | function collides(box, seenBoundingBoxes) { 46 | for (var i = 0, il = seenBoundingBoxes.length; i < il; i++) { 47 | var seen = seenBoundingBoxes[i]; 48 | if (!(box.x > seen.x + seen.w || box.y > seen.y + seen.h || box.x + box.w < seen.x || box.y + box.h < seen.y)) return true; 49 | } 50 | seenBoundingBoxes.push(box); 51 | return false; 52 | } 53 | 54 | function getMinValue(data) { 55 | var result = Number.MAX_VALUE; 56 | for (var i = 0; i < data.length; i++) { 57 | if (data[i][1] && data[i][1] < result) { 58 | result = data[i][1]; 59 | } 60 | if (data[i][3] && data[i][3] < result) { 61 | result = data[i][3]; 62 | } 63 | } 64 | return result; 65 | } 66 | 67 | 68 | function getMaxValue(data) { 69 | var result = Number.MIN_VALUE; 70 | for (var i = 0; i < data.length; i++) { 71 | if (data[i][1] && data[i][1] > result) { 72 | result = data[i][1]; 73 | } 74 | if (data[i][3] && data[i][3] > result) { 75 | result = data[i][3]; 76 | } 77 | } 78 | return result; 79 | } 80 | 81 | return { 82 | restrict: 'E', 83 | replace: true, 84 | link: function compile(scope, element, attrs, controller) { 85 | var scopeVarWithData = attrs['slopedata']; 86 | scope.$watch(scopeVarWithData, function () { 87 | var dataSet = scope.$eval(attrs['slopedata']); 88 | if (dataSet) { 89 | var leftLabel = attrs['leftlabel']; 90 | var rightLabel = attrs['rightlabel']; 91 | var width = attrs['width']; 92 | var height = attrs['height']; 93 | drawSlopeGraph(element, dataSet, leftLabel, rightLabel, width, height); 94 | } 95 | } 96 | ); 97 | } 98 | } 99 | ; 100 | }) 101 | ; 102 | --------------------------------------------------------------------------------