Using D3 javascript diagram without sending id of the element - angularjs

I'm trying use D3 diagram component in my Angular project. By default the svg tag used by D3 needs id, but I want to use the element itself instead of the id.
here is my angular directive code:
'use strict';
angular.module('mbCharts').directive('mbHumidity', [
'mbWebMetricsService', '$window', '$timeout', 'd3Service',
function (mbWebMetricsService ,$window, $timeout, d3Service) {
return {
//We restrict its use to an element
//as usually <bars-chart> is semantically
//more understandable
restrict: 'E',
//this is important,
//we don't want to overwrite our directive declaration
//in the HTML mark-up
replace: false,
//our data source would be an array
//passed thru chart-data attribute
template: "<svg width='97%' height='250' onclick='gauge1.update(NewValue());'></svg>",
link: function (scope, elem, attrs) {
var gauge1 = loadLiquidFillGauge(elem[0], 0);
var config1 = liquidFillGaugeDefaultSettings();
config1.circleColor = "#FF7777";
config1.textColor = "#FF4444";
config1.waveTextColor = "#FFAAAA";
config1.waveColor = "#FFDDDD";
config1.circleThickness = 0.2;
config1.textVertPosition = 0.2;
config1.waveAnimateTime = 1800;
scope.$on('mbWebMetricsService-received-data-event', function (evt, data) {
var val = Math.round(data[scope.metric]);
gauge1.update(val);
});
}
}
}
]);
and here is the code for loadLiquidFillGauge method:
function liquidFillGaugeDefaultSettings(){
return {
minValue: 0, // The gauge minimum value.
maxValue: 100, // The gauge maximum value.
circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
circleColor: "#178BCA", // The color of the outer circle.
waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
waveCount: 1, // The number of full waves per width of the wave circle.
waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
waveAnimate: true, // Controls if the wave scrolls or is static.
waveColor: "#178BCA", // The color of the fill wave.
waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
displayPercent: true, // If true, a % symbol is displayed after the value.
textColor: "#045681", // The color of the value text when the wave does not overlap it.
waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
};
}
function loadLiquidFillGauge(element, value, config) {
if(config == null) config = liquidFillGaugeDefaultSettings();
var gauge = d3.select(element);
var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
var locationX = parseInt(gauge.style("width"))/2 - radius;
var locationY = parseInt(gauge.style("height"))/2 - radius;
var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
var waveHeightScale;
if(config.waveHeightScaling){
waveHeightScale = d3.scale.linear()
.range([0,config.waveHeight,0])
.domain([0,50,100]);
} else {
waveHeightScale = d3.scale.linear()
.range([config.waveHeight,config.waveHeight])
.domain([0,100]);
}
var textPixels = (config.textSize*radius/2);
var textFinalValue = parseFloat(value).toFixed(2);
var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
var percentText = config.displayPercent?"%":"";
var circleThickness = config.circleThickness * radius;
var circleFillGap = config.circleFillGap * radius;
var fillCircleMargin = circleThickness + circleFillGap;
var fillCircleRadius = radius - fillCircleMargin;
var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
var waveLength = fillCircleRadius*2/config.waveCount;
var waveClipCount = 1+config.waveCount;
var waveClipWidth = waveLength*waveClipCount;
// Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
var textRounder = function(value){ return Math.round(value); };
if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
textRounder = function(value){ return parseFloat(value).toFixed(1); };
}
if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
textRounder = function(value){ return parseFloat(value).toFixed(2); };
}
// Data for building the clip wave area.
var data = [];
for(var i = 0; i <= 40*waveClipCount; i++){
data.push({x: i/(40*waveClipCount), y: (i/(40))});
}
// Scales for drawing the outer circle.
var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);
// Scales for controlling the size of the clipping path.
var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
// Scales for controlling the position of the clipping path.
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
.domain([0,1]);
var waveAnimateScale = d3.scale.linear()
.range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
.domain([0,1]);
// Scale for controlling the position of the text within the gauge.
var textRiseScaleY = d3.scale.linear()
.range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
.domain([0,1]);
// Center the gauge within the parent SVG.
var gaugeGroup = gauge.append("g")
.attr('transform','translate('+locationX+','+locationY+')');
// Draw the outer circle.
var gaugeCircleArc = d3.svg.arc()
.startAngle(gaugeCircleX(0))
.endAngle(gaugeCircleX(1))
.outerRadius(gaugeCircleY(radius))
.innerRadius(gaugeCircleY(radius-circleThickness));
gaugeGroup.append("path")
.attr("d", gaugeCircleArc)
.style("fill", config.circleColor)
.attr('transform','translate('+radius+','+radius+')');
// Text where the wave does not overlap.
var text1 = gaugeGroup.append("text")
.text(textRounder(textStartValue) + percentText)
.attr("class", "liquidFillGaugeText")
.attr("text-anchor", "middle")
.attr("font-size", textPixels + "px")
.style("fill", config.textColor)
.attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
// The clipping wave area.
var clipArea = d3.svg.area()
.x(function(d) { return waveScaleX(d.x); } )
.y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
.y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
var waveGroup = gaugeGroup.append("defs")
.append("clipPath")
.attr("id", "clipWave");
var wave = waveGroup.append("path")
.datum(data)
.attr("d", clipArea)
.attr("T", 0);
// The inner circle with the clipping wave attached.
var fillCircleGroup = gaugeGroup.append("g")
.attr("clip-path", "url(#clipWave)");
fillCircleGroup.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", fillCircleRadius)
.style("fill", config.waveColor);
// Text where the wave does overlap.
var text2 = fillCircleGroup.append("text")
.text(textRounder(textStartValue) + percentText)
.attr("class", "liquidFillGaugeText")
.attr("text-anchor", "middle")
.attr("font-size", textPixels + "px")
.style("fill", config.waveTextColor)
.attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
// Make the value count up.
if(config.valueCountUp){
var textTween = function(){
var i = d3.interpolate(this.textContent, textFinalValue);
return function(t) { this.textContent = textRounder(i(t)) + percentText; }
};
text1.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
text2.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
}
// Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
if(config.waveRise){
waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
.transition()
.duration(config.waveRiseTime)
.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
.each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
} else {
waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
}
if(config.waveAnimate) animateWave();
function animateWave() {
wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
wave.transition()
.duration(config.waveAnimateTime * (1-wave.attr('T')))
.ease('linear')
.attr('transform','translate('+waveAnimateScale(1)+',0)')
.attr('T', 1)
.each('end', function(){
wave.attr('T', 0);
animateWave(config.waveAnimateTime);
});
}
function GaugeUpdater(){
this.update = function(value){
var newFinalValue = parseFloat(value).toFixed(2);
var textRounderUpdater = function(value){ return Math.round(value); };
if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
textRounderUpdater = function(value){ return parseFloat(value).toFixed(1); };
}
if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
textRounderUpdater = function(value){ return parseFloat(value).toFixed(2); };
}
var textTween = function(){
var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
return function(t) { this.textContent = textRounderUpdater(i(t)) + percentText; }
};
text1.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
text2.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
.domain([0,1]);
var newHeight = waveRiseScale(fillPercent);
var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
var newClipArea;
if(config.waveHeightScaling){
newClipArea = d3.svg.area()
.x(function(d) { return waveScaleX(d.x); } )
.y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
.y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
} else {
newClipArea = clipArea;
}
var newWavePosition = config.waveAnimate?waveAnimateScale(1):0;
wave.transition()
.duration(0)
.transition()
.duration(config.waveAnimate?(config.waveAnimateTime * (1-wave.attr('T'))):(config.waveRiseTime))
.ease('linear')
.attr('d', newClipArea)
.attr('transform','translate('+newWavePosition+',0)')
.attr('T','1')
.each("end", function(){
if(config.waveAnimate){
wave.attr('transform','translate('+waveAnimateScale(0)+',0)');
animateWave(config.waveAnimateTime);
}
});
waveGroup.transition()
.duration(config.waveRiseTime)
.attr('transform','translate('+waveGroupXPosition+','+newHeight+')')
}
}
return new GaugeUpdater();
}

