Try as I might, I've been unable to render a d3.js county map without causing the map to blur significantly.
I'm using the usual tricks: My canvas style width is half that of my attribute width. I translate the context of the drawing half a pixel to offset any unwanted effects.
But it's still terribly blurry.
Can someone share the pattern for a crisp d3.js map made for canvas elements?
function drawQuintiles() {
var width = 960,
height = 500;
var projection = d3.geo.albers()
.scale(666);
var canvas = d3.select("#quintiles")
.append("canvas")
.attr("class",'canvasarea');
var context = canvas.node().getContext("2d");
var ratio = (window.devicePixelRatio / context.webkitBackingStorePixelRatio) || 1;
d3.select('.canvasarea')
.attr("width", width * ratio).attr("height", height * ratio)
.style("width", width + "px").style("height", height + "px");
context.scale(ratio, ratio);
var path = d3.geo.path()
.projection(projection)
.context(context);
d3.json("/data/us-counties.json", function(error, us) {
if (error) throw error;
context.strokeStyle = '#333';
context.beginPath();
var strokeWidth = 0.5;
var iTranslate = (strokeWidth % 2) / 2;
context.translate(iTranslate, 0);
context.lineWidth = strokeWidth;
context.lineCap = "round";
path(topojson.feature(us, us.objects.counties));
context.stroke();
});
}
This is the code I ended on. Removing the scale and the translate hack has the map rendering properly.
function drawQuintiles() {
var width = 1600;
d3.json("/data/us-counties.json", function(error, data) {
var projection = d3.geo.albersUsa();
var path = d3.geo.path().projection(projection);
var tracts = topojson.feature(data, data.objects.counties);
projection.scale(1).translate([0, 0]);
var b = path.bounds(tracts);
var whRatio = ((b[1][0] - b[0][0]) / (b[1][1] - b[0][1]));
var height = (width / 2) * whRatio;
var s = .98 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection.scale(s).translate(t);
var canvas = d3.select("#quintiles")
.append("canvas")
.attr("class",'canvasarea');
var context = canvas.node().getContext("2d");
var ratio = window.devicePixelRatio || 1;
d3.select('.canvasarea')
.attr("width", width ).attr("height", height )
.style("width", ((width * ratio) ) + "px").style("height", ((height * ratio) ) + "px");
var path = d3.geo.path()
.projection(projection)
.context(context);
if (error) throw error;
context.strokeStyle = '#333';
context.beginPath();
var strokeWidth = 0.5;
context.lineWidth = strokeWidth;
context.lineCap = "round";
path(topojson.feature(data, data.objects.counties));
context.stroke();
});
}
drawQuintiles();
Related
I was trying to contribute to this post, HTML5 Canvas and Line Width
but it was deleted because it's not an official answer, because technically I'm also asking a question using the following code I get the same problem.
"I'm drawing line graphs on a canvas. The lines draw fine. The graph is scaled, every segment is drawn, color are ok, etc. My only problem is visually the line width varies. It's almost like the nib of a caligraphy pen. If the stroke is upward the line is thin, if the stroke is horizontal, the line is thicker.
My line thickness is constant, and my strokeStyle is set to black. I don't see any other properties of the canvas that affect such a varying line width but there must be.
"
<html>
<head>
<style>html{font-family:Verdana;}</style>
<script type="text/javascript">
var canvas ;
var context ;
var Val_max;
var Val_min;
var sections;
var xScale;
var yScale;
var Samsung = [21000,21000,23000,22000,22000,23000,23000];
function init() {
// set these values for your data
sections = 7;
Val_max = 25000;
Val_min = 10000;
var stepSize = 1500;
var columnSize = 75;
var rowSize = 75;
var margin = 10;
var xAxis = [""," Monday "," Tuesday"," Wednesday"," Thursday"," Friday"," Saturday"," Sunday"]//;
//
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
context.fillStyle = "#4d4d4d"
context.font = "10 pt Arial"
yScale = (canvas.height - columnSize - margin) / (Val_max - Val_min);
xScale = (canvas.width - rowSize) / sections;
context.strokeStyle="#4d4d4d"; // color of grid lines
context.beginPath();
// print Parameters on X axis, and grid lines on the graph
for (i=1;i<=sections;i++) {
var x = i * xScale;
context.fillText(xAxis[i], x,columnSize - margin);
context.moveTo(x, columnSize);
context.lineTo(x, canvas.height - margin);
}
// print row header and draw horizontal grid lines
var count = 0;
for (scale=Val_max;scale>=Val_min;scale = scale - stepSize) {
var y = columnSize + (yScale * count * stepSize);
context.fillText(scale, margin,y + margin);
context.moveTo(rowSize,y)
context.lineTo(canvas.width,y)
count++;
}
context.stroke();
context.lineWidth=20;
context.translate(rowSize,canvas.height + Val_min * yScale);
context.scale(1,-1 * yScale);
// Color of each dataplot items
context.strokeStyle="#2FBC3A";
plotData(Samsung);
}
function plotData(dataSet) {
// context.beginPath();
// context.moveTo(0, dataSet[0]);
// for (i=1;i<sections;i++) {
// context.lineTo(i * xScale, dataSet[i]);
// }
// context.stroke();
var love=0;
for (i=1;i<sections;i++) {
context.beginPath();
context.moveTo(love, dataSet[i-1]);
context.lineTo(i * xScale, dataSet[i]);
love=i*xScale;
context.stroke();
}
}
</script>
</head>
<body onLoad="init()">
<div align="center">
<canvas id="canvas" height="400" width="650">
</canvas>
<br>
<!--Legends for Dataplot -->
<span style="color:#4d4d4d"> Graph </span>
</div>
</body>
</html>
You are changing your context's scaleY in a non-uniform way.
So all the drawings after this operation will get shrunk on the Y axis.
To avoid that, apply this scaling only on your coordinates, at the time of drawing i.e
context.scale(1, -1 * yScale);
...
context.lineTo(x, y);
becomes
context.lineTo(x, y * -1 * yScale);
This way, your coordinate gets correctly scaled, but your stroke keeps its correct scale.
Also, you were drawing each segment separately, which would produce some holes in between of every segments, so I took the liberty of merging them in a single sub-path.
var canvas;
var context;
var Val_max;
var Val_min;
var sections;
var xScale;
var yScale;
var Samsung = [21000, 21000, 23000, 22000, 22000, 23000, 23000];
function init() {
// set these values for your data
sections = 7;
Val_max = 25000;
Val_min = 10000;
var columnSize = 75;
var rowSize = 75;
var margin = 10;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
context.fillStyle = "#4d4d4d";
yScale = (canvas.height - columnSize - margin) / (Val_max - Val_min);
xScale = (canvas.width - rowSize) / sections;
context.lineWidth = 20;
context.translate(rowSize, canvas.height + Val_min * yScale);
//context.scale(1,-1 * yScale);
// ^-- Don't do that.
context.strokeStyle = "#2FBC3A";
plotData(Samsung);
}
function plotData(dataSet) {
var love = 0;
// make a single path from all the segments
context.beginPath();
for (var i = 0; i < sections; i++) {
// Here we scale the coordinate, not the drawings
context.lineTo(i * xScale, dataSet[i] * -1 * yScale);
love = i * xScale;
}
context.stroke();
}
init();
<canvas id="canvas" height="400" width="650">
</canvas>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#canvasOne
{
border: 1px solid black;
}
</style>
<script src="http://code.jquery.com/jquery-1.10.2.js" type="text/javascript"></script>
</head>
<body>
<div align="center">
<canvas id="canvasOne">
</canvas>
</div>
<script type="text/javascript">
var myCanvas = document.getElementById("canvasOne");
var myContext = myCanvas.getContext("2d");
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
init();
var numShapes;
var shapes;
var dragIndex;
var dragging;
var mouseX;
var mouseY;
var dragHoldX;
var dragHoldY;
var timer;
var targetX;
var targetY;
var easeAmount;
var bgColor;
var nodes;
var colorArr;
function init()
{
myCanvas.width = $(window).width() - 200;
myCanvas.height = $(window).height() - 200;
shapes = [];
nodes = ["0;Person;24828760;Alok Kumar;Gorakhpur;#F44336;28",
"0;Suspect;04/Dec/2016;4;Suman_Biswas;#3F51B5;20","1;Rule;4;Apparent Means;3 Parameter;#EEFF41;20",
"0;Policy;36QA649749;In-Force;Quarterly;#FF9800;20","3;Product;Pension;Saral Pension;SRPEN;#795548;20","3;Payment;Cheque;Realized;Lucknow;#0091EA;20",
"0;Policy;162348873;Lapsed;Quarterly;#FF9800;20","6;Product;Pension;Life-Long Pension;LLPP;#795548;20","6;Payment;Cheque;Realized;Gorakhpur;#0091EA;20",
"0;Policy;1EQF178639;Lapsed;Monthly;#FF9800;20","9;Product;Life;Shield;SHIELDA;#795548;20","9;Payment;Demand Draft;Realized;Lucknow;#0091EA;20"];
numShapes = nodes.length;
makeShapes();
drawScreen();
myCanvas.addEventListener("mousedown", mouseDownListener, false);
}
//drawing
function makeShapes()
{
var tempX;
var tempY;
for(var i = 0; i < numShapes; i++)
{
var centerX = myCanvas.width/2;
var centerY = myCanvas.height/2;
var nodeColor = nodes[i].split(";")[5];
var nodeRadius = nodes[i].split(";")[6];
var nodeConnect = nodes[i].split(";")[0];
if(i == 0)//center of circle
{
tempX = centerX
tempY = centerY;
}
else
{
//tempX = Math.random() * (myCanvas.width - tempRadius);
//tempY = Math.random() * (myCanvas.height - tempRadius);
//var x = x0 + r * Math.cos(2 * Math.PI * i / items);
//var y = y0 + r * Math.sin(2 * Math.PI * i / items);
//250 is the distance from center node to outside nodes it can be actual radius in degrees
tempX = shapes[nodeConnect].x + 300 * Math.cos(2 * Math.PI * i / numShapes);
tempY = shapes[nodeConnect].y + 300 * Math.sin(2 * Math.PI * i / numShapes);
}
tempShape = {x: tempX, y: tempY, rad: nodeRadius, color: nodeColor, text: nodes[i]};
shapes.push(tempShape);
}
}
//drawing both shape (line and circle) and screen
function drawScreen()
{
myContext.fillStyle = "#ffffff";
myContext.fillRect(0, 0, myCanvas.width, myCanvas.height);
drawShapes();
}
function drawShapes()
{
//line
for(var i = 1; i < numShapes; i++)
{
myContext.beginPath();
myContext.strokeStyle = "#B2B19D";
var nodeConnect = nodes[i].split(";")[0];
myContext.moveTo(shapes[nodeConnect].x, shapes[nodeConnect].y);
myContext.lineTo(shapes[i].x, shapes[i].y);
myContext.stroke();
}
//circle
for(var i = 0; i < numShapes; i++)
{
myContext.fillStyle = shapes[i].color;
myContext.beginPath();
myContext.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2*Math.PI, false);
myContext.closePath();
myContext.fill();
}
//text
for(var i = 0; i < numShapes; i++)
{
myContext.beginPath();
myContext.font = '10pt Arial';
myContext.fillStyle = 'black';
var textarr = shapes[i].text.split(";");
myContext.fillText(textarr[1], shapes[i].x + 30, shapes[i].y - 24);
/*myContext.fillText(textarr[2], shapes[i].x + 30, shapes[i].y + 1);
myContext.fillText(textarr[3], shapes[i].x + 30, shapes[i].y + 22);
myContext.fillText(textarr[4], shapes[i].x + 30, shapes[i].y + 44);*/
myContext.closePath();
myContext.fill();
}
}
//animation
function mouseDownListener(evt)
{
var highestIndex = -1;
var bRect = myCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left) * (myCanvas.width/bRect.width);
mouseY = (evt.clientY - bRect.top) * (myCanvas.height/bRect.height);
for(var i = 0; i < numShapes; i++)
{
if(hitTest(shapes[i], mouseX, mouseY))
{
dragging = true;
if(i > highestIndex)
{
dragHoldX = mouseX - shapes[i].x;
dragHoldY = mouseY - shapes[i].y;
highestIndex = i;
dragIndex = i;
}
}
}
if(dragging)
{
window.addEventListener("mousemove", mouseMoveListener, false);
}
myCanvas.removeEventListener("mousedown", mouseDownListener, false);
window.addEventListener("mouseup", mouseUpListener, false);
if(evt.preventDefault)
{
evt.preventDefault;
}
return false;
}
function mouseMoveListener(evt)
{
var shapeRad = shapes[dragIndex].rad;
var minX = shapeRad;
var maxX = myCanvas.width - shapeRad;
var minY = shapeRad;
var maxY = myCanvas.height - shapeRad;
//get mouse position correctly
var bRect = myCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left)*(myCanvas.width / bRect.width);
mouseY = (evt.clientY - bRect.top)*(myCanvas.height / bRect.height);
//clamp x and y position to prevent object from dragging outside canvas
posX = mouseX - dragHoldX;
posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
posY = mouseY - dragHoldY;
posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);
shapes[dragIndex].x = posX;
shapes[dragIndex].y = posY;
drawScreen();
}
function mouseUpListener(evt)
{
myCanvas.addEventListener("mousedown", mouseDownListener, false);
window.removeEventListener("mouseup", mouseUpListener, false);
if(dragging)
{
dragging = false;
window.removeEventListener("mousemove", mouseMoveListener, false);
}
}
function hitTest(shape, mx, my)
{
var dx = mx - shape.x;
var dy = my - shape.y;
return(dx * dx + dy * dy < shape.rad * shape.rad);
}
</script>
</body>
</html>
The following canvas animation creates nodes and edges. However due
to space constraint, some of the nodes are not visible due to canvas
height and width. Even adding overflow css to canvas dosen't help as
i am not able to scroll.
<canvas> context doesn't have a built-in scroll method.
You then have multiple ways to circumvent this limitation.
The first one, is as in #markE's answer, to scale your context's matrix so that your drawings fit into the required space. You could also refactor your code so that all coordinates are relative to the canvas size.
This way, you won't need scrollbars and all your drawings will just be scaled appropriately, which is the desirable behavior in most common cases.
But if you really need to have some scrolling feature, here are some ways :
The easiest and most recommended one : let the browser handle it.
You will have to set the size of your canvas to the maximum of your drawings, and wrap it in an other element which will scroll. By setting the overflow:auto css property on the container, our scrollbars appear and we have our scrolling feature.
In following example, the canvas is 5000px wide and the container 200px.
var ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
for (var w = 0; w < canvas.width; w += 100) {
for (var h = 0; h < canvas.height; h += 100) {
ctx.fillText(w + ',' + h, w, h);
}
}
#container {
width: 200px;
height: 200px;
overflow: auto;
border: 1px solid;
}
canvas{
display: block;
}
<div id="container">
<canvas id="canvas" height="5000" width="5000"></canvas>
</div>
Main advantages :
easily implemented.
users are used to these scrollbars.
Main caveats :
You're limited by canvas maximum sizes.
If your canvas is animated, you'll also draw for each frame parts of the canvas that aren't visible.
You have small control on scrollbars look and you'll still have to implement drag-to-scroll feature yourself for desktop browsers.
A second solution, is to implement this feature yourself, using canvas transform methods : particularly translate, transform and setTransform.
Here is an example :
var ctx = canvas.getContext('2d');
var app = {};
// the total area of our drawings, can be very large now
app.WIDTH = 5000;
app.HEIGHT = 5000;
app.draw = function() {
// reset everything (clears the canvas + transform + fillStyle + any other property of the context)
canvas.width = canvas.width;
// move our context by the inverse of our scrollbars' left and top property
ctx.setTransform(1, 0, 0, 1, -app.scrollbars.left, -app.scrollbars.top);
ctx.textAlign = 'center';
// draw only the visible area
var visibleLeft = app.scrollbars.left;
var visibleWidth = visibleLeft + canvas.width;
var visibleTop = app.scrollbars.top
var visibleHeight = visibleTop + canvas.height;
// you probably will have to make other calculations than these ones to get your drawings
// to draw only where required
for (var w = visibleLeft; w < visibleWidth + 50; w += 100) {
for (var h = visibleTop; h < visibleHeight + 50; h += 100) {
var x = Math.round((w) / 100) * 100;
var y = Math.round((h) / 100) * 100;
ctx.fillText(x + ',' + y, x, y);
}
}
// draw our scrollbars on top if needed
app.scrollbars.draw();
}
app.scrollbars = function() {
var scrollbars = {};
// initial position
scrollbars.left = 0;
scrollbars.top = 0;
// a single constructor for both horizontal and vertical
var ScrollBar = function(vertical) {
var that = {
vertical: vertical
};
that.left = vertical ? canvas.width - 10 : 0;
that.top = vertical ? 0 : canvas.height - 10;
that.height = vertical ? canvas.height - 10 : 5;
that.width = vertical ? 5 : canvas.width - 10;
that.fill = '#dedede';
that.cursor = {
radius: 5,
fill: '#bababa'
};
that.cursor.top = vertical ? that.cursor.radius : that.top + that.cursor.radius / 2;
that.cursor.left = vertical ? that.left + that.cursor.radius / 2 : that.cursor.radius;
that.draw = function() {
if (!that.visible) {
return;
}
// remember to reset the matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
// you can give it any shape you like, all canvas drawings operations are possible
ctx.fillStyle = that.fill;
ctx.fillRect(that.left, that.top, that.width, that.height);
ctx.beginPath();
ctx.arc(that.cursor.left, that.cursor.top, that.cursor.radius, 0, Math.PI * 2);
ctx.fillStyle = that.cursor.fill;
ctx.fill();
};
// check if we're hovered
that.isHover = function(x, y) {
if (x >= that.left - that.cursor.radius && x <= that.left + that.width + that.cursor.radius &&
y >= that.top - that.cursor.radius && y <= that.top + that.height + that.cursor.radius) {
// we are so record the position of the mouse and set ourself as the one hovered
scrollbars.mousePos = vertical ? y : x;
scrollbars.hovered = that;
that.visible = true;
return true;
}
// we were visible last call and no wheel event is happening
else if (that.visible && !scrollbars.willHide) {
that.visible = false;
// the app should be redrawn
return true;
}
}
return that;
};
scrollbars.horizontal = ScrollBar(0);
scrollbars.vertical = ScrollBar(1);
scrollbars.hovered = null;
scrollbars.dragged = null;
scrollbars.mousePos = null;
// check both of our scrollbars
scrollbars.isHover = function(x, y) {
return this.horizontal.isHover(x, y) || this.vertical.isHover(x, y);
};
// draw both of our scrollbars
scrollbars.draw = function() {
this.horizontal.draw();
this.vertical.draw();
};
// check if one of our scrollbars is visible
scrollbars.visible = function() {
return this.horizontal.visible || this.vertical.visible;
};
// hide it...
scrollbars.hide = function() {
// only if we're not using the mousewheel or dragging the cursor
if (this.willHide || this.dragged) {
return;
}
this.horizontal.visible = false;
this.vertical.visible = false;
};
// get the area's coord relative to our scrollbar
var toAreaCoord = function(pos, scrollBar) {
var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;
var ratio = (pos - sbBase) / (sbMax - sbBase);
return areaMax * ratio;
};
// get the scrollbar's coord relative to our total area
var toScrollCoords = function(pos, scrollBar) {
var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;
var ratio = pos / areaMax;
return ((sbMax - sbBase) * ratio) + sbBase;
}
scrollbars.scroll = function() {
// check which one of the scrollbars is active
var vertical = this.hovered.vertical;
// until where our cursor can go
var maxCursorPos = this.hovered[vertical ? 'height' : 'width'];
var pos = vertical ? 'top' : 'left';
// check that we're not out of the bounds
this.hovered.cursor[pos] = this.mousePos < 0 ? 0 :
this.mousePos > maxCursorPos ? maxCursorPos : this.mousePos;
// seems ok so tell the app we scrolled
this[pos] = toAreaCoord(this.hovered.cursor[pos], this.hovered);
// redraw everything
app.draw();
}
// because we will hide it after a small time
scrollbars.willHide;
// called by the wheel event
scrollbars.scrollBy = function(deltaX, deltaY) {
// it's not coming from our scrollbars
this.hovered = null;
// we're moving horizontally
if (deltaX) {
var newLeft = this.left + deltaX;
// make sure we're in the bounds
this.left = newLeft > app.WIDTH - canvas.width ? app.WIDTH - canvas.width : newLeft < 0 ? 0 : newLeft;
// update the horizontal cursor
this.horizontal.cursor.left = toScrollCoords(this.left, this.horizontal);
// show our scrollbar
this.horizontal.visible = true;
}
if (deltaY) {
var newTop = this.top + deltaY;
this.top = newTop > app.HEIGHT - canvas.height ? app.HEIGHT - canvas.height : newTop < 0 ? 0 : newTop;
this.vertical.cursor.top = toScrollCoords(this.top, this.vertical);
this.vertical.visible = true;
}
// if we were called less than the required timeout
clearTimeout(this.willHide);
this.willHide = setTimeout(function() {
scrollbars.willHide = null;
scrollbars.hide();
app.draw();
}, 500);
// redraw everything
app.draw();
};
return scrollbars;
}();
var mousedown = function(e) {
// tell the browser we handle this
e.preventDefault();
// we're over one the scrollbars
if (app.scrollbars.hovered) {
// new promotion ! it becomes the dragged one
app.scrollbars.dragged = app.scrollbars.hovered;
app.scrollbars.scroll();
}
};
var mousemove = function(e) {
// check the coordinates of our canvas in the document
var rect = canvas.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
// we're dragging something
if (app.scrollbars.dragged) {
// update the mouse position
app.scrollbars.mousePos = app.scrollbars.dragged.vertical ? y : x;
app.scrollbars.scroll();
} else if (app.scrollbars.isHover(x, y)) {
// something has changed, redraw to show or hide the scrollbar
app.draw();
}
e.preventDefault();
};
var mouseup = function() {
// we dropped it
app.scrollbars.dragged = null;
};
var mouseout = function() {
// we're out
if (app.scrollbars.visible()) {
app.scrollbars.hide();
app.scrollbars.dragged = false;
app.draw();
}
};
var mouseWheel = function(e) {
e.preventDefault();
app.scrollbars.scrollBy(e.deltaX, e.deltaY);
};
canvas.addEventListener('mousemove', mousemove);
canvas.addEventListener('mousedown', mousedown);
canvas.addEventListener('mouseup', mouseup);
canvas.addEventListener('mouseout', mouseout);
canvas.addEventListener('wheel', mouseWheel);
range.onchange = function() {
app.WIDTH = app.HEIGHT = this.value;
app.scrollbars.left = 0;
app.scrollbars.top = 0;
app.draw();
};
// an initial drawing
app.draw();
canvas {border: 1px solid;}
span{font-size: .8em;}
<canvas id="canvas" width="200" height="150"></canvas>
<span>
change the total area size
<input type="range" min="250" max="5000000" steps="250" value="5000" id="range" />
</span>
Main advantages :
no limitation for the size of your drawing areas.
you can customize your scrollbars as you wish.
you can control when the scrollbars are enable or not.
you can get the visible area quite easily.
Main caveats:
a bit more code than the CSS solution...
no really, that's a lot of code...
A third way I wrote some time ago for an other question took advantage of the ability to draw an other canvas with ctx.drawImage(). It has its own caveats and advantages, so I let you pick the one you need, but this last one also had a drag and slide feature which can be useful.
So your node drawings don't fit on the canvas size?
You can easily "shrink" your content to fit the visible canvas with just 1 command!
The context.scale(horizontalRescale,verticalRescale) command will shrink every following drawing by your specified horizontalRescale & verticalRescale percentages.
An Important note: You must make horizontalRescale,verticalRescale the same value or your content will be distorted.
The nice thing about using context.scale is that you don't have to change any of the code that draws your nodes ... canvas automatically scales all those nodes for you.
For example, this code will shrink your nodes to 80% of their original size:
var downscaleFactor= 0.80;
context.scale( downscaleFactor, downscaleFactor );
Rather than go through your 200+ lines of code, I leave it to you to calculate downscaleFactor.
I want to find a length of line and radius of circle on image depend on image width
see below image
var canvas = document.getElementById('loadCanvas'),lastPos, isDown = false;
ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
ctx.lineCap = "round";
ctx.lineWidth = $('#canvasSelWidth').val();
ctx.globalCompositeOperation = "multiply";
ctx.strokeStyle = $('#canvasSelColor').val();
canvas.onmousedown = function(e) {
isDown = true;
SPos = getPos(e);
lastPos = getPos(e);
};
window.onmousemove = function(e) {
if (!isDown) return;
var pos = getPos(e);
ctx.beginPath();
ctx.moveTo(lastPos.x, lastPos.y);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
lastPos = pos;
};
window.onmouseup = function(e) {
isDown = false
lPos = getPos(e);
measurementOnImageCanvas();
};
function getPos(e) {
var rect = canvas.getBoundingClientRect();
xPosition = e.clientX - rect.left;
yPosition = e.clientY - rect.top;
return {x: e.clientX - rect.left, y: e.clientY - rect.top}
}
I am use for first coordinate lPos and last SPos.
var xCorData = lPos.x - SPos.x
var yCorData = lPos.y - SPos.y
var finalPixel = Math.sqrt( xCorData*xCorData + yCorData*yCorData );
var centimeters = finalPixel * 2.54 / 96;
var mm = centimeters*10;
var inch = mm*0.0393701;
Please help me for short out from this problem
Can not be done
I am assuming you wish to get the physical size of a pixel on the client machine. Unfortunately there is no way to get the display dimensions.
window.screen.width and window.screen.height will get you the resolution of the display but there is no way of knowing the size of the display. Even if it was possible to get the device brand and model you still do not know if it is using its own display or is plugged into another. Even worse, multi display setups may have two or more different screen sizes so that your canvas has regions where the pixel physical size is different.
All you can do is ask the clien to enter the screen dimetions.
Assuming you have the screen diagonal.
At the moment I am on a 17.3 laptop with a 1680 by 945 pixel display. To get the pixel size.
Assume that the pixels are square.
const mmPerInch = 25.4; // constant
var screenDiagonal = 17.3 * mmPerInch; // ??? how to get this (17.3) is the problem
var resX = window.screen.width; // nor do you know if the pixel is square
var resY = window.screen.height;
// now get the number of pixels diagonally
if(typeof Math.hypot === "function"){ // use the new hypot function if available
var pixelsDiagonal = Math.hypot(resX,resY);
}else{
var pixelsDiagonal = Math.sqrt(resX*resX+resY*resY);
}
// then divide the screen size by the pixels to get the pixel size.
var pixelSize_mm = screenDiagonal / pixelsDiagonal;
// result pixel is 0.23 by 0.23 mm
You now have the size of a pixel in mm and can use that to get a accurate measure of objects you render. But it is no guarantee as the browser may be zoomed in or out.
To convert from pixels to mm just multiply pixel dimensions by pixel size
function pixel2mm(pixels){
return pixels * pixelSize_mm;
}
Also asking for the diagonal is no guarantee that the correct value is entered or even known. Also not all pixels are square and that will be even harder to find out.
If you want to measure things on a image more than on the screen, you have all the necessary code, just you needed to tie up togheter.
You have to ASSUME that you are on a 96 DPI device or putting the DPI as a parameter. Also giving a fixed scale for your canvas is the another way to go ( as if you would be on a map ).
var canvas = document.getElementById('loadCanvas'),lastPos, isDown = false;
ctx = canvas.getContext("2d");
ctx.lineCap = "round";
ctx.lineWidth = 2;
ctx.strokeStyle = 'blue';
canvas.onmousedown = function(e) {
isDown = true;
SPos = getPos(e);
lastPos = SPos;
};
window.onmousemove = function(e) {
if (!isDown) return;
var pos = getPos(e);
ctx.clearRect(0,0,500,500)
ctx.beginPath();
ctx.moveTo(lastPos.x, lastPos.y);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
lPos = pos;
};
window.onmouseup = function(e) {
isDown = false
lPos = getPos(e);
measurementOnImageCanvas();
};
function getPos(e) {
var rect = canvas.getBoundingClientRect();
xPosition = e.clientX - rect.left;
yPosition = e.clientY - rect.top;
return {x: e.clientX - rect.left, y: e.clientY - rect.top}
}
function measurementOnImageCanvas() {
var xCorData = lPos.x - SPos.x
var yCorData = lPos.y - SPos.y
var finalPixel = Math.sqrt( xCorData*xCorData + yCorData*yCorData );
var inches = finalPixel / 96;
var centimeters = inches * 2.54;
var millimiters = centimeters * 10;
alert('line lenght:\n' + inches.toFixed(2) + ' inches\n' + centimeters.toFixed(4) + ' centimeters\n' + millimiters.toFixed(2) + ' millimeters\nAssuming you are on a 96 DPI device');
}
<canvas id="loadCanvas" width=500 height=500 />
I have a website which targets mobile phones, if a user clicks on a 16:9 scaled responsive image, I want clicked image to be displayed landscape, so that the user can rotate his phone to view the image in full size. I can't save the image rotated because it is an external resource. Is there any way I can achieve the desired effect?
I can't simply rotate 90° because that would rotate the scaled image.
If I rotate the unscaled image, it extends the portrait viewport to landscape width and displays the rotated image centered.
I already tried using a canvas, but it didn't nearly fit my needs, the image gets destroyed by bad downscaling on Firefox using JSFiddle while it didn't display at all on the Intel XDK emulator.
Since my scenario doesn't seem that uncommon to me, I'm wondering whether I missed an easy solution.
Found a jsfiddle doing that, and made it simpler for you.
Check it out: http://jsfiddle.net/7fw66/39/
html:
<div>
<canvas id="canvas"><img id="image" src="http://3.bp.blogspot.com/-JhISDA9aj1Q/UTECr1GzirI/AAAAAAAAC2o/5qmvWZiCMRQ/s1600/Twitter.png" /></canvas>
</div>
javascript:
var screenwidth = 400;
var imgurl = "http://3.bp.blogspot.com/-JhISDA9aj1Q/UTECr1GzirI/AAAAAAAAC2o/5qmvWZiCMRQ/s1600/Twitter.png";
$(document).ready(function () {
loadImage();
$('#restore').click(function () {
loadImage();
});
$('#rotate_ccw').click(function () {
rotate_ccw();
});
$('#rotate_cw').click(function () {
rotate_cw();
});
});
function loadImage() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, 1, 0, 0);
var img = new Image();
if (imgurl == null || imgurl == "") {
imgurl = defaultimgurl;
}
img.src = imgurl;
var maxsize = screenwidth;
var w = maxsize;
var ratio = (img.width / w);
var h = (img.height / ratio);
canvas.width = w;
canvas.height = h;
img.onload = function () {
ctx.drawImage(img, 0, 0, w, h);
};
ctx.save();
}
function rotate_cw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = imgurl;
var maxsize = screenwidth;
var w = maxsize;
var ratio = (img.width / w);
var h = (img.height / ratio);
canvas.width = h;
canvas.height = w;
ctx.translate(w - h, w);
ctx.rotate((-90 * Math.PI) / 180);
ctx.translate(0, -(w - h));
ctx.drawImage(img, 0, 0, w, h);
ctx.save();
}
function rotate_ccw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = imgurl;
var maxsize = screenwidth;
var w = maxsize;
var ratio = (img.width / w);
var h = (img.height / ratio);
canvas.width = h;
canvas.height = w;
ctx.translate(h, 0);
ctx.rotate((90 * Math.PI) / 180);
ctx.drawImage(img, 0, 0, w, h);
ctx.save();
}
function urlProvided() {
var userinput = document.getElementById('imageurl');
imgurl = userinput.value;
loadImage();
}
Hello all I've tried relentlessly to write a script that will check the width of a string and if it is greater than a certain amount, lower the font size and draw it again. Except this is not working. Once I initially draw the text, it never updates to the correct font size. It just stays what it was originally and never gets any smaller throwing the script into an endless loop. Here is my code:
var nFontSize = 25;
var draw_name = function(text) {
var c=document.getElementById("badgeCanvas");
var ctx=c.getContext("2d");
ctx.font="nFontSize OpenSansBold";
ctx.fillStyle = "#000000";
ctx.fillText(text,80,25);
var metrics = ctx.measureText(text);
var width = metrics.width;
while (width > 200) {
nFontSize--;
ctx.font="nFontSize OpenSansBold";
ctx.fillText(text,80,25);
metrics = ctx.measureText(text);
width = metrics.width;
}
ctx.font="nFontSize OpenSansBold";
ctx.fillStyle = "#000000";
ctx.fillText(text,80,25);
"nFontSize OpenSansBold" is not a valid font. Try
ctx.font= nFontSize + "px OpenSansBold";
Also, you don't have to render it to measure the text so remove the ctx.fillText(text,80,25); before your while loop and inside your while loop. Otherwise, it will look ugly.
var nFontSize = 25;
var draw_name = function(text) {
var c=document.getElementById("badgeCanvas");
var ctx=c.getContext("2d");
ctx.font= nFontSize + "px OpenSansBold";
ctx.fillStyle = "#000000";
var metrics = ctx.measureText(text);
var width = metrics.width;
while (width > 200) {
nFontSize--;
ctx.font= nFontSize + "px OpenSansBold";
metrics = ctx.measureText(text);
width = metrics.width;
}
ctx.font= nFontSize + "px OpenSansBold";
ctx.fillStyle = "#000000";
ctx.fillText(text,80,25);