UNPKG

save-svg-as-png

Version:

Convert a browser SVG to PNG or dataUri

461 lines (422 loc) 22 kB
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script> <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> <script> angular.module("ngRadialGauge",[]).directive('ngRadialGauge', ['$window', '$timeout', function ($window, $timeout) { return { restrict: 'EAC', scope: { data: '=', lowerLimit: '=', upperLimit: '=', ranges: '=', value: '=', valueUnit: '=', precision: '=', majorGraduationPrecision: '=', label: '=',// MODIFIED onClick: '&' }, link: function (scope, ele, attrs) { var defaultUpperLimit = 100; var defaultLowerLimit = 0; var initialized = false; var renderTimeout; var gaugeAngle = parseInt(attrs.angle) || 120; //New width variable, now works in conjunction with fixed viewBox sizing var _width = attrs.width || "100%"; /* Colin Bester Width and height are not really such an issue with SVG but choose these values as width of 300 seems to be pretty baked into code. I took the easy path seeing as size is not that relevant and hard coded width and height as I was too lazy to dig deep into code. May be the wrong call, but seems safe option. */ var view = { width : 300, height : 225 }; var innerRadius = Math.round((view.width * 130) / 300); var outerRadius = Math.round((view.width * 145) / 300); var majorGraduations = parseInt(attrs.majorGraduations - 1) || 5; var minorGraduations = parseInt(attrs.minorGraduations) || 10; var majorGraduationLength = Math.round((view.width * 16) / 300); var minorGraduationLength = Math.round((view.width * 10) / 300); var majorGraduationMarginTop = Math.round((view.width * 7) / 300); var majorGraduationColor = attrs.majorGraduationColor || "#B0B0B0"; var minorGraduationColor = attrs.minorGraduationColor || "#D0D0D0"; var majorGraduationTextColor = attrs.majorGraduationTextColor || "#6C6C6C"; var needleColor = attrs.needleColor || "#416094"; var valueVerticalOffset = Math.round((view.width * 30) / 300); var inactiveColor = "#D7D7D7"; var transitionMs = parseInt(attrs.transitionMs) || 750; var majorGraduationTextSize = parseInt(attrs.majorGraduationTextSize); var needleValueTextSize = parseInt(attrs.needleValueTextSize); var needle = undefined; //The scope.data object might contain the data we need, otherwise we fall back on the scope.xyz property var extractData = function (prop) { if (!scope.data) return scope[prop]; if (scope.data[prop] === undefined || scope.data[prop] == null) { return scope[prop]; } return scope.data[prop]; }; var maxLimit; var minLimit; var value; var valueUnit; var precision; var majorGraduationPrecision; var ranges; var label; var updateInternalData = function() { maxLimit = extractData('upperLimit') ? extractData('upperLimit') : defaultUpperLimit; minLimit = extractData('lowerLimit') ? extractData('lowerLimit') : defaultLowerLimit; value = extractData('value'); valueUnit = extractData('valueUnit'); precision = extractData('precision'); majorGraduationPrecision = extractData('majorGraduationPrecision'); ranges = extractData('ranges'); label = extractData('label'); // MODIFIED }; updateInternalData(); /* Colin Bester Add viewBox and width attributes. Used view.width and view.height in case it's decided that hardcoding these values is an issue. Width can be specified as %, px etc and will scale image to fit. */ var svg = d3.select(ele[0]) .append('svg') .attr('width', _width) .attr('viewBox', '0 0 '+view.width+' '+view.height); // .attr('view.width', view.width) // .attr('height', view.width * 0.75); var renderMajorGraduations = function (majorGraduationsAngles) { var centerX = view.width / 2; var centerY = view.width / 2; //Render Major Graduations majorGraduationsAngles.forEach(function (pValue, index) { var cos1Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength)); var sin1Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength)); var cos2Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop)); var sin2Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop)); var x1 = centerX + cos1Adj; var y1 = centerY + sin1Adj * -1; var x2 = centerX + cos2Adj; var y2 = centerY + sin2Adj * -1; svg.append("svg:line") .attr("x1", x1) .attr("y1", y1) .attr("x2", x2) .attr("y2", y2) .style("stroke", majorGraduationColor); renderMinorGraduations(majorGraduationsAngles, index); }); }; var renderMinorGraduations = function (majorGraduationsAngles, indexMajor) { var minorGraduationsAngles = []; if (indexMajor > 0) { var minScale = majorGraduationsAngles[indexMajor - 1]; var maxScale = majorGraduationsAngles[indexMajor]; var scaleRange = maxScale - minScale; for (var i = 1; i < minorGraduations; i++) { var scaleValue = minScale + i * scaleRange / minorGraduations; minorGraduationsAngles.push(scaleValue); } var centerX = view.width / 2; var centerY = view.width / 2; //Render Minor Graduations minorGraduationsAngles.forEach(function (pValue, indexMinor) { var cos1Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - minorGraduationLength)); var sin1Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - minorGraduationLength)); var cos2Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop)); var sin2Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop)); var x1 = centerX + cos1Adj; var y1 = centerY + sin1Adj * -1; var x2 = centerX + cos2Adj; var y2 = centerY + sin2Adj * -1; svg.append("svg:line") .attr("x1", x1) .attr("y1", y1) .attr("x2", x2) .attr("y2", y2) .style("stroke", minorGraduationColor); }); } }; var getMajorGraduationValues = function (pMinLimit, pMaxLimit, pPrecision) { var scaleRange = pMaxLimit - pMinLimit; var majorGraduationValues = []; for (var i = 0; i <= majorGraduations; i++) { var scaleValue = pMinLimit + i * scaleRange / (majorGraduations); majorGraduationValues.push(scaleValue.toFixed(pPrecision)); } return majorGraduationValues; }; var getMajorGraduationAngles = function () { var scaleRange = 2 * gaugeAngle; var minScale = -1 * gaugeAngle; var graduationsAngles = []; for (var i = 0; i <= majorGraduations; i++) { var scaleValue = minScale + i * scaleRange / (majorGraduations); graduationsAngles.push(scaleValue); } return graduationsAngles; }; var getNewAngle = function(pValue) { var scale = d3.scale.linear().range([0, 1]).domain([minLimit, maxLimit]); var ratio = scale(pValue); var scaleRange = 2 * gaugeAngle; var minScale = -1 * gaugeAngle; var newAngle = minScale + (ratio * scaleRange); return newAngle; }; var renderMajorGraduationTexts = function (majorGraduationsAngles, majorGraduationValues, pValueUnit) { if (!ranges) return; var centerX = view.width / 2; var centerY = view.width / 2; var textVerticalPadding = 5; var textHorizontalPadding = 5; var lastGraduationValue = majorGraduationValues[majorGraduationValues.length - 1]; var textSize = isNaN(majorGraduationTextSize) ? (view.width * 12) / 300 : majorGraduationTextSize; var fontStyle = textSize + "px Courier"; var dummyText = svg.append("text") .attr("x", centerX) .attr("y", centerY) .attr("fill", "transparent") .attr("text-anchor", "middle") .style("font", fontStyle) .text(lastGraduationValue + pValueUnit); var textWidth = dummyText.node().getBBox().width; for (var i = 0; i < majorGraduationsAngles.length; i++) { var angle = majorGraduationsAngles[i]; var cos1Adj = Math.round(Math.cos((90 - angle) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength - textHorizontalPadding)); var sin1Adj = Math.round(Math.sin((90 - angle) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength - textVerticalPadding)); var sin1Factor = 1; if (sin1Adj < 0) sin1Factor = 1.1; if (sin1Adj > 0) sin1Factor = 0.9; if (cos1Adj > 0) { if (angle > 0 && angle < 45) { cos1Adj -= textWidth / 2; } else { cos1Adj -= textWidth; } } if (cos1Adj < 0) { if (angle < 0 && angle > -45) { cos1Adj -= textWidth / 2; } } if (cos1Adj == 0) { cos1Adj -= angle == 0 ? textWidth / 4 : textWidth / 2; } var x1 = centerX + cos1Adj; var y1 = centerY + sin1Adj * sin1Factor * -1; svg.append("text") .attr("class", "mtt-majorGraduationText") .style("font", fontStyle) .attr("text-align", "center") .attr("x", x1) .attr("dy", y1) .attr("fill", majorGraduationTextColor) .text(majorGraduationValues[i] + pValueUnit); } }; var renderGraduationNeedle = function (value, valueUnit, precision, minLimit, maxLimit) { svg.selectAll('.mtt-graduation-needle').remove(); svg.selectAll('.mtt-graduationValueText').remove(); svg.selectAll('.mtt-graduation-needle-center').remove(); var centerX = view.width / 2; var centerY = view.width / 2; var centerColor; if (typeof value === 'undefined') { centerColor = inactiveColor; } else { centerColor = needleColor; var needleAngle = getNewAngle(value); var needleLen = innerRadius - majorGraduationLength - majorGraduationMarginTop; var needleRadius = (view.width * 2.5) / 300; var textSize = isNaN(needleValueTextSize) ? (view.width * 12) / 300 : needleValueTextSize; var fontStyle = textSize + "px Courier"; if (value >= minLimit && value <= maxLimit) { var lineData = [ [needleRadius, 0], [0, -needleLen], [-needleRadius, 0], [needleRadius, 0] ]; var pointerLine = d3.svg.line().interpolate('monotone'); var pg = svg.append('g').data([lineData]) .attr('class', 'mtt-graduation-needle') .style("fill", needleColor) .attr('transform', 'translate(' + centerX + ',' + centerY + ')'); needle = pg.append('path') .attr('d', pointerLine) .attr('transform', 'rotate('+needleAngle+')'); } svg.append("text") .attr("x", centerX) .attr("y", centerY + valueVerticalOffset) .attr("class", "mtt-graduationValueText") .attr("fill", needleColor) .attr("text-anchor", "middle") .attr("font-weight", "bold") .style("font", fontStyle) .text(value.toFixed(precision) + valueUnit); // MODIFIED: Added a customizable label svg.append("text") .attr("x", centerX) .attr("y", centerY + valueVerticalOffset + 10) .attr("class", "mtt-graduationValueText") .attr("fill", needleColor) .attr("text-anchor", "middle") .attr("font-weight", "bold") .style("font", fontStyle) .text(label); } var circleRadius = (view.width * 6) / 300; svg.append("circle") .attr("r", circleRadius) .attr("cy", centerX) .attr("cx", centerY) .attr("fill", centerColor) .attr("class", "mtt-graduation-needle-center"); }; $window.onresize = function () { scope.$apply(); }; scope.$watch(function () { return angular.element($window)[0].innerWidth; }, function () { scope.render(); }); /* Colin Bester Removed watching of data.value as couldn't see reason for this, plus it's the cause of flicker when using data=option mode of using directive. I'm assuming that calling render function is not what was intended on every value update. */ // scope.$watchCollection('[ranges, data.ranges, data.value]', function () { scope.$watchCollection('[ranges, data.ranges]', function () { scope.render(); }, true); scope.render = function () { updateInternalData(); svg.selectAll('*').remove(); if (renderTimeout) clearTimeout(renderTimeout); renderTimeout = $timeout(function () { var d3DataSource = []; if (typeof ranges === 'undefined') { d3DataSource.push([minLimit, maxLimit, inactiveColor]); } else { //Data Generation ranges.forEach(function (pValue, index) { d3DataSource.push([pValue.min, pValue.max, pValue.color]); }); } //Render Gauge Color Area var translate = "translate(" + view.width / 2 + "," + view.width / 2 + ")"; var cScale = d3.scale.linear().domain([minLimit, maxLimit]).range([-1 * gaugeAngle * (Math.PI / 180), gaugeAngle * (Math.PI / 180)]); var arc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(outerRadius) .startAngle(function (d) { return cScale(d[0]); }) .endAngle(function (d) { return cScale(d[1]); }); svg.selectAll("path") .data(d3DataSource) .enter() .append("path") .attr("d", arc) .style("fill", function (d) { return d[2]; }) .attr("transform", translate); var majorGraduationsAngles = getMajorGraduationAngles(); var majorGraduationValues = getMajorGraduationValues(minLimit, maxLimit, majorGraduationPrecision); renderMajorGraduations(majorGraduationsAngles); renderMajorGraduationTexts(majorGraduationsAngles, majorGraduationValues, valueUnit); renderGraduationNeedle(value, valueUnit, precision, minLimit, maxLimit); initialized = true; }, 200); }; var onValueChanged = function(pValue, pPrecision, pValueUnit) { if (typeof pValue === 'undefined' || pValue == null) return; if (needle && pValue >= minLimit && pValue <= maxLimit) { var needleAngle = getNewAngle(pValue); needle.transition() .duration(transitionMs) .ease('elastic') .attr('transform', 'rotate('+needleAngle+')'); svg.selectAll('.mtt-graduationValueText') .text('[ ' + pValue.toFixed(pPrecision) + pValueUnit + ' ]') ; } else { svg.selectAll('.mtt-graduation-needle').remove(); svg.selectAll('.mtt-graduationValueText').remove(); svg.selectAll('.mtt-graduation-needle-center').attr("fill", inactiveColor); } }; scope.$watchCollection('[value, data.value]', function () { if (!initialized) return; updateInternalData(); onValueChanged(value, precision, valueUnit); }, true); } }; }]); </script> <script src=saveSvgAsPng.js></script> <script> angular.module('RadialGaugeDemo', [ 'ngRadialGauge' ]); angular.module('RadialGaugeDemo').controller('RadialGaugeDemoCtrl', ['$scope', '$timeout', function ($scope, $timeout) { $scope.value = 1.5; $scope.upperLimit = 6; $scope.lowerLimit = 0; $scope.unit = "kW"; $scope.precision = 2; $scope.ranges = [ { min: 0, max: 1.5, color: '#DEDEDE' }, { min: 1.5, max: 2.5, color: '#8DCA2F' }, { min: 2.5, max: 3.5, color: '#FDC702' }, { min: 3.5, max: 4.5, color: '#FF7700' }, { min: 4.5, max: 6.0, color: '#C50200' } ]; $scope.OnClick = function() { console.log("click"); svgAsDataUri(document.getElementsByTagName("svg")[0], null, function (uri) { var img = '<img class="img-thumbnail" src="' + uri + '">'; d3.select("#svgpreview").html(img); }); } }]); </script> </head> <body ng-app="RadialGaugeDemo"> <div ng-controller="RadialGaugeDemoCtrl"> <div width="10%" ng-radial-gauge ranges="ranges" value="value" value-unit="unit" precision="precision" lower-limit="lowerLimit" upper-limit="upperLimit"></div> <a href="" ng-click="OnClick()">Click Here to show image Preview</a> <div id="svgpreview"></div> </div> </body> </html>