Do it like this to select the SVG within the element:
var gauge = d3.select(element).select("svg");

Related

OpenLayer 4 draw arrows on map

I have a map with multiple points I want to draw arrows that point to these locations from the border of the map. The arrow should dynamically update its position on the screen when to users pan or zoom the map.
How can one draw an arrow on a map that point to location?
You can draw regular lines to the points and apply arrow style to them as shown in this example.
You just need to place the arrow at the end coordinate instead of applying it on each segment.
var styleFunction = function (feature) {
var geometry = feature.getGeometry();
var styles = [
// Linestring
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
})
})
];
var coordinates = geometry.getCoordinates();
var length = coordinates.length;
// Last two coordinates for calculating rotation
var end = coordinates[length - 1];
var start = coordinates[length - 2];
var dx = end[0] - start[0];
var dy = end[1] - start[1];
var rotation = Math.atan2(dy, dx);
// Arrow
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(end),
image: new ol.style.Icon({
src: 'https://openlayers.org/en/v4.6.5/examples/data/arrow.png',
anchor: [0.75, 0.5],
rotateWithView: true,
rotation: -rotation
})
}));
return styles;
};

D3 area graph animation

I have an area graph ( see js fiddle https://jsfiddle.net/o7df3tyn/ ) I want to animate this area graph. I tried the approach in this
question , but this doesnt seem to help because I have more line graphs in the the same svg element
var numberOfDays = 30;
var vis = d3.select('#visualisation'),
WIDTH = 1000,
HEIGHT = 400,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 50
};
var drawArea = function (data) {
var areaData = data;
// var areaData = data.data;
var xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([0, numberOfDays + 1]),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([_.min(areaData), _.max(areaData)]);
var area = d3.svg.area()
.interpolate("monotone")
.x(function(d) {
return xRange(areaData.indexOf(d));
})
.y0(HEIGHT)
.y1(function(d) {
return yRange(d);
});
var path = vis.append("path")
.datum(areaData)
.attr("fill", 'lightgrey')
.attr("d", area);
};
var data = [1088,978,1282,755,908,1341,616,727,1281,247,1188,11204,556,15967,623,681,605,7267,4719,9665,5719,5907,3520,1286,1368,3243,2451,1674,1357,7414,2726]
drawArea(data);
So I cant use the curtain approach.
I want to animate the area from bottom.
Any ideas / explanations ?
Just in case anyone else stuck in the same problem, #thatOneGuy nailed the exact problem. My updated fiddle is here https://jsfiddle.net/sahils/o7df3tyn/14/
https://jsfiddle.net/DavidGuan/o7df3tyn/2/
vis.append("clipPath")
.attr("id", "rectClip")
.append("rect")
.attr("width", 0)
.attr("height", HEIGHT);
You can have a try now.
Remember add clip-path attr to the svg elements you want to hide
In this case
var path = vis.append("path")
.datum(areaData)
.attr("fill", 'lightgrey')
.attr("d", area)
.attr("clip-path", "url(#rectClip)")
Update:
If we want to grow the area from bottom:
vis.append("clipPath")
.attr("id", "rectClip")
.append("rect")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.attr("transform", "translate(0," + HEIGHT + ")")
d3.select("#rectClip rect")
.transition().duration(6000)
.attr("transform", "translate(0," + 0 + ")")
The other answer is okay but this doesn't animate the graph.
Here is how I would do it.
I would add an animation tween to the path so it tweens from 0 to the point on the path.
Something like so :
//create an array of 0's the same size as your current array :
var startData = areaData.map(function(datum) {
return 0;
});
//use this and tween between startData and data
var path = vis.append("path")
.datum(startdata1)
.attr("fill", 'lightgrey')
.attr("d", area)
.transition()
.duration(1500)
.attrTween('d', function() {
var interpolator = d3.interpolateArray(startData, areaData );
return function(t) {
return area(interpolator(t));
}
});
The reason why yours wasn't working was because of this line :
.x(function(d) {
return xRange(areaData.indexOf(d));
})
d at this point is a value between 0 and the current piece of data, so areaData.indexOf(d) will not work.
Just change this :
.x(function(d,i) {
return xRange(i);
})
This will increment along the x axis :)
Updated fiddle : https://jsfiddle.net/thatOneGuy/o7df3tyn/17/

