<!DOCTYPE html>
<html>
<head>
<link data-require="normalize@6.0.0" data-semver="6.0.0" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/6.0.0/normalize.min.css" />
<link href="https://unpkg.com/basscss@7.1.1/css/basscss.min.css" rel="stylesheet" />
<link data-require="bootstrap@4.0.0-alpha.6" data-semver="4.0.0-alpha.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" />
<link data-require="font-awesome@4.7.0" data-semver="4.7.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body ng-app="app">
<app-main></app-main>
<script data-require="jquery@3.1.1" data-semver="3.1.1" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script data-require="tether@1.4.0" data-semver="1.4.0" src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
<script data-require="bootstrap@4.0.0-alpha.6" data-semver="4.0.0-alpha.6" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
<script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
<script data-require="angular.js@1.6.2" data-semver="1.6.2" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.js"></script>
<script src="app.js"></script>
<script src="d3.service.js"></script>
<script src="gauge.component.js"></script>
<script src="progress-circle.component.js"></script>
</body>
</html>
// Code goes here
angular.module('app',[
'app.d3',
'app.gauge',
'app.progressCircle'
]);
angular.module('app').component('appMain', {
templateUrl: 'app-main.html',
controller: MainCtrl,
controllerAs: 'main'
});
function MainCtrl($element) {
var main = this;
main.data = [
{id: 1, total: 10},
{id: 2, total: 50},
{id: 3, total: 20},
{id: 4, total: 40},
{id: 5, total: 30},
{id: 6, total: 100}
];
// shared
// access through child.parent.getBarColor(idx)
main.getBarColor = function(idx){
return idx % 2 === 0 ? 'red': 'blue';
}
}
/* Styles go here */
/* use padding hack for IE */
.svg-container {
width: 100%;
height: 0;
padding-top: 60%; /* choose percent of height/width */
position: relative;
}
svg {
background: #dedede;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: auto;
}
div.svg-container.gauge {
}
div.svg-container.progress-circle {
}
<div>
<header class="bg-green text-white text-center">
<h3 class="p2">
<i class="fa fa-circle-o-notch"></i>
Angular SVG Gauge
</h3>
</header>
<section class="container">
<h3>Gauge Widget</h3>
<!-- Gauge component -->
<gauge center-x="300"
center-y="300"
radius="200"
max-value="180"
gradient-interval="10"
current-value="45"
gradients-offset="10">
</gauge>
</section>
<section class="container">
<h3>Progress Circle</h3>
<!-- progress-circle component -->
<progress-circle></progress-circle>
</section>
</div>
<h5>Gauge:</h5>
<p>
<label for="toggle">toggle</label>
<input type="range"
name="range" id="toggle"
ng-value="{{gauge.specs.currentValue}}"
ng-model="gauge.specs.currentValue"
min="0" max="180"/>
<i>{{gauge.specs.currentValue}}</i>
</p>
<p>
<label for="textToggle">Enter Value:</label>
<input type="number" ng-model="gauge.specs.currentValue"
placeholder="" />
</p>
<div class="svg-container gauge">
<!-- gauge -->
<svg class="svg-scalable"
viewBox="0 0 600 400"
preserveAspectRation="xMidYMid meet">
<!-- viewBox="0 0 100 100" -->
<g>
<!-- <path d="M [starting x] [starting y] A [radius] [radius] 0 0 1 [ending x] [ending y]" stroke-width="[arc thickness]" stroke="[arc color]" fill="none" /> -->
<!-- background -->
<path id="gaugeBackground" ng-attr-d="{{ gauge.background }}" stroke-width="10" stroke="black" fill="none"/>
<!-- gauge value -->
<path ng-attr-d="{{ gauge.value }}" stroke-width="10" stroke="#2a9fbc" fill="none"/>
<!-- invisible arc for textPath to follow, slightly larger -->
<path id="gradients" ng-attr-d="{{ gauge.gradients }}" stroke-width="0" fill="none" />
<!--<text dx="[relative x offset]" dy="[relative y offset]" text-anchor="start|middle|end">-->
<!-- <textPath xlink:href="[path element id]" startOffset="[percentage offset]%">-->
<!-- text to display-->
<!-- </textPath>-->
<!--</text>-->
<!-- gradient ticks -->
<text ng-repeat="gradient in gauge.specs.gradients" dx="0" dy="0" text-anchor="middle" style="font: bold large arial">
<textPath xlink:href="#gradients" startOffset="{{ gradient.offset }}%">
{{ gradient.value }}
</textPath>
</text>
<!-- Fix for last tick-->
<text dx="{{ gauge.specs.maxValueCoordinates.x }}" dy="{{ gauge.specs.maxValueCoordinates.y }}" text-anchor="middle" style="font: bold large arial" transform="rotate(90, {{ gauge.specs.maxValueCoordinates.x}}, {{ gauge.specs.maxValueCoordinates.y }} )">
{{ gauge.specs.maxValue }}
</text>
<text dx="50%" dy="50%" text-anchor="middle"
alignment-baseline="hanging" style="font-size: 7rem">
{{ gauge.specs.currentValue }}
</text>
</g>
</svg>
</div>
angular.module('app.gauge', []);
angular.module('app.gauge')
.component('gauge', {
require: {
parent: '^appMain'
},
bindings: {
centerX: '=',
centerY: '=',
radius: '<',
maxValue: '<',
gradientInterval: '<',
currentValue: '<',
gradientsOffset: '<'
},
controller: GaugeCtrl,
controllerAs: 'gauge',
templateUrl: 'gauge.html',
bindToController: true
});
function GaugeCtrl(d3, $scope) {
var gauge = this;
// set defaults
gauge.specs = {
centerX: 0, // pass in 300
centerY: 0, // pass in 300
radius: 0, // pass in 200
maxValue: 0, // pass in 180
gradientInterval: 0,
currentValue: 0, // 45 passed in
gradients: [],
gradientsOffset: 0, // 10
maxValueCoordinates: null
};
// pass in values from component passed-in values
function initPassedInValues() {
// grab all props from controller
var keys = Object.keys(gauge);
// if ctrl key is in gauge.specs object, copy over to specs
keys.forEach(function(key,idx){
if (gauge.specs.hasOwnProperty(key)) {
gauge.specs[key] = gauge[key];
}
});
}
// passedin padding
gauge.$onInit = function() {
initPassedInValues(); // process passed-in values from component
initGauge();
initGradients();
}
gauge.$postLink = function() {
// d3 logic
}
gauge.$onChanges = function() {
}
// function defs
var getCoordinatesForAngle = function(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = ((angleInDegrees - 180.0) * Math.PI / 180.0);
return {
x: parseInt(centerX + (radius * Math.cos(angleInRadians))),
y: parseInt(centerY + (radius * Math.sin(angleInRadians)))
};
};
// calc background and value arc
// radius as param - diff for circle vs. text path
var getArcPathForAngle = function(startingAngle, endingAngle, radius) {
var startingPt = getCoordinatesForAngle(
gauge.specs.centerX,
gauge.specs.centerY,
radius,
startingAngle);
var endingPt = getCoordinatesForAngle(
gauge.specs.centerX,
gauge.specs.centerY,
radius,
endingAngle);
return ["M", startingPt.x, startingPt.y, "A", radius, radius, 0, 0, 1, endingPt.x, endingPt.y].join(' ');
};
// textPath ticks
function initGradients() {
// use < instead of <= so doesn't show last value, taken care of with fixLastGradientTextValue fn
for (var value = 0, offset = 0; value < gauge.specs.maxValue; value += gauge.specs.gradientInterval, offset += 100/18) {
gauge.specs.gradients.push({value: value, offset: offset});
}
}
function initGauge() {
// draw background
gauge.background = getArcPathForAngle(0, gauge.specs.maxValue, gauge.specs.radius);
// draw gauge value
gauge.value = getArcPathForAngle(0, gauge.specs.currentValue, gauge.specs.radius);
// draw gradient tick values
gauge.gradients = getArcPathForAngle(0, gauge.specs.maxValue, gauge.specs.radius + gauge.specs.gradientsOffset);
// fix last text value and rotate
gauge.specs.maxValueCoordinates = getCoordinatesForAngle(
gauge.specs.centerX,
gauge.specs.centerY,
gauge.specs.radius + gauge.specs.gradientsOffset,
gauge.specs.maxValue);
}
// called by ng-changes on range input element
// gauge.recalcValue = function() {
// gauge.value = getArcPathForAngle(0, gauge.specs.currentValue, gauge.specs.radius);
// };
// additional watcher for currentValue
$scope.$watch('gauge.specs.currentValue', function(oldValue, newValue) {
initGauge();
}, true);
}
angular.module('app.progressCircle', []);
angular.module('app.progressCircle')
.component('progressCircle', {
require: {
parent: '^appMain'
},
bindings: {
},
controller: ProgressCircleCtrl,
controllerAs: 'progressCircle',
templateUrl: 'progress-circle.html',
bindToController: true
});
function ProgressCircleCtrl() {
var progressCircle = this;
// passedin padding
progressCircle.$onInit = function() {
// d3 logic
// d3.select('.vertical-bars svg')
// .append('text')
// .text('vertical bars + d3')
// .attr('text-anchor','middle')
// .attr('alignment-baseline', 'middle')
// .attr('font-size','16px')
// .attr('fill','black')
// .attr('x', '50%')
// .attr('y', '50%')
}
progressCircle.$postLink = function() {
}
}
<!-- could refactor these:
https://codepen.io/craig-o-curtis/pen/LLdaad
https://codepen.io/craig-o-curtis/pen/vZRPMV
https://codepen.io/craig-o-curtis/pen/PjRLrv
-->
<h5>Progress Circle:</h5>
<div class="svg-container progress-circle">
<!-- progress circle -->
<svg class="svg-scalable"
viewBox="0 0 200 100"
preserveAspectRation="xMinYMin meet">
<g>
</g>
</svg>
</div>
angular.module('app.d3',[])
.factory('d3', function() {
return d3;
});