|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
div { float: left; position: relative; } |
|
#left { border-right: 1px solid #ccc; } |
|
#right { border-left: 1px solid #ccc; } |
|
span { |
|
position: absolute; |
|
top: 11px; left: 11px; |
|
font-family: sans-serif; |
|
font-size: 11px; |
|
font-weight: 700; |
|
} |
|
</style> |
|
<body> |
|
<div id="left"> |
|
<span>Non-scaled canvas</span> |
|
</div> |
|
<div id="right"> |
|
<span>DPI-scaled canvas</span> |
|
</div> |
|
<script src="//d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
var width = 958 |
|
var height = 500 |
|
|
|
var canvases = [ |
|
canvas('#left', [width / 2, height]), |
|
canvas('#right', [width / 2, height], [width / 2, 0]).retinaScaled() |
|
] |
|
|
|
function canvas(sel, size, offset) { |
|
offset = offset || [0, 0] |
|
|
|
var width = size[0] |
|
var height = size[1] |
|
|
|
var canvas = d3.select(sel).append('canvas') |
|
.attr('width', width) |
|
.attr('height', height) |
|
|
|
var ctx = canvas.node().getContext('2d') |
|
|
|
function draw(node) { |
|
ctx.clearRect(0, 0, width, height) |
|
|
|
var x = node.x - offset[0] |
|
var y = node.y - offset[1] |
|
|
|
if (x + node.width < 0 || x >= width || y + node.height < 0 || y >= height) { |
|
return |
|
} |
|
|
|
ctx.beginPath() |
|
ctx.moveTo(x, y) |
|
ctx.lineTo(x += node.width, y) |
|
ctx.lineTo(x, y += node.height) |
|
ctx.lineTo(x -= node.width, y) |
|
ctx.lineTo(x, y -= node.height) |
|
|
|
ctx.strokeStyle = '#f00' |
|
ctx.stroke() |
|
|
|
ctx.font = node.height * 0.75 + 'px Georgia, serif' |
|
ctx.textAlign = 'center' |
|
ctx.strokeText('A', x + node.width / 2, y + node.height * 0.75) |
|
} |
|
|
|
draw.retinaScaled = function () { |
|
if (!window.devicePixelRatio) { return } |
|
|
|
canvas |
|
.attr('width', width * window.devicePixelRatio) |
|
.attr('height', height * window.devicePixelRatio) |
|
.style('width', width + 'px') |
|
.style('height', height + 'px') |
|
|
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio) |
|
|
|
return this |
|
} |
|
|
|
return draw |
|
} |
|
|
|
var size = 420 |
|
var velocity = 2 |
|
var angle = 0 |
|
|
|
var node = { |
|
x: width / 2 - size / 2, |
|
y: height / 2 - size / 2, |
|
vx: velocity * Math.cos(angle * Math.PI / 180), |
|
vy: velocity * Math.sin(angle * Math.PI / 180), |
|
width: size, |
|
height: size |
|
} |
|
|
|
var boxForce = boundedBox() |
|
.bounds([[0, 0], [width, height]]) |
|
.size(function (d) { return [d.width, d.height] }) |
|
|
|
d3.forceSimulation() |
|
.velocityDecay(0) |
|
.alphaTarget(1) |
|
.on('tick', ticked) |
|
.force('box', boxForce) |
|
.nodes([node]) |
|
|
|
function ticked() { |
|
canvases.forEach(function (c) { c(node) }) |
|
} |
|
|
|
function boundedBox() { |
|
var nodes |
|
var bounds |
|
var size |
|
var sizes |
|
|
|
function force() { |
|
var node |
|
var size |
|
var i = -1 |
|
while (++i < nodes.length) { |
|
node = nodes[i] |
|
size = sizes[i] |
|
if (node.x + node.vx < bounds[0][0] || node.x + node.vx + size[0] > bounds[1][0]) { |
|
node.x += node.vx |
|
node.vx = -node.vx |
|
} |
|
if (node.y + node.vy < bounds[0][1] || node.y + node.vy + size[1] > bounds[1][1]) { |
|
node.y += node.vy |
|
node.vy = -node.vy |
|
} |
|
} |
|
} |
|
|
|
force.initialize = function (_) { |
|
sizes = (nodes = _).map(size) |
|
} |
|
|
|
force.bounds = function (_) { |
|
return (arguments.length ? (bounds = _, force) : bounds) |
|
} |
|
|
|
force.size = function (_) { |
|
return (arguments.length |
|
? (size = typeof _ === 'function' ? _ : constant(_), force) |
|
: size) |
|
} |
|
|
|
return force |
|
} |
|
|
|
function constant(_) { |
|
return function () { return _ } |
|
} |
|
|
|
</script> |
|
</body> |