How to display values in Stacked Multi-bar chart - nvd3 Graphs

I got a scenario where in I need to display value for every stack in stacked multi-bar chart - nvd3 graph as we can display value in discrete value - nvd3 graph.
I understand, 'showvalue' is used in discrete bar controller, can we use showvalue in stacked graph, if not please suggest with an alternative solution.
Thanks in advance
Currently isn't possible. That option is available only in the discrete bar chart.
From the maintainer:
We don't have this ability. Stacked/Grouped charts also have complex animations making this a tricky thing to solve. We use tooltips instead.
Source https://github.com/novus/nvd3/issues/150
If you want to do this you need to make your own implementation using d3. There is a good example here http://plnkr.co/edit/BNpAlFalKz0zkkSszHh1?p=preview. It's using the angular wrapper but it's a good starting point.
var app = angular.module('app', ['nvd3ChartDirectives']);
app.controller('chartCtrl', function($scope, $timeout) {
var ANIMATION_TIME = 1500,
countSeriesDisplayed = 2,
promise,
labels = ["label1", "label2", "label3", "label4", "label5"];
$scope.isStacked = false;
// Example data
$scope.chartData = [{
"key": "Series 1",
"values": [
[0, 10],
[1, 20],
[2, 30],
[3, 40],
[4, 50]
]
}, {
"key": "Series 2",
"values": [
[0, 10],
[1, 40],
[2, 60],
[3, 20],
[4, 40]
]
}];
/* To add labels, images, or other nodes on the created SVG, we need to wait
* for the chart to be rendered with a callback.
* Once the chart is rendered, a timeout is set to wait for the animation to
* finish.
*
* Then, we need to find the position of the labels and set it with the
* transform attribute in SVG.
* To do so, we have to get the width and height of each bar/group of bar
* which changes if stacked or not
*
*/
// Callback called when the chartData is assigned
$scope.initLabels = function() {
return function(graph) {
promise = $timeout(function() {
var svg = d3.select("svg"),
lastRects, rectWidth,
heightForXvalue = []; // Used for grouped mode
// We get one positive rect of each serie from the svg (here the last serie)
lastRects = svg.selectAll("g.nv-group").filter(
function(d, i) {
return i == countSeriesDisplayed - 1;
}).selectAll("rect.positive");
if ($scope.isStacked) {
// If stacked, we get the width of one rect
rectWidth = lastRects.filter(
function(d, i) {
return i == countSeriesDisplayed - 1;
}).attr("width");
} else {
// If grouped, we need to get the greatest height of each bar
var nvGroups = svg.selectAll("g.nv-group").selectAll("rect.positive");
nvGroups.each(
function(d, i) {
// Get the Min height space for each group (Max height for each group)
var rectHeight = parseFloat(d3.select(this).attr("y"));
if (angular.isUndefined(heightForXvalue[i])) {
heightForXvalue[i] = rectHeight;
} else {
if (rectHeight < heightForXvalue[i]) {
heightForXvalue[i] = rectHeight;
}
}
}
);
// We get the width of one rect multiplied by the number of series displayed
rectWidth = lastRects.filter(
function(d, i) {
return i == countSeriesDisplayed - 1;
}).attr("width") * countSeriesDisplayed;
}
// We choose a width equals to 70% of the group width
var labelWidth = rectWidth * 70 / 100;
var groupLabels = svg.select("g.nv-barsWrap").append("g");
lastRects.each(
function(d, index) {
var transformAttr = d3.select(this).attr("transform");
var yPos = parseFloat(d3.select(this).attr("y"));
groupLabels.append("text")
.attr("x", (rectWidth / 2) - (labelWidth /2)) // We center the label
// We add a padding of 5 above the highest rect
.attr("y", (angular.isUndefined(heightForXvalue[index]) ? yPos : heightForXvalue[index]) - 5)
// We set the text
.text(labels[index])
.attr("transform", transformAttr)
.attr("class", "bar-chart-label");
});
}, ANIMATION_TIME);
}
};
// Tooltips
$scope.toolTipContentFunction = function () {
return function (key, x, y, e, graph) {
return labels[x];
}
};
$scope.$on('$destroy', function () {
// Cancel timeout if still active
$timeout.cancel(promise);
});
});
UPDATE:
I've created a gist that could help you to implement this by yourself.
https://gist.github.com/topicus/217444acb4204f364e46

How to enable responsive design for Fabric.js

Is there a way to make a Fabric.js canvas resize with the browser to enable the same result on any device? I'm talking about responsive design.
Has anyone a code example?
This jsFiddle is a working solution. It is inspired by this github issue.
These are the required things:
A div that surrounds the canvas that is controlled by fabric.
<div class="fabric-canvas-wrapper">
<canvas id="theCanvas"></canvas>
</div>
And a window resize handler that triggers the calculation and setting of the new canvas dimension and the zoom.
window.onresize = resizeCanvas() {
const outerCanvasContainer = document.getElementById('fabric-canvas-wrapper');
const ratio = canvas.getWidth() / canvas.getHeight();
const containerWidth = outerCanvasContainer.clientWidth;
const scale = containerWidth / canvas.getWidth();
const zoom = canvas.getZoom() * scale;
canvas.setDimensions({width: containerWidth, height: containerWidth / ratio});
canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
}
That's all. It seems to work perfectly. Please let me know if any issues come up with this solution.
I have used fabric.js in version 3.6.2.
Basically you need to get the device screen's width and height. Afterwards just resize the canvas accordingly in your Javascript. Example:
var width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
var height = (window.innerHeight > 0) ? window.innerHeight : screen.height;
var canvas = document.getElementById("canvas");
canvas.width = width;
canvas.height = height;
You might have screens with varying resolution ratios though, what I usually do in this case is calculate your original width's and the device's width ratio and then adjust both width and height accordingly with that value. Example:
var originalWidth = 960; //Example value
var width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
var widthRatio = originalWidth / width;
canvas.width *= widthRatio;
canvas.height *= widthRatio;
This usually works fine for me on any device, hope this helps.
I know it's been a while but i found an easier way to do it using the zoom feature.
This code is triggered when the window resize is completed. It allows me to determine based on the new dimension, the zoom factor needed to resize everything in the canvas.
function rescale_canvas_if_needed()
{
var optimal_dimensions = get_optimal_canvas_dimensions();
var scaleFactor=optimal_dimensions[0]/wpd.canvas_w;
if(scaleFactor != 1) {
wpd_editor.canvas.setWidth(optimal_dimensions[0]);
wpd_editor.canvas.setHeight(optimal_dimensions[1]);
wpd_editor.canvas.setZoom(scaleFactor);
wpd_editor.canvas.calcOffset();
wpd_editor.canvas.renderAll();
}
}
$( window ).resize(function() {
clearTimeout(resizeId);
resizeId = setTimeout(handle_resize, 500);
});
function handle_resize()
{
$(".canvas-container").hide();
rescale_canvas_if_needed();
$(".canvas-container").show();
}
i thought this one was the answer I needed https://stackoverflow.com/a/29445765/1815624
it seemed so close but after some adjustments I got what I wanted it down scales when needed and maxes out at a scale of 1.
The canvas size you desire should be set as width/height var optimal_dimensions = [1000,1000];
this needs some more changes to handle height as well so when a user rotates there phone to landscape it may become to big to see it all on the screen
the new edited code will be at 1000 pixels in width and height for this demo
update: added detection for height and width to scale to window...
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="1000" height="1000"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.0.0/fabric.min.js"></script>
<script>
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
canvas.item(0).set({
borderColor: 'gray',
cornerColor: 'black',
cornerSize: 12,
transparentCorners: true
});
canvas.setActiveObject(canvas.item(0));
canvas.renderAll();
function rescale_canvas_if_needed(){
var optimal_dimensions = [400,400];
var scaleFactorX=window.innerWidth/optimal_dimensions[0];
var scaleFactorY=window.innerHeight/optimal_dimensions[1];
if(scaleFactorX < scaleFactorY && scaleFactorX < 1) {
canvas.setWidth(optimal_dimensions[0] *scaleFactorX);
canvas.setHeight(optimal_dimensions[1] *scaleFactorX);
canvas.setZoom(scaleFactorX);
} else if(scaleFactorX > scaleFactorY && scaleFactorY < 1){
canvas.setWidth(optimal_dimensions[0] *scaleFactorY);
canvas.setHeight(optimal_dimensions[1] *scaleFactorY);
canvas.setZoom(scaleFactorY);
}else {
canvas.setWidth(optimal_dimensions[0] );
canvas.setHeight(optimal_dimensions[1] );
canvas.setZoom(1);
}
canvas.calcOffset();
canvas.renderAll();
}
function handle_resize(){
$(".canvas-container").hide();
rescale_canvas_if_needed();
$(".canvas-container").show();
}
var resizeId = null;
$(function() {
$(window).resize(function() {
if(resizeId != null)
clearTimeout(resizeId);
resizeId = setTimeout(handle_resize, 500);
});
console.log( "ready!" );
/* auto size it right away... */
resizeId = setTimeout(handle_resize, 500);
});
</script>
hope this helps someone...good day.
Resize the canvas depending on the size of an element with jQuery.
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.setHeight(jQuery('#image').height());
canvas.setWidth(jQuery('#image').width());
canvas.renderAll();
}
resizeCanvas();

How to stop a Google map from drawing a second rectangle after a button is clicked

I am using a the following code to draw a rectangle on the map to a random lat. & long. based on a city name when a find button is clicked. The problem is if you do not put a different city name in and click the find button the program will draw a second rectangle on the cities original lat. & long. I either need to clear the map and redraw the rectangle or something not quite sure so a second rectangle can not be drawn. Here is the code I am using.
function codeAddress() {
var address = document.getElementById("gadres").value;
if(address=='') {
alert("Address can not be empty!");
return;
}
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
document.getElementById('lat').value=results[0].geometry.location.lat().toFixed(6);
document.getElementById('lng').value=results[0].geometry.location.lng().toFixed(6);
var latlong = "(" + results[0].geometry.location.lat().toFixed(6) + " , " +
+ results[0].geometry.location.lng().toFixed(6) + ")";
map.setZoom(15);
var mapcenter = map.getCenter();
var maplat = mapcenter.lat();
var maplng = mapcenter.lng();
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(maplat - 0.002, maplng - 0.002),
new google.maps.LatLng(maplat + 0.002, maplng + 0.002)
);
var rectangle = new google.maps.Rectangle({
bounds: bounds,
draggable:true,
editable: true,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 4,
fillColor: '#FF0000',
fillOpacity: 0.30,
});
rectangle.setMap(map);
google.maps.event.addListener(rectangle, 'bounds_changed', function() {
document.getElementById('coords').value = rectangle.getBounds();
});
} else {
alert("Lat and long cannot be found.");
}
});
}
all help greatly appreciated.
I haven't tried this yet but perhaps you can store your bounds to an array then use a loop statement for that array to check if there is an equal bounds using the equals() method
https://developers.google.com/maps/documentation/javascript/reference#LatLngBounds

Resources